1
votes

I'm trying to mutate and replace column values with vectors in stringr. I'm having some issues which I guess is related to how the function recycles. I'm new to R and can't seem to figure out exactly what I'm doing wrong.

The column I'd like to change:

[1] "3+4" "3+3"  NA    "3+4"  NA   "4+3" "4+4" "4+3" "4+4" "5+4" "4+3" "4+3" "3+4" "4+3"
[15] "4"   NA    "4+3" NA    NA    "3+4" "4+5" NA    "3+4" NA    NA    "3+4" NA    "3+4"
[29] "3+4" "3+4" "3+3" "3"   NA    "3+3" "3+3" NA    "4+5" NA    "3+3" "3+4" "4+4" "3+4"
[43] "4+4" "3+3" "3+4" "3+4" NA    "4+3" "4+3" "3+3" "3+3" "3+4"

I'd like to change this to 3+3 = 1, 3+4 = 2, 4+3 = 3, 4+4 = 4, 4+5 = 5, 5+5 = 5. These are Gleason scores and Gleason grade groups for prostate cancer.

Running one at times works just fine:

mrgb_trus <- mrgb_trus %>% 
mutate(MRGGG = str_replace_all(MRGB_gleason, "3\\+4", "2"))

Adding vectors:

mrgb_trus <- mrgb_trus %>% 
mutate(MRGGG = str_replace_all(MRGB_gleason, c("3\\+3", "3\\+4", "4\\+3", 
                                      "4\\+4", "4\\+5", "5\\+4", 
                                      "5\\+5"), c("1", "2", "3", 
                                      "4", "5", "5", "5")))                                                  

produces the warning

Warning message:
In stri_replace_first_regex(string, pattern,   fix_replacement(replacement),  :
longer object length is not a multiple of shorter object length

and does not return the desired output. What am I doing wrong? As you can see there are also some NAs and two values "3" and "4" that don't match the pattern. I'd also like to change the NAs to 0 and 3 and 4 to 1.

2
Do 4+5 and 5+5 both get the level 5 or did you typo there? - LAP

2 Answers

0
votes

One of the approach could be

#define your mapping here
lhs <- c('3+3', '3+4', '4+3', '4+4', '4+5', '5+5', '3', '4')
rhs <- c(1, 2, 3, 4, 5, 5, 1, 1)

df$col1_new <- ifelse(is.na(df$col1), 0, rhs[match(df$col1, lhs)])

which gives

> df$col1_new
 [1]  2  1  0  2  0  3  4  3  4 NA  3  3  2  3  1  0  3  0  0  2  5  0  2  0  0  2  0  2  2  2  1  1  0  1  1  0  5
[38]  0  1  2  4  2  4  1  2  2  0  3  3  1  1  2

Note that you are still missing definition for 5+4 in your sample data.


Sample data:

df <- structure(list(col1 = c("3+4", "3+3", NA, "3+4", NA, "4+3", "4+4", 
"4+3", "4+4", "5+4", "4+3", "4+3", "3+4", "4+3", "4", NA, "4+3", 
NA, NA, "3+4", "4+5", NA, "3+4", NA, NA, "3+4", NA, "3+4", "3+4", 
"3+4", "3+3", "3", NA, "3+3", "3+3", NA, "4+5", NA, "3+3", "3+4", 
"4+4", "3+4", "4+4", "3+3", "3+4", "3+4", NA, "4+3", "4+3", "3+3", 
"3+3", "3+4")), .Names = "col1", row.names = c(NA, -52L), class = "data.frame")
0
votes

To address the error you got: the "all" in str_replace_all isn't that it will replace all values in one vector with all values in another vector. Rather, it's more like setting a global flag in a reprex. It's for situations like this:

stringr::str_replace("a2bb4", "\\d", "x")
#> [1] "axbb4"
stringr::str_replace_all("a2bb4", "\\d", "x")
#> [1] "axbbx"

What you want is rather to recode one set of values as another set of values. Here are 3 tidyverse-based ways.

#  3+3 = 1, 3+4 = 2, 4+3 = 3, 4+4 = 4, 4+5 = 5, 5+5 = 5

library(tidyverse)

x <- c("3+4", "3+3",  NA, "3+4",  NA, "4+3", "4+4", "4+3", "4+4", "5+4", "4+3", "4+3", "3+4", "4+3", "4",   NA, "4+3", NA, NA, "3+4", "4+5", NA, "3+4", NA, NA, "3+4", NA, "3+4", "3+4", "3+4", "3+3", "3",   NA, "3+3", "3+3", NA, "4+5", NA, "3+3", "3+4", "4+4", "3+4", "4+4", "3+3", "3+4", "3+4", NA, "4+3", "4+3", "3+3", "3+3", "3+4")

First, dplyr::recode takes a named vector, where the names are the old values, and the elements are the new values.

recode(x, "3+3" = "1", "3+4" = "2", "4+3" = "3", "4+4" = "4", "4+5" = "5", "5+5" = "5")
#>  [1] "2"   "1"   NA    "2"   NA    "3"   "4"   "3"   "4"   "5+4" "3"  
#> [12] "3"   "2"   "3"   "4"   NA    "3"   NA    NA    "2"   "5"   NA   
#> [23] "2"   NA    NA    "2"   NA    "2"   "2"   "2"   "1"   "3"   NA   
#> [34] "1"   "1"   NA    "5"   NA    "1"   "2"   "4"   "2"   "4"   "1"  
#> [45] "2"   "2"   NA    "3"   "3"   "1"   "1"   "2"

My preference for a task like this has become making factors, because I think of these discrete text values as levels. forcats makes it easy to recode and manipulate factor levels. In this case, I only use fct_recode (which takes old & new values in the reverse order from recode!), but if you had several levels that were being changed to "5", for example, you could use fct_collapse. You also get a warning by using factors of the fact that you tried to recode a level that isn't present, and you get a list of the current factors, which lets you see that you haven't yet recoded "5+4".

fct_recode(as.factor(x), "1" = "3+3", "2" = "3+4", "3" = "4+3", "4" = "4+4", "5" = "4+5", "5" = "5+5")
#> Warning: Unknown levels in `f`: 5+5
#>  [1] 2    1    <NA> 2    <NA> 3    4    3    4    5+4  3    3    2    3   
#> [15] 4    <NA> 3    <NA> <NA> 2    5    <NA> 2    <NA> <NA> 2    <NA> 2   
#> [29] 2    2    1    3    <NA> 1    1    <NA> 5    <NA> 1    2    4    2   
#> [43] 4    1    2    2    <NA> 3    3    1    1    2   
#> Levels: 3 1 2 4 5 5+4

The third way is probably the most sustainable, especially if you need to come back to this in a month or pass information along to a colleague: Make a lookup table and join.

lookup <- tribble(
  ~old_val, ~new_val,
  "3+3",     "1",
  "3+4",     "2",
  "4+3",     "3",
  "4+4",     "4",
  "4+5",     "5",
  "5+5",     "5"
)
tibble(x = x) %>%
  left_join(lookup, by = c("x" = "old_val"))
#> # A tibble: 52 x 2
#>    x     new_val
#>    <chr> <chr>  
#>  1 3+4   2      
#>  2 3+3   1      
#>  3 <NA>  <NA>   
#>  4 3+4   2      
#>  5 <NA>  <NA>   
#>  6 4+3   3      
#>  7 4+4   4      
#>  8 4+3   3      
#>  9 4+4   4      
#> 10 5+4   <NA>   
#> # ... with 42 more rows

Created on 2018-07-02 by the reprex package (v0.2.0).