Most efficient way to write a mapping function given a large CSV of recoded data

Viewed 91

Imagine I have a dataframe that is loaded from a large csv someone has given me, containing a mapping/recode of data that I want to apply to other datasets. Here's a small reproducible example of what might be in the csv:

library(wakefield)
csv_mapping <- data.frame(
  from = as.character(name(30)),
  to = as.character(likert_7(30))  
)

What is the quickest way to create a mapping function from this dataframe in a way that is independent of the csv data source? I would usually do it by running:

dput(csv_mapping$from)
dput(csv_mapping$to)

in my console and then I would copy and paste the vectors into a function and use plyr::mapvalues() as follows:

mapping_fn <- function(x) {

  fromvec <- c("Kameira", "Sanavi", "Avangelene", "Maryonna", "Wyvonna", "Enam", 
               "Yain", "Tyonna", "Shekira", "Eleanna", "Azriela", "Saajida", 
               "Chantee", "Julieanne", "Genisha", "Delesha", "Macenzi", "Alyasia", 
               "Latonga", "Josuhe", "Arter", "Stone", "Ramaj", "Lilinoe", "Zacharie", 
               "Joshuamichael", "Desseray", "Colorado", "Jaidn", "Verline")

  tovec <- c("Agree", "Somewhat Disagree", "Agree", "Agree", "Neutral", 
          "Somewhat Disagree", "Neutral", "Strongly Agree", "Somewhat Disagree", 
          "Disagree", "Strongly Disagree", "Disagree", "Somewhat Agree", 
          "Strongly Disagree", "Strongly Disagree", "Somewhat Agree", "Strongly Agree", 
          "Somewhat Agree", "Disagree", "Disagree", "Strongly Agree", "Strongly Disagree", 
          "Disagree", "Somewhat Agree", "Strongly Disagree", "Strongly Disagree", 
          "Neutral", "Somewhat Agree", "Agree", "Disagree")

  plyr::mapvalues(x, from = fromvec, to = tovec, warn_missing = F)

}

Is there a cleverer or quicker way to do this without using mapvalues, given that plyr is considered retired now?

3 Answers

A very simple solution using recode from dplyr package

level_key <- setNames(csv_mapping$to, csv_mapping$from)
dplyr::recode(csv_mapping$from, !!!level_key)

Basically we create the named vector level_key that contains the key-value pairs, and afterwards we use unquote splicing inside the recode function.


Example

library(wakefield)
set.seed(42)
csv_mapping <- data.frame(
  from = as.character(name(5)),
  to = as.character(likert_7(5))  
)
csv_mapping

#       from                to
# 1 Merrissa Strongly Disagree
# 2  Lilbert           Neutral
# 3  Rudelle    Strongly Agree
# 4  Kaymani Somewhat Disagree
# 5   Kenadi          Disagree

level_key <- setNames(csv_mapping$to, csv_mapping$from)
dplyr::recode(csv_mapping$from, !!!level_key)
# [1] "Strongly Disagree" "Neutral"           "Strongly Agree"    "Somewhat Disagree" "Disagree"

One natural way to do this is with a join. This is particularly useful if your data is already in a dataframe, though you can massage it if you truly only want the vector of mapped values.

Say we have a mapping defined by the csv like so:

csv_mapping <- data.frame(from = c("Kameira", "Sanavi", "Avangelene", 
                                   "Maryonna", "Wyvonna"),
                          to = c("Agree", "Somewhat Disagree", "Agree",
                                 "Agree", "Neutral"))

csv_mapping
#>         from                to
#> 1    Kameira             Agree
#> 2     Sanavi Somewhat Disagree
#> 3 Avangelene             Agree
#> 4   Maryonna             Agree
#> 5    Wyvonna           Neutral

Then say we have a dataframe df where the column x gives the values we'd like to map to new values. Note that df can also contain other columns, in this case we'll add some random values for demontstration.

df <- data.frame(x = c("Sanavi", "Maryonna", "Maryonna", "Wyvonna",
                       "Kameira","Avangelene", "Sanavi", "Wyvonna"),
                 vals = rnorm(8))

df
#>            x        vals
#> 1     Sanavi -0.95005745
#> 2   Maryonna -0.20650715
#> 3   Maryonna -0.07755789
#> 4    Wyvonna  1.72379970
#> 5    Kameira -1.36642679
#> 6 Avangelene -1.48638577
#> 7     Sanavi  0.16987157
#> 8    Wyvonna -0.55194346

Then, we can use dplyr's left_join to bring in the mapped values to the dataframe. (You can read more here).

dplyr::left_join(df, csv_mapping, by = c("x" = "from"))
#>            x        vals                to
#> 1     Sanavi -0.95005745 Somewhat Disagree
#> 2   Maryonna -0.20650715             Agree
#> 3   Maryonna -0.07755789             Agree
#> 4    Wyvonna  1.72379970           Neutral
#> 5    Kameira -1.36642679             Agree
#> 6 Avangelene -1.48638577             Agree
#> 7     Sanavi  0.16987157 Somewhat Disagree
#> 8    Wyvonna -0.55194346           Neutral

At this point, you have each x value's corresponding to value from the given map. If you only want those to values you can simply pull the to column from the dataframe.

Created on 2020-06-03 by the reprex package (v0.3.0)

So based on Ric S answer above, I could use my original approach but use dplyr instead of plyr like this:

mapping_fn <- function(x) {

    fromvec <-  c("Kameira", "Sanavi", "Avangelene", "Maryonna", "Wyvonna", "Enam", 
                 "Yain", "Tyonna", "Shekira", "Eleanna", "Azriela", "Saajida", 
                 "Chantee", "Julieanne", "Genisha", "Delesha", "Macenzi", "Alyasia", 
                 "Latonga", "Josuhe", "Arter", "Stone", "Ramaj", "Lilinoe", "Zacharie", 
                 "Joshuamichael", "Desseray", "Colorado", "Jaidn", "Verline")
    
    tovec <- c("Agree", "Somewhat Disagree", "Agree", "Agree", "Neutral", 
               "Somewhat Disagree", "Neutral", "Strongly Agree", "Somewhat Disagree", 
               "Disagree", "Strongly Disagree", "Disagree", "Somewhat Agree", 
               "Strongly Disagree", "Strongly Disagree", "Somewhat Agree", "Strongly Agree", 
               "Somewhat Agree", "Disagree", "Disagree", "Strongly Agree", "Strongly Disagree", 
               "Disagree", "Somewhat Agree", "Strongly Disagree", "Strongly Disagree", 
               "Neutral", "Somewhat Agree", "Agree", "Disagree")

  
    level_key <- setNames(tovec, fromvec)
    dplyr::recode(x, !!!level_key)
    
}

Related