Seleccionar valores de diferentes columnas en función de una variable que contenga nombres de columna

Tengo una tabla de datos como esta:

col1 col2 col3 new 1 4 55 col1 2 3 44 col2 3 34 35 col2 4 44 87 col3 

Quiero rellenar otra columna matched_value que contenga los valores de los nombres de columna respectivos dados en la new columna:

 col1 col2 col3 new matched_value 1 4 55 col1 1 2 3 44 col2 3 3 34 35 col2 34 4 44 87 col3 87 

Por ejemplo, en la primera fila, el valor de new es “col1”, por lo que matched_value toma el valor de col1 , que es 1.

¿Cómo puedo hacer esto de manera eficiente en R en una tabla de datos muy grande?

Una excusa para usar el oscuro .BY :

 DT[, newval := .SD[[.BY[[1]]]], by=new] col1 col2 col3 new newval 1: 1 4 55 col1 1 2: 2 3 44 col2 3 3: 3 34 35 col2 34 4: 4 44 87 col3 87 

Cómo funciona. Esto divide los datos en grupos según las cadenas en new . El valor de la cadena para cada grupo se almacena en newname = .BY[[1]] . Usamos esta cadena para seleccionar la columna correspondiente de .SD través de .SD[[newname]] . .SD significa S ubset of D ata.

Alternativas. get(.BY[[1]]) debería funcionar igual de bien en lugar de .SD[[.BY[[1]]]] . Según un punto de referencia dirigido por @David, las dos formas son igualmente rápidas.

Podemos hacer match la columna ‘nueva’ con los nombres de columna del conjunto de datos para obtener el índice de columna, cbind con el índice de fila ( 1:nrow(df1) ) y extraer los elementos correspondientes del conjunto de datos en función del índice de fila / columna. Se puede asignar a una nueva columna.

 df1$matched_value <- df1[-4][cbind(1:nrow(df1),match(df1$new, colnames(df1) ))] df1 # col1 col2 col3 new matched_value #1 1 4 55 col1 1 #2 2 3 44 col2 3 #3 3 34 35 col2 34 #4 4 44 87 col3 87 

NOTA: Si el OP tiene una data.table , una opción es convertir a data.frame o usar with=FALSE mientras subconjunta.

  setDF(df1) #to convert to 'data.frame'. 

Puntos de referencia

 set.seed(45) df2 <- data.frame(col1= sample(1:9, 20e6, replace=TRUE), col2= sample(1:20, 20e6, replace=TRUE), col3= sample(1:40, 20e6, replace=TRUE), col4=sample(1:30, 20e6, replace=TRUE), new= sample(paste0('col', 1:4), 20e6, replace=TRUE), stringsAsFactors=FALSE) system.time(df2$matched_value <- df2[-5][cbind(1:nrow(df2),match(df2$new, colnames(df2) ))]) # user system elapsed # 2.54 0.37 2.92