2012-03-23 9 views
11

Quiero "recorrer" las filas de un data.table y calcular un promedio para cada fila. El promedio se debe calcular sobre la base del siguiente mecanismo:"Loop through" data.table para calcular promedios condicionales

  1. Busque el identificador ID en la fila i (ID (i))
  2. buscar el valor de T2 en la fila i (T2 (i))
  3. Calcular la media de los valores de Data1 en todas las filas j, que cumplen estos dos criterios: ID(j) = ID(i) y T1(j) = T2(i)
  4. Introduzca el promedio calculado en el Datos2 columna de la fila i

    DF = data.frame(ID=rep(c("a","b"),each=6), 
          T1=rep(1:2,each=3), T2=c(1,2,3), Data1=c(1:12)) 
    DT = data.table(DF) 
    DT[ , Data2:=NA_real_] 
        ID T1 T2 Data1 Data2 
    [1,] a 1 1  1 NA 
    [2,] a 1 2  2 NA 
    [3,] a 1 3  3 NA 
    [4,] a 2 1  4 NA 
    [5,] a 2 2  5 NA 
    [6,] a 2 3  6 NA 
    [7,] b 1 1  7 NA 
    [8,] b 1 2  8 NA 
    [9,] b 1 3  9 NA 
    [10,] b 2 1 10 NA 
    [11,] b 2 2 11 NA 
    [12,] b 2 3 12 NA 
    

Para este sencillo ejemplo, el resultado debe ser similar a esto:

 ID T1 T2 Data1 Data2 
[1,] a 1 1  1 2 
[2,] a 1 2  2 5 
[3,] a 1 3  3 NA 
[4,] a 2 1  4 2 
[5,] a 2 2  5 5 
[6,] a 2 3  6 NA 
[7,] b 1 1  7 8 
[8,] b 1 2  8 11 
[9,] b 1 3  9 NA 
[10,] b 2 1 10 8 
[11,] b 2 2 11 11 
[12,] b 2 3 12 NA 

Creo que una manera de hacer esto sería colocar a través de las filas, pero creo que es ineficiente. He echado un vistazo a la función apply(), pero estoy seguro de que resolvería mi problema. También podría usar data.frame en lugar de data.table si esto lo hace mucho más eficiente o más fácil. El conjunto de datos real contiene aproximadamente 1 millón de filas.

+2

La especificación escrita parece difícil de poner en práctica, pero su ejemplo sugiere que dentro de cada grupo de ID desea los medios de algún grupo de valores para el cual T2 está en el rango de valores de T1. Pero incluso esa interpretación se desmorona cuando intentamos averiguar por qué Data2 en la segunda fila debería ser 5. ????? –

+0

@DWin eso se debe a que el promedio se realiza en la columna 'Data1'. 'Data2 [2]' es igual a 5 porque 5 es el promedio de '(4, 5, 6)'. – ulidtko

Respuesta

10

La regla de oro es agregar primero, y luego unirse a eso.

agg = DT[,mean(Data1),by=list(ID,T1)] 
setkey(agg,ID,T1) 
DT[,Data2:={JT=J(ID,T2);agg[JT,V1][[3]]}] 
     ID T1 T2 Data1 Data2 
[1,] a 1 1  1  2 
[2,] a 1 2  2  5 
[3,] a 1 3  3 NA 
[4,] a 2 1  4  2 
[5,] a 2 2  5  5 
[6,] a 2 3  6 NA 
[7,] b 1 1  7  8 
[8,] b 1 2  8 11 
[9,] b 1 3  9 NA 
[10,] b 2 1 10  8 
[11,] b 2 2 11 11 
[12,] b 2 3 12 NA 

Como puede ver, es un poco feo en este caso (pero será rápido). Está previsto añadir drop lo que evitará el bit [[3]], y tal vez podría proporcionar una manera de contar [.data.table para evaluar i en llamar a su alcance (es decir, sin auto unirse a) lo que evitaría el bit JT= que se necesita aquí porque ID es tanto agg y DT.

keyby ha sido añadido a v1.8.0 en R-Forge, así que eso evita la necesidad de setkey, también.

+0

Gracias Matthew. Esto es increíblemente rápido. ¿Existe la posibilidad de darle a la columna 'V1' de' agg' un nombre personalizado correcto al crearla para evitar confusiones sobre los nombres de las columnas? – Cake

+1

Pruebe 'DT [, list (myname = mean (Data1)), by = list (ID, T1)]'. También vea [data.table wiki] (http://rwiki.sciviews.org/doku.php?id=packages:cran:data.table) punto 3, para una mayor aceleración en este caso. –

+0

Reemplacé su tercera línea con 'DT [, Data2: = {agg [J (ID, T2)] [[3]]}]', y obtengo los mismos resultados. es decir, ** evité ** el bit 'JT =' (también en ', V1'). ¿Son esas malas prácticas de mi parte? –

2

Una alternativa algo más rápida que iterar sobre filas sería una solución que emplea vectorización.

R> d <- data.frame(ID=rep(c("a","b"),each=6), T1=rep(1:2,each=3), T2=c(1,2,3), Data1=c(1:12)) 
R> d 
    ID T1 T2 Data1 
1 a 1 1  1 
2 a 1 2  2 
3 a 1 3  3 
4 a 2 1  4 
5 a 2 2  5 
6 a 2 3  6 
7 b 1 1  7 
8 b 1 2  8 
9 b 1 3  9 
10 b 2 1 10 
11 b 2 2 11 
12 b 2 3 12 

R> rowfunction <- function(i) with(d, mean(Data1[which(T1==T2[i] & ID==ID[i])])) 
R> d$Data2 <- sapply(1:nrow(d), rowfunction) 
R> d 
    ID T1 T2 Data1 Data2 
1 a 1 1  1  2 
2 a 1 2  2  5 
3 a 1 3  3 NaN 
4 a 2 1  4  2 
5 a 2 2  5  5 
6 a 2 3  6 NaN 
7 b 1 1  7  8 
8 b 1 2  8 11 
9 b 1 3  9 NaN 
10 b 2 1 10  8 
11 b 2 2 11 11 
12 b 2 3 12 NaN 

Además, preferiría para preprocesar los datos antes de conseguir que en R. es decir, si está recuperando los datos de un servidor SQL, podría ser una mejor opción dejar que el servidor calcule los promedios, ya que es muy probable que haga un mejor trabajo en esto.

R en realidad no es muy bueno en el crujido de números, por varias razones. Pero es excelente cuando se hacen estadísticas sobre los datos ya preprocesados.

1

Usando tapply y parte de otro post reciente:

DF = data.frame(ID=rep(c("a","b"),each=6), T1=rep(1:2,each=3), T2=c(1,2,3), Data1=c(1:12)) 

EDIT: En realidad, la mayor parte de la función original es redundante y fue pensado para otra cosa. Aquí, simplificado:

ansMat <- tapply(DF$Data1, DF[, c("ID", "T1")], mean) 

i <- cbind(match(DF$ID, rownames(ansMat)), match(DF$T2, colnames(ansMat))) 

DF<-cbind(DF,Data2 = ansMat[i]) 


# ansMat<-tapply(seq_len(nrow(DF)), DF[, c("ID", "T1")], function(x) { 
# curSub <- DF[x, ] 
# myIndex <- which(DF$T2 == curSub$T1 & DF$ID == curSub$ID) 
# meanData1 <- mean(curSub$Data1) 
# return(meanData1 = meanData1) 
# }) 

El truco estaba en tapply sobre ID y T1 en lugar de ID y T2. ¿Algo más rápido?