Replacement of some equal elements of a string using different values in a data frame

Viewed 51

I want to replace each 'COL' word in the column 'b' of the 'test' data frame, by each element in the column 'a', and put the result in other column, but preserving both order and structure of the character string of the column 'b'.

test <- data.frame(a = c("COL167", "COL2010;COL2012"),
                   b = c("COL;MO, K", "P;COL, NY, S, COL"))

I have tried the following, but it is not the result that I need:

for(i in 1:length(test$a)){
    test$c[i] <- gsub(pattern = "COL", x = test$b[i], replacement = test$a[i])
}

> test
                a                  b                                          c
1          COL167          COL;MO, K                               COL167;MO, K
2 COL2010;COL2012  P;COL, NY, S, COL  P;COL2010;COL2012, NY, S, COL2010;COL2012

I expect the following result:

              a                  b                          c
1          COL167          COL;MO, K               COL167;MO, K
2 COL2010;COL2012  P;COL, NY, S, COL  P;COL2010, NY, S, COL2012
2 Answers

Building on what you have already done, I think this would work, but note that you might see some performance issues if your table is large. Also note that, this assumes that size of values to be replaced is equal to values used for replacement.

As gsub doesn't allow for vectorized replacement (replaces all the matched instances with first values of replacement), here I have converted both strings and replacements into vectors, so I can replace each matched substring individually.

test <- data.frame(a = c("COL167", "COL2010;COL2012"),
                   b = c("COL;MO, K", "P;COL, NY, S, COL"))

re = function(string, replacement){
  gsub('COL', replacement, string)
}

for(i in 1:nrow(test)){
  #splitting values of column a into vector, this is required for replacement
  replacement = unlist(strsplit(test$a[i], ';'))
  
  #split values of column b into vecto, this is required for replacement
  b_value = unlist(strsplit(test$b[i], ' '))
  
  #select those which have 'COL' substring
  ind_to_replace = which(grepl('COL', b_value))
  
  #replace matched values
  result = mapply(re, b_value[ind_to_replace], replacement)
  
  #replace the column b value with new string
  b_value[ind_to_replace] = result
  
  #join the string
  test$results[i] = paste(b_value, collapse = ' ')
}

test
#>                 a                 b                   results
#> 1          COL167         COL;MO, K              COL167;MO, K
#> 2 COL2010;COL2012 P;COL, NY, S, COL P;COL2010, NY, S, COL2012

Created on 2020-09-05 by the reprex package (v0.3.0)

I'll propose a solution using the rowwise function of dplyr.

While it's true that gsub isn't vectorized, the mgsub function in the package of the same name is. My approach is for each row:

  1. turn all of the instances of COL in column b into a vector

  2. make a vector from all the COL+ entries from column a

  3. use vector 2 to replace the old values of COL from b. mutate creates a new column with the result.

     library(mgsub)        
     library(stringr)
     library(dplyr)
    
     test %>%
     rowwise() %>%
     mutate(new_col = 
         unlist((mgsub(b,
               unlist(str_extract_all(b,"COL")),
               unlist(str_extract_all(a,"COL.*?\\b")))
      )))
    
      # A tibble: 2 x 3
      # Rowwise: 
         a               b                 new_col                  
       <chr>           <chr>             <chr>                    
    1 COL167          COL;MO, K         COL167;MO, K             
    2 COL2010;COL2012 P;COL, NY, S, COL P;COL2010, NY, S, COL2010
    

mgsub takes 3 arguments. The string you're working on, the expression you want to replace within that string, and the expression you want to use as the replacement. This package allows you to have multiple patterns to replace and be replaced - both can appear as vectors.

I applied this function to each row - first I designated the b column as the string of interest. Second, all the COL's in column b is what we want to replace and I made this into a vector using stringr::str_extract_all. I extracted all instances of COL and then we have to unlist this output because str_extract_all returns a list. Third, I used the same process to extract the COL+ entries from column a. In summary, we use the entries in column a to replace the characters of interest within column b.

    "COL.*?\\b" 

selects the letters COL followed by as few characters as possible before reaching a word boundary which allows us to turn the entries in column a into multiple items (COL2010, COL2012 etc).

We have to unlist the mutated row (i.e. the first "unlist") because dplyr outputs a list-column.

Related