So bilden Sie schnell Gruppen (Quartile, Dezile usw.), indem Sie Spalten in einem Datenrahmen anordnen


74

Ich sehe viele Fragen und Antworten zu orderund sort. Gibt es etwas, das Vektoren oder Datenrahmen in Gruppierungen sortiert (wie Quartile oder Dezile)? Ich habe eine "manuelle" Lösung, aber es gibt wahrscheinlich eine bessere Lösung, die in Gruppen getestet wurde.

Hier ist mein Versuch:

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

Gibt es einen besseren (saubereren / schnelleren / einzeiligen) Ansatz? Vielen Dank!

Antworten:


79

Die Methode, die ich benutze, ist eine dieser oder 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))

Eine Alternative könnte sein:

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

Der erste hat den Nebeneffekt, dass die Quartile mit den Werten gekennzeichnet werden, was ich für "gut" halte, aber wenn es nicht "gut für Sie" wäre oder die in den Kommentaren angesprochenen gültigen Probleme ein Problem darstellen, könnten Sie gehen mit Version 2. Sie können labels=in verwenden cutoder diese Zeile zu Ihrem Code hinzufügen:

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

Oder noch schneller, aber etwas unklarer, wie es funktioniert, obwohl es kein Faktor mehr ist, sondern ein numerischer Vektor:

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

12
cut()hat ein Argument, labelsdas verwendet werden kann, damit Sie die factor()Zeile nicht benötigen - fügen Sie einfach labels = 1:4den cut()Aufruf Ihrer ersten Zeile hinzu.
Gavin Simpson

3
Das Hmisc-Paket hat auch eine cut2-Funktion mit einem "m" -Argument, das in "m" (ungefähr) gleiche Abschnitte schneidet.
IRTFM

1
Ich möchte hinzufügen, dass der Fehler: "Unterbrechungen" sind nicht eindeutig , wenn Sie Quantile für eine Zeitreihe mit einigen Duplikaten berechnen, weil beispielsweise das niedrigste Quantil (0%) dem nächsthöheren (10) entspricht %). findIntervalwie oben verwendet scheint in diesem Fall besser zu sein
user3032689

@ 42- Könnten Sie bitte dasselbe für Dezile und Daten mit NAs vorschlagen?
Wassermann

für Dezile probs=c((0:9)/10), Inf)mit findInterval oder probs=seq(0,1, by=0.1))zum Schneiden verwenden. Ein wichtiger Unterschied zwischen diesen beiden Funktionen besteht darin, dass die Intervalle standardmäßig links für findIntervalund rechts für geschlossen sind cut. Guter Punkt über NAs; Wie sum oder main oder max sollte wahrscheinlich na.rm = TRUE für addieren quantile.
IRTFM

86

Es gibt eine praktische ntileFunktion im Paket dplyr. Es ist flexibel in dem Sinne, dass Sie sehr einfach die Anzahl der * Kacheln oder "Bins" definieren können, die Sie erstellen möchten.

Laden Sie das Paket (installieren Sie es zuerst, wenn Sie es nicht haben) und fügen Sie die Quartilspalte hinzu:

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

Oder wenn Sie die dplyr-Syntax verwenden möchten:

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

Ergebnis in beiden Fällen ist:

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

Daten:

Beachten Sie, dass Sie die Spalte "Quartil" nicht im Voraus erstellen und verwenden müssen set.seed, um die Randomisierung reproduzierbar zu machen:

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

Gute Alternative, aber Ihrer Antwort fehlen Informationen zu den Haltepunkten, die von ntile(einschließlich niedrigster, höchster, Bindungen) verwendet werden
EDC

2
Das sollte das Problem der Endpunkte beheben, oder? temp <- temp %>% mutate(quartile = cut(x = ntile(value, 100), breaks = seq(25,100,25) , include.lowest = TRUE, right = FALSE , labels = FALSE))
Hannes101

20

Ich werde die data.tableVersion für alle anderen hinzufügen, die sie googeln (dh die Lösung von @ BondedDust data.tablewurde in ein bisschen übersetzt und reduziert):

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

Was ist viel besser (sauberer, schneller ) als das, was ich getan habe:

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

Beachten Sie jedoch, dass bei diesem Ansatz die Quantile unterschiedlich sein müssen, z. B. wenn dies fehlschlägt rep(0:1, c(100, 1)). Was in diesem Fall zu tun ist, ist offen, also überlasse ich es Ihnen.


2
Die data.table-Version ist übrigens die schnellste Methode. Danke @MichaelChirico.
rafa.pereira

1
Ich denke hier right = Fist falsch. Der Maximalwert ist nicht nur nicht gruppiert, sondern Ihre Daten sind 1:21, der Median ist 11, sondern wird in die .75-Gruppe gruppiert.
00schneider

8

Sie können die quantile()Funktion verwenden, müssen jedoch bei der Verwendung mit Rundung / Präzision umgehen cut(). Damit

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

Geben:

> 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

5

Tut mir leid, dass ich etwas zu spät zur Party komme. Ich wollte meinen Einzeiler mit hinzufügen, cut2da ich max / min für meine Daten nicht kannte und wollte, dass die Gruppen identisch groß sind. Ich habe über cut2 in einer Ausgabe gelesen, die als Duplikat markiert war (Link unten).

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

Ergebnis:

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

Ähnliches Problem, in dem ich ausführlich über cut2 gelesen habe


5

Die Anpassung dplyr::ntilean data.tableOptimierungen bietet eine schnellere Lösung.

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

Wahrscheinlich nicht als sauberer zu qualifizieren, aber es ist schneller und einzeilig.

Timing bei größerem Datensatz

Vergleich dieser Lösung mit ntileund cutfür data.tablewie von @docendo_discimus und @MichaelChirico vorgeschlagen.

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

Gibt:

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

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

0

Ich möchte eine Version vorschlagen, die robuster zu sein scheint, da ich bei der Verwendung quantile()der Unterbrechungsoption cut()in meinem Datensatz auf viele Probleme gestoßen bin. Ich benutze die ntileFunktion von plyr, aber es funktioniert auch ecdfals Eingabe.

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

Ist das korrekt?


0

Probieren Sie diese Funktion aus

getQuantileGroupNum <- function(vec, group_num, decreasing=FALSE) {
  if(decreasing) {
    abs(cut(vec, quantile(vec, probs=seq(0, 1, 1 / group_num), type=8, na.rm=TRUE), labels=FALSE, include.lowest=T) - group_num - 1)
  } else {
    cut(vec, quantile(vec, probs=seq(0, 1, 1 / group_num), type=8, na.rm=TRUE), labels=FALSE, include.lowest=T)
  }
}
> t1 <- runif(7)
> t1
[1] 0.4336094 0.2842928 0.5578876 0.2678694 0.6495285 0.3706474 0.5976223
> getQuantileGroupNum(t1, 4)
[1] 2 1 3 1 4 2 4
> getQuantileGroupNum(t1, 4, decreasing=T)
[1] 3 4 2 4 1 3 1

-1

Es gibt möglicherweise einen schnelleren Weg, aber ich würde tun:

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))
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.