two tables sort ordenar data columns column according sorting r order

sorting - tables - sort by 2 columns r



Cómo formar rápidamente grupos(cuartiles, deciles, etc.) ordenando columna(s) en un marco de datos (9)

Veo muchas preguntas y respuestas order sort y sort . ¿Hay algo que clasifique vectores o marcos de datos en agrupaciones (como cuartiles o deciles)? Tengo una solución "manual", pero es probable que haya una mejor solución que haya sido probada en grupo.

Aquí está mi intento:

temp <- data.frame(name=letters[1:12], value=rnorm(12), quartile=rep(NA, 12)) temp # name value quartile # 1 a 2.55118169 NA # 2 b 0.79755259 NA # 3 c 0.16918905 NA # 4 d 1.73359245 NA # 5 e 0.41027113 NA # 6 f 0.73012966 NA # 7 g -1.35901658 NA # 8 h -0.80591167 NA # 9 i 0.48966739 NA # 10 j 0.88856758 NA # 11 k 0.05146856 NA # 12 l -0.12310229 NA temp.sorted <- temp[order(temp$value), ] temp.sorted$quartile <- rep(1:4, each=12/4) temp <- temp.sorted[order(as.numeric(rownames(temp.sorted))), ] temp # name value quartile # 1 a 2.55118169 4 # 2 b 0.79755259 3 # 3 c 0.16918905 2 # 4 d 1.73359245 4 # 5 e 0.41027113 2 # 6 f 0.73012966 3 # 7 g -1.35901658 1 # 8 h -0.80591167 1 # 9 i 0.48966739 3 # 10 j 0.88856758 4 # 11 k 0.05146856 2 # 12 l -0.12310229 1

¿Hay un enfoque mejor (más limpio / más rápido / una línea)? ¡Gracias!


El método que uso es uno de estos o Hmisc::cut2(value, g=4) :

temp$quartile <- with(temp, cut(value, breaks=quantile(value, probs=seq(0,1, by=0.25), na.rm=TRUE), include.lowest=TRUE))

Un alternativo puede ser:

temp$quartile <- with(temp, factor( findInterval( val, c(-Inf, quantile(val, probs=c(0.25, .5, .75)), Inf) , na.rm=TRUE), labels=c("Q1","Q2","Q3","Q4") ))

El primero tiene el efecto secundario de etiquetar los cuartiles con los valores, que considero "algo bueno", pero si no fuera "bueno para ti", o los problemas válidos planteados en los comentarios eran una preocupación que podrías abordar. con la versión 2. Puede usar labels= in cut , o puede agregar esta línea a su código:

temp$quartile <- factor(temp$quartile, levels=c("1","2","3","4") )

O incluso más rápido pero un poco más oscuro en la forma en que funciona, aunque ya no es un factor, sino más bien un vector numérico:

temp$quartile <- as.numeric(temp$quartile)


Hay una función ntile útil en el paquete dplyr . Es flexible en el sentido de que puede definir fácilmente la cantidad de * mosaicos o "contenedores" que desea crear.

Cargue el paquete (instálelo primero si no lo hizo) y agregue la columna de cuartil:

library(dplyr) temp$quartile <- ntile(temp$value, 4)

O bien, si desea usar la sintaxis de dplyr:

temp <- temp %>% mutate(quartile = ntile(value, 4))

El resultado en ambos casos es:

temp # name value quartile #1 a -0.56047565 1 #2 b -0.23017749 2 #3 c 1.55870831 4 #4 d 0.07050839 2 #5 e 0.12928774 3 #6 f 1.71506499 4 #7 g 0.46091621 3 #8 h -1.26506123 1 #9 i -0.68685285 1 #10 j -0.44566197 2 #11 k 1.22408180 4 #12 l 0.35981383 3

datos:

Tenga en cuenta que no necesita crear la columna "cuartil" de antemano y usar set.seed para hacer la aleatorización reproducible:

set.seed(123) temp <- data.frame(name=letters[1:12], value=rnorm(12))


La adaptación de dplyr::ntile para aprovechar las optimizaciones de data.table brinda una solución más rápida.

library(data.table) setDT(temp) temp[order(value) , quartile := floor( 1 + 4 * (.I-1) / .N)]

Probablemente no califica como más limpio, pero es más rápido y de una sola línea.

Tiempo en un conjunto de datos más grande

Comparando esta solución con ntile y cut para data.table según lo propuesto por @docendo_discimus y @MichaelChirico.

library(microbenchmark) library(dplyr) set.seed(123) n <- 1e6 temp <- data.frame(name=sample(letters, size=n, replace=TRUE), value=rnorm(n)) setDT(temp) microbenchmark( "ntile" = temp[, quartile_ntile := ntile(value, 4)], "cut" = temp[, quartile_cut := cut(value, breaks = quantile(value, probs = seq(0, 1, by=1/4)), labels = 1:4, right=FALSE)], "dt_ntile" = temp[order(value), quartile_ntile_dt := floor( 1 + 4 * (.I-1)/.N)] )

Da:

Unit: milliseconds expr min lq mean median uq max neval ntile 608.1126 647.4994 670.3160 686.5103 691.4846 712.4267 100 cut 369.5391 373.3457 375.0913 374.3107 376.5512 385.8142 100 dt_ntile 117.5736 119.5802 124.5397 120.5043 124.5902 145.7894 100


Me gustaría proponer una versión, que parece ser más robusta, ya que tuve muchos problemas al usar quantile() en el cut() opción de cut() en mi conjunto de datos. Estoy usando la función ntile de plyr , pero también funciona con ecdf como entrada.

temp[, `:=`(quartile = .bincode(x = ntile(value, 100), breaks = seq(0,100,25), right = TRUE, include.lowest = TRUE) decile = .bincode(x = ntile(value, 100), breaks = seq(0,100,10), right = TRUE, include.lowest = TRUE) )] temp[, `:=`(quartile = .bincode(x = ecdf(value)(value), breaks = seq(0,1,0.25), right = TRUE, include.lowest = TRUE) decile = .bincode(x = ecdf(value)(value), breaks = seq(0,1,0.1), right = TRUE, include.lowest = TRUE) )]

¿Es eso correcto?


Perdón por llegar un poco tarde a la fiesta. Quería agregar mi trazador de líneas usando cut2 ya que no sabía max / min para mis datos y quería que los grupos fueran idénticamente grandes. Leí acerca de cut2 en un problema que fue marcado como duplicado (enlace a continuación).

library(Hmisc) #For cut2 set.seed(123) #To keep answers below identical to my random run temp <- data.frame(name=letters[1:12], value=rnorm(12), quartile=rep(NA, 12)) temp$quartile <- as.numeric(cut2(temp$value, g=4)) #as.numeric to number the factors temp$quartileBounds <- cut2(temp$value, g=4) temp

Resultado:

> temp name value quartile quartileBounds 1 a -0.56047565 1 [-1.265,-0.446) 2 b -0.23017749 2 [-0.446, 0.129) 3 c 1.55870831 4 [ 1.224, 1.715] 4 d 0.07050839 2 [-0.446, 0.129) 5 e 0.12928774 3 [ 0.129, 1.224) 6 f 1.71506499 4 [ 1.224, 1.715] 7 g 0.46091621 3 [ 0.129, 1.224) 8 h -1.26506123 1 [-1.265,-0.446) 9 i -0.68685285 1 [-1.265,-0.446) 10 j -0.44566197 2 [-0.446, 0.129) 11 k 1.22408180 4 [ 1.224, 1.715] 12 l 0.35981383 3 [ 0.129, 1.224)

Problema similar donde leo sobre cut2 en detalle


Posiblemente hay una manera más rápida, pero yo haría:

a <- rnorm(100) # Our data q <- quantile(a) # You can supply your own breaks, see ?quantile # Define a simple function that checks in which quantile a number falls getQuant <- function(x) { for (i in 1:(length(q)-1)) { if (x>=q[i] && x<q[i+1]) break; } i } # Apply the function to the data res <- unlist(lapply(as.matrix(a), getQuant))


Puede usar la función quantile() , pero necesita manejar el redondeo / precisión al usar cut() . Asi que

set.seed(123) temp <- data.frame(name=letters[1:12], value=rnorm(12), quartile=rep(NA, 12)) brks <- with(temp, quantile(value, probs = c(0, 0.25, 0.5, 0.75, 1))) temp <- within(temp, quartile <- cut(value, breaks = brks, labels = 1:4, include.lowest = TRUE))

Dando:

> head(temp) name value quartile 1 a -0.56047565 1 2 b -0.23017749 2 3 c 1.55870831 4 4 d 0.07050839 2 5 e 0.12928774 3 6 f 1.71506499 4


data.table versión de data.table para que alguien más la data.table en Google (es decir, la solución de @ BondedDust se tradujo a data.table y se redujo un poco):

library(data.table) setDT(temp) temp[ , quartile := cut(value, breaks = quantile(value, probs = 0:4/4), labels = 1:4, right = FALSE)]

Que es mucho mejor (más limpio, faster ) de lo que había estado haciendo:

temp[ , quartile := as.factor(ifelse(value < quantile(value, .25), 1, ifelse(value < quantile(value, .5), 2, ifelse(value < quantile(value, .75), 3, 4))]

Tenga en cuenta, sin embargo, que este enfoque requiere que los cuantiles sean distintos, por ejemplo, fallará en la rep(0:1, c(100, 1)) ; qué hacer en este caso es de final abierto, así que te lo dejo a ti.


temp$quartile <- ceiling(sapply(temp$value,function(x) sum(x-temp$value>=0))/(length(temp$value)/4))