2011-04-27 7 views
13

Tengo un data.frame tan simple como esta:Subconjunto una trama de datos sobre la base de entrada de la columna (o fila)

id group idu value 
1 1  1_1 34 
2 1  2_1 23 
3 1  3_1 67 
4 2  4_2 6 
5 2  5_2 24 
6 2  6_2 45 
1 3  1_3 34 
2 3  2_3 67 
3 3  3_3 76 

desde donde quiero recuperar un subconjunto con las primeras entradas de cada grupo; algo así como:

id group idu value 
1 1  1_1 34 
4 2  4_2 6 
1 3  1_3 34 

id no es exclusivo, por lo que el enfoque no debe confiar en ello.

¿Puedo lograr esto evitando los bucles?

dput() de los datos:

structure(list(id = c(1L, 2L, 3L, 4L, 5L, 6L, 1L, 2L, 3L), group = c(1L, 
1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L), idu = structure(c(1L, 3L, 5L, 
7L, 8L, 9L, 2L, 4L, 6L), .Label = c("1_1", "1_3", "2_1", "2_3", 
"3_1", "3_3", "4_2", "5_2", "6_2"), class = "factor"), value = c(34L, 
23L, 67L, 6L, 24L, 45L, 34L, 67L, 76L)), .Names = c("id", "group", 
"idu", "value"), class = "data.frame", row.names = c(NA, -9L)) 

Respuesta

10

Usando millones fila df de Gavin:

DF3 <- data.frame(id = sample(1000, 1000000, replace = TRUE), 
        group = factor(rep(1:1000, each = 1000)), 
        value = runif(1000000)) 
DF3 <- within(DF3, idu <- factor(paste(id, group, sep = "_"))) 

Creo que la forma más rápida es reordenar la trama de datos y luego utilizar duplicated:

system.time({ 
    DF4 <- DF3[order(DF3$group), ] 
    out2 <- DF4[!duplicated(DF4$group), ] 
}) 
# user system elapsed 
# 0.335 0.107 0.441 

Esto se compara con 7 segundos para el método Fascin lapply + split de Gavin en mi computadora.

En general, cuando se trabaja con marcos de datos, el enfoque más rápido suele ser generar todos los índices y luego hacer un solo subconjunto.

+0

+1 es un excelente ejemplo. –

+0

Este es un enfoque agradable, pero para agregar una nota adicional, los datos reales también pueden repetir el código de grupo, lo que requiere un paso adicional: agregar un ID de grupo único real a todo el conjunto de datos, tal vez según la columna de marca de tiempo –

+0

.duplicado devuelve el primer valor de un grupo duplicado? – zach

1

creo que esto va a hacer el truco:

aggregate(data["idu"], data["group"], function (x) x[1]) 

Por su pregunta actualizado, me gustaría recomendar el uso ddply del paquete plyr:

ddply(data, .(group), function (x) x[1,]) 
+0

Funciona también. gracias Daniel –

+0

Ver respuesta actualizada para su pregunta actualizada. –

5

Actualización a la luz del comentario de OP

Si hace esto en millones + filas, todas las opciones así suministradas serán lentas. Aquí están algunos tiempos de comparación en un conjunto de datos ficticios de 100.000 filas:

set.seed(12) 
DF3 <- data.frame(id = sample(1000, 100000, replace = TRUE), 
        group = factor(rep(1:100, each = 1000)), 
        value = runif(100000)) 
DF3 <- within(DF3, idu <- factor(paste(id, group, sep = "_"))) 

> system.time(out1 <- do.call(rbind, lapply(split(DF3, DF3["group"]), `[`, 1,))) 
    user system elapsed 
19.594 0.053 19.984 
> system.time(out3 <- aggregate(DF3[,-2], DF3["group"], function (x) x[1])) 
    user system elapsed 
12.419 0.141 12.788 

renuncié a hacer con un millón de filas. Mucho más rápido, lo creas o no, es:

out2 <- matrix(unlist(lapply(split(DF3[, -4], DF3["group"]), `[`, 1,)), 
       byrow = TRUE, nrow = (lev <- length(levels(DF3$group)))) 
colnames(out2) <- names(DF3)[-4] 
rownames(out2) <- seq_len(lev) 
out2 <- as.data.frame(out2) 
out2$group <- factor(out2$group) 
out2$idu <- factor(paste(out2$id, out2$group, sep = "_"), 
        levels = levels(DF3$idu)) 

Las salidas son (efectivamente) el mismo:

> all.equal(out1, out2) 
[1] TRUE 
> all.equal(out1, out3[, c(2,1,3,4)]) 
[1] "Attributes: < Component 2: Modes: character, numeric >"    
[2] "Attributes: < Component 2: target is character, current is numeric >" 

(la diferencia entre out1 (o out2) y out3 (la versión aggregate()) es sólo en los rownames de los componentes)

con un tiempo de:.

user system elapsed 
    0.163 0.001 0.168 

en el problema fila 100.000, y en este problema millones consecutivas:

set.seed(12) 
DF3 <- data.frame(id = sample(1000, 1000000, replace = TRUE), 
        group = factor(rep(1:1000, each = 1000)), 
        value = runif(1000000)) 
DF3 <- within(DF3, idu <- factor(paste(id, group, sep = "_"))) 

con un tiempo de

user system elapsed 
11.916 0.000 11.925 

Utilización de la versión de la matriz (que produce out2) está haciendo más rápido el millón de filas que las otras versiones están haciendo el problema de 100.000 filas. Esto solo muestra que trabajar con matrices es muy rápido, y el cuello de botella en la versión my do.call() es rbind(), el resultado es el resultado.

El momento problema millones fila se hizo con:

system.time({out4 <- matrix(unlist(lapply(split(DF3[, -4], DF3["group"]), 
              `[`, 1,)), 
          byrow = TRUE, 
          nrow = (lev <- length(levels(DF3$group)))) 
      colnames(out4) <- names(DF3)[-4] 
      rownames(out4) <- seq_len(lev) 
      out4 <- as.data.frame(out4) 
      out4$group <- factor(out4$group) 
      out4$idu <- factor(paste(out4$id, out4$group, sep = "_"), 
           levels = levels(DF3$idu))}) 

original

Si los datos están en DF, digamos, a continuación:

do.call(rbind, lapply(with(DF, split(DF, group)), head, 1)) 

hará lo que quiere:

> do.call(rbind, lapply(with(DF, split(DF, group)), head, 1)) 
    idu group 
1 1  1 
2 4  2 
3 7  3 

Si los nuevos datos están en DF2 entonces tenemos:

> do.call(rbind, lapply(with(DF2, split(DF2, group)), head, 1)) 
    id group idu value 
1 1  1 1_1 34 
2 4  2 4_2  6 
3 1  3 1_3 34 

Pero para la velocidad, es probable que quieren subconjunto en lugar de utilizar head() y podemos ganar un poco al no utilizar with(), por ejemplo:

do.call(rbind, lapply(split(DF2, DF2$group), `[`, 1,)) 

> system.time(replicate(1000, do.call(rbind, lapply(split(DF2, DF2$group), `[`, 1,)))) 
    user system elapsed 
    3.847 0.040 4.044 
> system.time(replicate(1000, do.call(rbind, lapply(split(DF2, DF2$group), head, 1)))) 
    user system elapsed 
    4.058 0.038 4.111 
> system.time(replicate(1000, aggregate(DF2[,-2], DF2["group"], function (x) x[1]))) 
    user system elapsed 
    3.902 0.042 4.106 
+0

parece funcionar Gavin. Edité el contenido de esta pregunta, pero puede no verse afectado. Tengo que probar su rendimiento con un archivo de datos de 2 millones de líneas. –

+0

@Paulo He actualizado la respuesta, con algunos tiempos de comparación para ejecuciones repetidas en esta recopilación de datos. –

+0

@Paulo Cardosa Hice algunos tiempos en un problema grande y todas las opciones eran lentas, así que proporcioné una versión que funciona con una matriz y es mucho más rápida. Tiempos en un problema de millón de filas incluido. –

1

una solución utilizando plyr, suponiendo que sus datos están en un objeto denominado zzz:

ddply(zzz, "group", function(x) x[1 ,]) 

Otra opción que toma la diferencia entre las filas y debe ser más rápida, pero depende del objeto que se solicita de antemano. Esto también supone que no tiene un valor de grupo de 0:

zzz <- zzz[order(zzz$group) ,] 

zzz[ diff(c(0,zzz$group)) != 0, ] 
Cuestiones relacionadas