De un marco de datos como este
test <- data.frame('id'= rep(1:5,2), 'string'= LETTERS[1:10])
test <- test[order(test$id), ]
rownames(test) <- 1:10
> test
id string
1 1 A
2 1 F
3 2 B
4 2 G
5 3 C
6 3 H
7 4 D
8 4 I
9 5 E
10 5 J
Quiero crear uno nuevo con la primera fila de cada par de id / string. Si sqldf aceptó el código R dentro de él, la consulta podría verse así:
res <- sqldf("select id, min(rownames(test)), string
from test
group by id, string")
> res
id string
1 1 A
3 2 B
5 3 C
7 4 D
9 5 E
¿Existe una solución que no sea la de crear una nueva columna como
test$row <- rownames(test)
y ejecutando la misma consulta sqldf con min (fila)?

Respuestas:
Puede utilizar
duplicatedpara hacer esto muy rápidamente.Puntos de referencia, para los fanáticos de la velocidad:
ju <- function() test[!duplicated(test$id),] gs1 <- function() do.call(rbind, lapply(split(test, test$id), head, 1)) gs2 <- function() do.call(rbind, lapply(split(test, test$id), `[`, 1, )) jply <- function() ddply(test,.(id),function(x) head(x,1)) jdt <- function() { testd <- as.data.table(test) setkey(testd,id) # Initial solution (slow) # testd[,lapply(.SD,function(x) head(x,1)),by = key(testd)] # Faster options : testd[!duplicated(id)] # (1) # testd[, .SD[1L], by=key(testd)] # (2) # testd[J(unique(id)),mult="first"] # (3) # testd[ testd[,.I[1L],by=id] ] # (4) needs v1.8.3. Allows 2nd, 3rd etc } library(plyr) library(data.table) library(rbenchmark) # sample data set.seed(21) test <- data.frame(id=sample(1e3, 1e5, TRUE), string=sample(LETTERS, 1e5, TRUE)) test <- test[order(test$id), ] benchmark(ju(), gs1(), gs2(), jply(), jdt(), replications=5, order="relative")[,1:6] # test replications elapsed relative user.self sys.self # 1 ju() 5 0.03 1.000 0.03 0.00 # 5 jdt() 5 0.03 1.000 0.03 0.00 # 3 gs2() 5 3.49 116.333 2.87 0.58 # 2 gs1() 5 3.58 119.333 3.00 0.58 # 4 jply() 5 3.69 123.000 3.11 0.51Intentémoslo de nuevo, pero solo con los contendientes de la primera manga y con más datos y más réplicas.
set.seed(21) test <- data.frame(id=sample(1e4, 1e6, TRUE), string=sample(LETTERS, 1e6, TRUE)) test <- test[order(test$id), ] benchmark(ju(), jdt(), order="relative")[,1:6] # test replications elapsed relative user.self sys.self # 1 ju() 100 5.48 1.000 4.44 1.00 # 2 jdt() 100 6.92 1.263 5.70 1.15fuente
!duplicated(x)encuentra el primero de cada grupo incluso si no está ordenado, iiuc.Yo prefiero el enfoque dplyr.
group_by(id)seguido de cualquierafilter(row_number()==1)oslice(1)oslice_head(1)# (dplyr => 1.0)top_n(n = -1)top_n()utiliza internamente la función de rango. Negativo selecciona desde la parte inferior del rango.En algunos casos, puede ser necesario organizar los identificadores después de group_by.
library(dplyr) # using filter(), top_n() or slice() m1 <- test %>% group_by(id) %>% filter(row_number()==1) m2 <- test %>% group_by(id) %>% slice(1) m3 <- test %>% group_by(id) %>% top_n(n = -1)Los tres métodos devuelven el mismo resultado
# A tibble: 5 x 2 # Groups: id [5] id string <int> <fct> 1 1 A 2 2 B 3 3 C 4 4 D 5 5 Efuente
sliceTambién vale la pena mencionarlo .slice(x)es un atajo parafilter(row_number() %in% x).data.tableadata.framepara que esto funcione?data.tablehereda del, pordata.framelo que en muchos casos puede usar comandos dplyr en undata.table. El ejemplo anterior, por ejemplo, también funciona sitestes adata.table. Consulte, por ejemplo, stackoverflow.com/questions/13618488/… para una explicación más profundaQué pasa
DT <- data.table(test) setkey(DT, id) DT[J(unique(id)), mult = "first"]Editar
También hay un método único para el
data.tablesque devolverá la primera fila por clavejdtu <- function() unique(DT)Creo que si realiza un pedido
testfuera del punto de referencia, también puede eliminar la conversiónsetkeyydata.tabledel punto de referencia (ya que la clave de configuración básicamente se ordena por id, lo mismo queorder).set.seed(21) test <- data.frame(id=sample(1e3, 1e5, TRUE), string=sample(LETTERS, 1e5, TRUE)) test <- test[order(test$id), ] DT <- data.table(DT, key = 'id') ju <- function() test[!duplicated(test$id),] jdt <- function() DT[J(unique(id)),mult = 'first'] library(rbenchmark) benchmark(ju(), jdt(), replications = 5) ## test replications elapsed relative user.self sys.self ## 2 jdt() 5 0.01 1 0.02 0 ## 1 ju() 5 0.05 5 0.05 0y con mas datos
** Edite con un método único **
set.seed(21) test <- data.frame(id=sample(1e4, 1e6, TRUE), string=sample(LETTERS, 1e6, TRUE)) test <- test[order(test$id), ] DT <- data.table(test, key = 'id') test replications elapsed relative user.self sys.self 2 jdt() 5 0.09 2.25 0.09 0.00 3 jdtu() 5 0.04 1.00 0.05 0.00 1 ju() 5 0.22 5.50 0.19 0.03El método único es el más rápido aquí.
fuente
unique(DT,by="id")funciona directamentedata.tableversión> = 1.9.8, elbyargumento predeterminado parauniqueesby = seq_along(x)(todas las columnas), en lugar del predeterminado anteriorby = key(x)Una
ddplyopción sencilla :ddply(test,.(id),function(x) head(x,1))Si la velocidad es un problema, se podría tomar un enfoque similar con
data.table:testd <- data.table(test) setkey(testd,id) testd[,.SD[1],by = key(testd)]o esto podría ser considerablemente más rápido:
testd[testd[, .I[1], by = key(testd]$V1]fuente
ahora, para
dplyragregar un contador distinto.df %>% group_by(aa, bb) %>% summarise(first=head(value,1), count=n_distinct(value))Creas grupos, los resumen dentro de grupos.
Si los datos son numéricos, puede utilizar:
first(value)[también haylast(value)] en lugar dehead(value, 1)ver: http://cran.rstudio.com/web/packages/dplyr/vignettes/introduction.html
Lleno:
> df Source: local data frame [16 x 3] aa bb value 1 1 1 GUT 2 1 1 PER 3 1 2 SUT 4 1 2 GUT 5 1 3 SUT 6 1 3 GUT 7 1 3 PER 8 2 1 221 9 2 1 224 10 2 1 239 11 2 2 217 12 2 2 221 13 2 2 224 14 3 1 GUT 15 3 1 HUL 16 3 1 GUT > library(dplyr) > df %>% > group_by(aa, bb) %>% > summarise(first=head(value,1), count=n_distinct(value)) Source: local data frame [6 x 4] Groups: aa aa bb first count 1 1 1 GUT 2 2 1 2 SUT 2 3 1 3 SUT 3 4 2 1 221 3 5 2 2 217 3 6 3 1 GUT 2fuente
dplyrque no requieren escribir una declaración para que se incluya cada columna (consulte la respuesta de atomman a continuación, por ejemplo). Also I'm not sure what *"if data is numeric"* has anything to do with whether or not one would useprimero (valor) `vshead(value)(o simplementevalue[1])(1) SQLite tiene una
rowidpseudocolumna incorporada, por lo que esto funciona:sqldf("select min(rowid) rowid, id, string from test group by id")dando:
rowid id string 1 1 1 A 2 3 2 B 3 5 3 C 4 7 4 D 5 9 5 E(2) También en
sqldfsí mismo tiene unrow.names=argumento:sqldf("select min(cast(row_names as real)) row_names, id, string from test group by id", row.names = TRUE)dando:
id string 1 1 A 3 2 B 5 3 C 7 4 D 9 5 E(3) Una tercera alternativa que combine los elementos de los dos anteriores podría ser incluso mejor:
sqldf("select min(rowid) row_names, id, string from test group by id", row.names = TRUE)dando:
id string 1 1 A 3 2 B 5 3 C 7 4 D 9 5 ETenga en cuenta que los tres se basan en una extensión de SQLite para SQL donde se garantiza que el uso de
minomaxdará como resultado que las otras columnas se elijan de la misma fila. (En otras bases de datos basadas en SQL, esto puede no estar garantizado).fuente
Una opción de base R es el
split()-lapply()-do.call()modismo:> do.call(rbind, lapply(split(test, test$id), head, 1)) id string 1 1 A 2 2 B 3 3 C 4 4 D 5 5 EUna opción más directa es
lapply()la[función:> do.call(rbind, lapply(split(test, test$id), `[`, 1, )) id string 1 1 A 2 2 B 3 3 C 4 4 D 5 5 EEl espacio de coma
1, )al final de lalapply()llamada es esencial, ya que equivale a llamar[1, ]para seleccionar la primera fila y todas las columnas.fuente