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
duplicated
para 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.51
Intenté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.15
fuente
!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 E
fuente
slice
También vale la pena mencionarlo .slice(x)
es un atajo parafilter(row_number() %in% x)
.data.table
adata.frame
para que esto funcione?data.table
hereda del, pordata.frame
lo que en muchos casos puede usar comandos dplyr en undata.table
. El ejemplo anterior, por ejemplo, también funciona sitest
es 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.tables
que devolverá la primera fila por clavejdtu <- function() unique(DT)
Creo que si realiza un pedido
test
fuera del punto de referencia, también puede eliminar la conversiónsetkey
ydata.table
del 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 0
y 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.03
El método único es el más rápido aquí.
fuente
unique(DT,by="id")
funciona directamentedata.table
versión> = 1.9.8, elby
argumento predeterminado paraunique
esby = seq_along(x)
(todas las columnas), en lugar del predeterminado anteriorby = key(x)
Una
ddply
opció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
dplyr
agregar 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 2
fuente
dplyr
que 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 use
primero (valor) `vshead(value)
(o simplementevalue[1]
)(1) SQLite tiene una
rowid
pseudocolumna 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
sqldf
sí 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 E
Tenga en cuenta que los tres se basan en una extensión de SQLite para SQL donde se garantiza que el uso de
min
omax
dará 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 E
Una 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 E
El 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