Comment créer rapidement des groupes (quartiles, déciles, etc.) en classant des colonnes dans un bloc de données

Je vois beaucoup de questions et de réponses concernant l’ order et le sort . Y a-t-il quelque chose qui sortinge les vecteurs ou les blocs de données en groupes (comme les quartiles ou les déciles)? J’ai une solution “manuelle”, mais il y a probablement une meilleure solution qui a été testée en groupe.

Voici ma tentative:

 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 

Existe-t-il une meilleure approche (plus propre / plus rapide / une seule ligne)? Merci!

La méthode que j’utilise est l’une d’entre elles ou 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 autre pourrait être:

 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") )) 

La première a pour effet secondaire de qualifier les quartiles de valeurs, ce que je considère comme une "bonne chose", mais si ce n'était pas "bon pour vous", ou si les problèmes valables soulevés dans les commentaires étaient une préoccupation, vous pourriez aller avec la version 2. Vous pouvez utiliser des labels= in cut , ou vous pouvez append cette ligne à votre code:

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

Ou encore plus vite mais légèrement plus obscur dans son fonctionnement, bien que ce ne soit plus un facteur, mais plutôt un vecteur numérique:

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

Il existe une fonction pratique dans le package dplyr . C’est flexible en ce sens que vous pouvez très facilement définir le nombre de * tuiles ou de “bacs” que vous souhaitez créer.

Chargez le paquet (installez d’abord si vous ne l’avez pas fait) et ajoutez la colonne quartile:

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

Ou, si vous souhaitez utiliser la syntaxe dplyr:

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

Le résultat dans les deux cas est:

 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 

Les données:

Notez qu'il n'est pas nécessaire de créer la colonne "quartile" à l'avance et d'utiliser set.seed pour rendre la randomisation reproductible:

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

J’appendai la version de data.table pour tout le monde Googling (c.-à-d. La solution de @ BondedDust traduite en data.table et épurée un peu):

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

Ce qui est beaucoup mieux (plus propre, plus rapide ) que ce que je faisais:

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

Notez, cependant, que cette approche nécessite que les quantiles soient distincts, par exemple, ils échoueront sur rep(0:1, c(100, 1)) ; que faire dans ce cas est ouvert et je vous laisse le soin de le faire.

Vous pouvez utiliser la fonction quantile() , mais vous devez gérer l’arrondi / la précision lors de l’utilisation de cut() . Alors

 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)) 

Donnant:

 > 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 

L’adaptation de dplyr::ntile pour tirer parti des optimisations de data.table fournit une solution plus rapide.

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

Probablement n’est pas considéré comme plus propre, mais c’est plus rapide et une ligne.

Timing sur un plus grand dataset

Comparer cette solution à ntile et cut pour data.table comme proposé par @docendo_discimus et @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)] ) 

Donne:

 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 

Désolé d’être un peu en retard pour la fête. Je voulais append mon seul liner en utilisant cut2 car je ne connaissais pas les données max / min et souhaitais que les groupes soient identiques. J’ai lu à propos de cut2 dans un numéro qui était marqué comme duplicata (lien ci-dessous).

 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 

Résultat:

 > 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) 

Problème similaire où j'ai lu à propos de cut2 en détail

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

Je voudrais proposer une version qui semble être plus robuste, car j’ai rencontré beaucoup de problèmes en utilisant quantile() dans l’option de pause cut() sur mon jeu de données. J’utilise la fonction ntile de plyr , mais elle fonctionne aussi avec ecdf en entrée.

 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) )] 

Est-ce exact?

Il y a peut-être un moyen plus rapide, mais je le ferais:

 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