¿Cómo emular SQL “partición por” en R?
data.table dplyr (4)
Desde data.table v1.9.5+
, se ha implementado la función frank()
(para rango rápido ). frank()
es útil en escenarios interactivos, donde como frankv()
permite programar fácilmente con.
Implementa todas las operaciones disponibles en base::rank
. Además, las ventajas son:
frank()
opera en listas , marcos de datos y tablas de datos además de vectores atómicos .Podemos especificar, para cada columna, si el rango debe calcularse en orden creciente o decreciente.
También implementa un tipo de rango
dense
además de otros tipos enbase
.Puede usar
-
en una columna de caracteres también para clasificar por orden decreciente.
Aquí hay una ilustración de todos los puntos anteriores utilizando la misma tabla de datos de DT
de @BenBarnes ''(excelente).
datos:
require(data.table)
set.seed(10)
sample_n <- function(x, n) sample(x, n, replace=TRUE)
DT <- data.table(
ID = seq_len(4*3),
group = rep(1:4,each=3),
value = rnorm(4*3),
info = c(sample_n(letters[1:2], 8), sample_n(letters[3:4], 4)))
En columnas individuales:
Calcular el rango
dense
:DT[, rank := frank(value, ties.method="dense"), by=group]
También puedes usar los otros métodos min
, max
, random
, average
y first
.
En orden decreciente:
DT[, rank := frank(-value, ties.method="dense"), by=group]
Utilizando
frankv
, similar afrank
:# increasing order frankv(DT, "value", ties.method="dense") # decreasing order frankv(DT, "value", order=-1L, ties.method="dense")
En multiples columnas
Puede usar .SD
, que significa subconjunto de datos y contiene los datos correspondientes a ese grupo. Consulte la Introducción a la viñeta HTML de data.table para más información sobre .SD
.
Clasificar por
info, value
columnas mientras se agrupan porgroup
:DT[, rank := frank(.SD, info, value, ties.method="dense"), by=group]
Use
-
para especificar el orden decreciente:DT[, rank := frank(.SD, info, -value, ties.method="dense"), by=group]
También puede usar
-
directamente en columnas de caracteresDT[, rank := frank(.SD, -info, -value, ties.method="dense"), by=group]
Puede usar frankv
manera similar y proporcionar las columnas al argumento cols
y el orden según el cual las columnas deben clasificarse utilizando el argumento order
.
Pequeño punto de referencia para comparar con base::rank
:
set.seed(45L)
x = sample(1e4, 1e7, TRUE)
system.time(ans1 <- base::rank(x, ties.method="first"))
# user system elapsed
# 22.200 0.255 22.536
system.time(ans2 <- frank(x, ties.method="first"))
# user system elapsed
# 0.745 0.014 0.762
identical(ans1, ans2) # [1] TRUE
¿Cómo puedo realizar funciones analíticas como las funciones ROW_NUMBER (), RANK () o DENSE_RANK () de Oracle (consulte http://www.orafaq.com/node/55 ) en un marco de datos R? El paquete CRAN "plyr" está muy cerca, pero sigue siendo diferente.
Estoy de acuerdo en que la funcionalidad de cada función puede lograrse de manera ad hoc. Pero mi principal preocupación es el rendimiento. Sería bueno evitar el uso de la unión o el acceso a la indexación, por el bien de la memoria y la velocidad.
Me gusta data.table tanto como el siguiente tipo, pero no siempre es necesario. data.table siempre será más rápido, pero incluso para conjuntos de datos moderadamente grandes, si el número de grupos es bastante pequeño, plyr seguirá funcionando adecuadamente.
Lo que hizo BenBarnes usando data.table
s se puede hacer de manera tan compacta (pero como señalé antes, probablemente más lento en muchos casos) usando plyr :
library(plyr)
ddply(DT,.(group),transform,valRank = rank(-value))
ddply(DT,.(group),transform,valRank = rank(info,ties.method = "min"),
valRankDense = as.integer(factor(info)))
e incluso sin cargar un solo paquete adicional en absoluto:
do.call(rbind,by(DT,DT$group,transform,valRank = rank(-value)))
do.call(rbind,by(DT,DT$group,transform,valRank = rank(info,ties.method = "min"),
valRankDense = as.integer(factor(info))))
aunque pierdas algunas de las sutilezas sintácticas en ese último caso.
No creo que haya un equivalente directo a las funciones analíticas de Oracle. Plyr probablemente podrá lograr algunas de las funciones analíticas, pero no todas directamente. Estoy seguro de que R puede replicar cada función por separado, pero no creo que haya un solo paquete que lo haga todo.
Si hay una operación específica que debes lograr en R, entonces haz un poco de googlear, y si te quedas vacío, haz una pregunta específica aquí en .
El paquete data.table
, especialmente a partir de la versión 1.8.1, ofrece gran parte de la funcionalidad de la partición en términos de SQL. rank(x, ties.method = "min")
en R es similar a Oracle RANK()
, y hay una manera de usar los factores (descritos a continuación) para imitar la función DENSE_RANK()
. Una forma de imitar ROW_NUMBER
debería ser obvia al final.
Aquí hay un ejemplo: Cargue la última versión de data.table
de R-Forge:
install.packages("data.table",
repos= c("http://R-Forge.R-project.org", getOption("repos")))
library(data.table)
Crea algunos datos de ejemplo:
set.seed(10)
DT<-data.table(ID=seq_len(4*3),group=rep(1:4,each=3),value=rnorm(4*3),
info=c(sample(c("a","b"),4*2,replace=TRUE),
sample(c("c","d"),4,replace=TRUE)),key="ID")
> DT
ID group value info
1: 1 1 0.01874617 a
2: 2 1 -0.18425254 b
3: 3 1 -1.37133055 b
4: 4 2 -0.59916772 a
5: 5 2 0.29454513 b
6: 6 2 0.38979430 a
7: 7 3 -1.20807618 b
8: 8 3 -0.36367602 a
9: 9 3 -1.62667268 c
10: 10 4 -0.25647839 d
11: 11 4 1.10177950 c
12: 12 4 0.75578151 d
Clasifique cada ID
disminuyendo el value
dentro del group
(tenga en cuenta que -
delante del value
para indicar el orden decreciente):
> DT[,valRank:=rank(-value),by="group"]
ID group value info valRank
1: 1 1 0.01874617 a 1
2: 2 1 -0.18425254 b 2
3: 3 1 -1.37133055 b 3
4: 4 2 -0.59916772 a 3
5: 5 2 0.29454513 b 2
6: 6 2 0.38979430 a 1
7: 7 3 -1.20807618 b 2
8: 8 3 -0.36367602 a 1
9: 9 3 -1.62667268 c 3
10: 10 4 -0.25647839 d 3
11: 11 4 1.10177950 c 1
12: 12 4 0.75578151 d 2
Para DENSE_RANK()
con vínculos en el valor que se está clasificando, puede convertir el valor en un factor y luego devolver los valores enteros subyacentes. Por ejemplo, clasifique cada ID
según la info
dentro del group
(compare infoRank
con infoRankDense
):
DT[,infoRank:=rank(info,ties.method="min"),by="group"]
DT[,infoRankDense:=as.integer(factor(info)),by="group"]
R> DT
ID group value info valRank infoRank infoRankDense
1: 1 1 0.01874617 a 1 1 1
2: 2 1 -0.18425254 b 2 2 2
3: 3 1 -1.37133055 b 3 2 2
4: 4 2 -0.59916772 a 3 1 1
5: 5 2 0.29454513 b 2 3 2
6: 6 2 0.38979430 a 1 1 1
7: 7 3 -1.20807618 b 2 2 2
8: 8 3 -0.36367602 a 1 1 1
9: 9 3 -1.62667268 c 3 3 3
10: 10 4 -0.25647839 d 3 2 2
11: 11 4 1.10177950 c 1 1 1
12: 12 4 0.75578151 d 2 2 2
ps Hola Matthew Dowle.
LEAD y LAG
Para imitar LEAD y LAG, comience con la respuesta que se proporciona here . Yo crearía una variable de rango basada en el orden de las ID dentro de los grupos. Esto no sería necesario con los datos falsos como se indicó anteriormente, pero si las ID no están en orden secuencial dentro de los grupos, esto dificultaría un poco la vida. Así que aquí hay algunos nuevos datos falsos con ID no secuenciales:
set.seed(10)
DT<-data.table(ID=sample(seq_len(4*3)),group=rep(1:4,each=3),value=rnorm(4*3),
info=c(sample(c("a","b"),4*2,replace=TRUE),
sample(c("c","d"),4,replace=TRUE)),key="ID")
DT[,idRank:=rank(ID),by="group"]
setkey(DT,group, idRank)
> DT
ID group value info idRank
1: 4 1 -0.36367602 b 1
2: 5 1 -1.62667268 b 2
3: 7 1 -1.20807618 b 3
4: 1 2 1.10177950 a 1
5: 2 2 0.75578151 a 2
6: 12 2 -0.25647839 b 3
7: 3 3 0.74139013 c 1
8: 6 3 0.98744470 b 2
9: 9 3 -0.23823356 a 3
10: 8 4 -0.19515038 c 1
11: 10 4 0.08934727 c 2
12: 11 4 -0.95494386 c 3
Luego, para obtener los valores del registro 1 anterior, use las variables group
e idRank
y reste 1
del idRank
y use el argumento multi = ''last''
. Para obtener el valor del registro de las dos entradas anteriores, reste 2
.
DT[,prev:=DT[J(group,idRank-1), value, mult=''last'']]
DT[,prev2:=DT[J(group,idRank-2), value, mult=''last'']]
ID group value info idRank prev prev2
1: 4 1 -0.36367602 b 1 NA NA
2: 5 1 -1.62667268 b 2 -0.36367602 NA
3: 7 1 -1.20807618 b 3 -1.62667268 -0.3636760
4: 1 2 1.10177950 a 1 NA NA
5: 2 2 0.75578151 a 2 1.10177950 NA
6: 12 2 -0.25647839 b 3 0.75578151 1.1017795
7: 3 3 0.74139013 c 1 NA NA
8: 6 3 0.98744470 b 2 0.74139013 NA
9: 9 3 -0.23823356 a 3 0.98744470 0.7413901
10: 8 4 -0.19515038 c 1 NA NA
11: 10 4 0.08934727 c 2 -0.19515038 NA
12: 11 4 -0.95494386 c 3 0.08934727 -0.1951504
Para LEAD, agregue el desplazamiento apropiado a la variable idRank
y cambie a multi = ''first''
:
DT[,nex:=DT[J(group,idRank+1), value, mult=''first'']]
DT[,nex2:=DT[J(group,idRank+2), value, mult=''first'']]
ID group value info idRank prev prev2 nex nex2
1: 4 1 -0.36367602 b 1 NA NA -1.62667268 -1.2080762
2: 5 1 -1.62667268 b 2 -0.36367602 NA -1.20807618 NA
3: 7 1 -1.20807618 b 3 -1.62667268 -0.3636760 NA NA
4: 1 2 1.10177950 a 1 NA NA 0.75578151 -0.2564784
5: 2 2 0.75578151 a 2 1.10177950 NA -0.25647839 NA
6: 12 2 -0.25647839 b 3 0.75578151 1.1017795 NA NA
7: 3 3 0.74139013 c 1 NA NA 0.98744470 -0.2382336
8: 6 3 0.98744470 b 2 0.74139013 NA -0.23823356 NA
9: 9 3 -0.23823356 a 3 0.98744470 0.7413901 NA NA
10: 8 4 -0.19515038 c 1 NA NA 0.08934727 -0.9549439
11: 10 4 0.08934727 c 2 -0.19515038 NA -0.95494386 NA
12: 11 4 -0.95494386 c 3 0.08934727 -0.1951504 NA NA