Iterating over columns in a data frame in order to replace values from matching data in list of data frames

Viewed 1355

I'm interested in building a function making use of apply/sapply or Map that would iterate over available columns in dta and replace values in each column with matched values from data frame available in a nameless list of data frames with list item index corresponding to the column number of the dta data frame.

Example

Given objects:

set.seed(1)
size <- 20

# Data set
dta <-
    data.frame(
        unitA = sample(LETTERS[1:4], size = size, replace = TRUE),
        unitB = sample(letters[16:20], size = size, replace = TRUE),
        unitC = sample(month.abb[1:4], size = size, replace = TRUE),
        someValue = sample(1:1e6, size = size, replace = TRUE)
    )

# Meta data
lstMeta <- list(
    # Unit A definitions
    data.frame(
        V1 = c("A", "B", "D"),
        V2 = c("Letter A", "Letter B", "Letter D")
    ),
    # Unit B definitions
    data.frame(
        V1 = c("t", "q"),
        V2 = c("small t", "small q")
    ),
    # Unit C definitions
    data.frame(
        V1 = c("Mar", "Jan"),
        V2 = c("March", "January")
    )
)

Desired results

When applied on dta, the function should return a data.frame corresponding to the extract below:

unitA       unitB    unitC      someValue
Letter B    small t  Apr        912876
Letter B    small q  March      293604
       C    s        Apr        459066
Letter D    p        March      332395
Letter A    small q  March      650871
Letter D    small q  Apr        258017
Letter D    p        January    478546
C           small q  Feb        766311
C           small t  March      84247
Letter A    small q  March      875322
Letter A    r        Feb        339073
Letter A    r        Ap         839441
C           r        Feb        346684
Letter B    p        January    333775
Letter D    small t  January    476352
(...)

Existing approach

replaceLbls <- function(dataSet, lstDict) {
    sapply(seq_along(dataSet), function(i) {
        # Take corresponding metadata data frame
        dtaDict <- lstDict[[i]]

        # Replace values in selected column
        # Where matches on V1 push corrsponding values from V2
        dataSet[,i][match(dataSet[,i], dtaDict[,1])] <- dtaDict[,2][match(dtaDict[,1], dataSet[,i])]  
    })
}

# Testing -----------------------------------------------------------------

replaceLbls(dataSet = dta, lstDict = lstMeta)

Of course the approach proposed above does not work as it will try to use NA in assignments; but it summarises what I want to achieve:

Error in x[...] <- m : NAs are not allowed in subscripted assignments In addition: Warning message: In [<-.factor(*tmp*, match(dataSet[, i], dtaDict[, 1]), value = c(NA, : invalid factor level, NA generated

Additional remarks

Source data set

The key characteristics of the data are:

  • The list is nameless so subsetting has to be done by item numbers not by names
  • Item number correspond to column numbers
  • There is no full match between metadata data frames available in the list of data frames and unit columns available in the data
  • The someValue column also should be iterated over as it may contain labels that should be replaced

Solution

  • I'm not interested in dplyr/data.table/sqldf-based solutions.
  • I'm not interested in nested for-loops
4 Answers
Related