webentwicklung-frage-antwort-db.com.de

Verwenden von Dplyr-Fensterfunktionen zur Berechnung der Perzentile

Ich habe eine funktionierende Lösung, suche aber nach einer saubereren, besser lesbaren Lösung, die möglicherweise einige der neueren Dplyr-Fensterfunktionen nutzt.

Wenn ich mit dem mtcars-Datensatz die 25., 50., 75. Perzentile und den Mittelwert und die Anzahl der Meilen pro Gallone ("mpg") anhand der Anzahl der Zylinder ("cyl") anzeigen möchte, verwende ich den folgenden Code:

library(dplyr)
library(tidyr)

# load data
data("mtcars")

# Percentiles used in calculation
p <- c(.25,.5,.75)

# old dplyr solution 
mtcars %>% group_by(cyl) %>% 
  do(data.frame(p=p, stats=quantile(.$mpg, probs=p), 
                n = length(.$mpg), avg = mean(.$mpg))) %>%
  spread(p, stats) %>%
  select(1, 4:6, 3, 2)

# note: the select and spread statements are just to get the data into
#       the format in which I'd like to see it, but are not critical

Gibt es einen Weg, wie ich dies mit dplyr mit einigen der Summenfunktionen (n_tiles, percent_rank usw.) sauberer machen kann? Mit sauber meine ich ohne die "do" -Anweisung.

Vielen Dank

37
dreww2

Wenn Sie purrr::map verwenden möchten, können Sie dies wie folgt tun!

library(tidyverse)

mtcars %>%
  tbl_df() %>%
  nest(-cyl) %>%
  mutate(Quantiles = map(data, ~ quantile(.$mpg)),
         Quantiles = map(Quantiles, ~ bind_rows(.) %>% gather())) %>% 
  unnest(Quantiles)

#> # A tibble: 15 x 3
#>      cyl key   value
#>    <dbl> <chr> <dbl>
#>  1     6 0%     17.8
#>  2     6 25%    18.6
#>  3     6 50%    19.7
#>  4     6 75%    21  
#>  5     6 100%   21.4
#>  6     4 0%     21.4
#>  7     4 25%    22.8
#>  8     4 50%    26  
#>  9     4 75%    30.4
#> 10     4 100%   33.9
#> 11     8 0%     10.4
#> 12     8 25%    14.4
#> 13     8 50%    15.2
#> 14     8 75%    16.2
#> 15     8 100%   19.2

Erstellt am 2018-11-10 vom reprex-Paket (v0.2.1)

Eine schöne Sache über diesen Ansatz ist, dass die Ausgabe sauber ist, eine Beobachtung pro Zeile.

22
Julia Silge

UPDATE 2: Noch ein Update, um die summarise() der vorherigen Version mit enframe in einen Einzeiler zu verwandeln:

library(tidyverse)

mtcars %>% 
  group_by(cyl) %>% 
  summarise(mpg = list(enframe(quantile(mpg, probs=c(0.25,0.5,0.75))))) %>% 
  unnest
    cyl quantiles   mpg
1     4       25% 22.80
2     4       50% 26.00
3     4       75% 30.40
4     6       25% 18.65
5     6       50% 19.70
6     6       75% 21.00
7     8       25% 14.40
8     8       50% 15.20
9     8       75% 16.25

Dies kann mit tidyeval in eine allgemeinere Funktion umgewandelt werden:

q_by_group = function(data, value.col, ..., probs=seq(0,1,0.25)) {

  value.col=enquo(value.col)
  groups=enquos(...)

  data %>% 
    group_by(!!!groups) %>% 
    summarise(mpg = list(enframe(quantile(!!value.col, probs=probs)))) %>% 
    unnest
}

q_by_group(mtcars, mpg)
q_by_group(mtcars, mpg, cyl)
q_by_group(mtcars, mpg, cyl, vs, probs=c(0.5,0.75))
q_by_group(iris, Petal.Width, Species)

UPDATE: Hier ist eine Variation der Antwort von @ JuliaSilge, bei der die Quantile durch Schachteln erhalten werden, jedoch ohne map. Es ist jedoch eine zusätzliche Codezeile erforderlich, um eine Spalte mit den Quantilebenen hinzuzufügen, da ich nicht sicher bin, wie (oder ob es möglich ist), die Namen der Quantile direkt in einer separaten Spalte vom Aufruf an quantile zu erfassen. .

p = c(0.25,0.5,0.75)

mtcars %>% 
  group_by(cyl) %>% 
  summarise(quantiles = list(sprintf("%1.0f%%", p*100)),
            mpg = list(quantile(mpg, p))) %>% 
  unnest

ORIGINAL ANTWORT

Hier ist ein dplyr-Ansatz, der do vermeidet, jedoch einen separaten Aufruf von quantile für jeden Quantilwert erfordert. 

mtcars %>% group_by(cyl) %>%
  summarise(`25%`=quantile(mpg, probs=0.25),
            `50%`=quantile(mpg, probs=0.5),
            `75%`=quantile(mpg, probs=0.75),
            avg=mean(mpg),
            n=n())

  cyl   25%  50%   75%      avg  n
1   4 22.80 26.0 30.40 26.66364 11
2   6 18.65 19.7 21.00 19.74286  7
3   8 14.40 15.2 16.25 15.10000 14

Es wäre besser, wenn summarise mit einem einzigen Aufruf von quantile mehrere Werte zurückgeben könnte. Dies scheint jedoch ein offenes Problem in der dplyr-Entwicklung zu sein.

48
eipi10

Hierbei handelt es sich um einen dplyr-Ansatz, der die tidy()-Funktion des broom-Pakets verwendet. Leider erfordert es weiterhin do(), ist jedoch wesentlich einfacher.

library(dplyr)
library(broom)

mtcars %>%
    group_by(cyl) %>%
    do( tidy(t(quantile(.$mpg))) )

was gibt:

    cyl   X0.  X25.  X50.  X75. X100.
  (dbl) (dbl) (dbl) (dbl) (dbl) (dbl)
1     4  21.4 22.80  26.0 30.40  33.9
2     6  17.8 18.65  19.7 21.00  21.4
3     8  10.4 14.40  15.2 16.25  19.2

Beachten Sie die Verwendung von t(), da das broom-Paket über keine Methode für benannte Numerik verfügt.

Dies basiert auf meiner früheren Antwort für summary () hier .

14
Bastiaan Quast

Sie sind sich nicht sicher, wie do() in dplyr vermieden werden kann, aber Sie können dies mit c() und as.list() mit data.table auf ziemlich einfache Weise tun:

require(data.table) 
as.data.table(mtcars)[, c(as.list(quantile(mpg, probs=p)), 
                        avg=mean(mpg), n=.N), by=cyl]
#    cyl   25%  50%   75%      avg  n
# 1:   6 18.65 19.7 21.00 19.74286  7
# 2:   4 22.80 26.0 30.40 26.66364 11
# 3:   8 14.40 15.2 16.25 15.10000 14

Ersetzen Sie by durch keyby, wenn Sie sie nach cyl-Spalte sortieren möchten.

9
Arun

Bei dieser Lösung werden nur dplyr und tidyr verwendet. Sie können Ihre Quantile in der dplyr-Kette angeben und tidyr::crossing() zum "Stapeln" mehrerer Kopien des Datensatzes vor dem Gruppieren und Zusammenfassen verwenden.

diamonds %>%  # Initial data
  tidyr::crossing(pctile = 0:4/4) %>%  # Specify quantiles; crossing() is like expand.grid()
  dplyr::group_by(cut, pctile) %>%  # Indicate your grouping var, plus your quantile var
  dplyr::summarise(quantile_value = quantile(price, unique(pctile))) %>%  # unique() is needed
  dplyr::mutate(pctile = sprintf("%1.0f%%", pctile*100))  # Optional prettification

Ergebnis:

# A tibble: 25 x 3
# Groups:   cut [5]
         cut pctile quantile_value
       <ord>  <chr>          <dbl>
 1      Fair     0%         337.00
 2      Fair    25%        2050.25
 3      Fair    50%        3282.00
 4      Fair    75%        5205.50
 5      Fair   100%       18574.00
 6      Good     0%         327.00
 7      Good    25%        1145.00
 8      Good    50%        3050.50
 9      Good    75%        5028.00
10      Good   100%       18788.00
11 Very Good     0%         336.00
12 Very Good    25%         912.00
13 Very Good    50%        2648.00
14 Very Good    75%        5372.75
15 Very Good   100%       18818.00
16   Premium     0%         326.00
17   Premium    25%        1046.00
18   Premium    50%        3185.00
19   Premium    75%        6296.00
20   Premium   100%       18823.00
21     Ideal     0%         326.00
22     Ideal    25%         878.00
23     Ideal    50%        1810.00
24     Ideal    75%        4678.50
25     Ideal   100%       18806.00

Die unique() ist notwendig, um dplyr::summarise() wissen zu lassen, dass Sie nur einen Wert pro Gruppe wünschen.

3
isDotR

Hier ist eine ziemlich lesbare Lösung, die dplyr und purrr verwendet, um Quantile in einem ordentlichen Format zurückzugeben:

Code

library(dplyr)
library(purrr)

mtcars %>% 
    group_by(cyl) %>% 
    do({x <- .$mpg
        map_dfr(.x = c(.25, .5, .75),
                .f = ~ data_frame(Quantile = .x,
                                  Value = quantile(x, probs = .x)))
       })

Ergebnis

# A tibble: 9 x 3
# Groups:   cyl [3]
    cyl Quantile Value
  <dbl>    <dbl> <dbl>
1     4     0.25 22.80
2     4     0.50 26.00
3     4     0.75 30.40
4     6     0.25 18.65
5     6     0.50 19.70
6     6     0.75 21.00
7     8     0.25 14.40
8     8     0.50 15.20
9     8     0.75 16.25
0
bschneidr

Auf viele verschiedene Arten beantwortet. dplyr distinct machte den Unterschied für das, was ich tun wollte.

mtcars %>%
   select(cyl, mpg) %>%
   group_by(cyl) %>%
   mutate( qnt_0   = quantile(mpg, probs= 0),
           qnt_25  = quantile(mpg, probs= 0.25),
           qnt_50  = quantile(mpg, probs= 0.5),
           qnt_75  = quantile(mpg, probs= 0.75),
           qnt_100 = quantile(mpg, probs= 1),
              mean = mean(mpg),
                sd = sd(mpg)
          ) %>%
   distinct(qnt_0 ,qnt_25 ,qnt_50 ,qnt_75 ,qnt_100 ,mean ,sd)

macht

# A tibble: 3 x 8
# Groups:   cyl [3]
  qnt_0 qnt_25 qnt_50 qnt_75 qnt_100  mean    sd   cyl
  <dbl>  <dbl>  <dbl>  <dbl>   <dbl> <dbl> <dbl> <dbl>
1  17.8   18.6   19.7   21      21.4  19.7  1.45     6
2  21.4   22.8   26     30.4    33.9  26.7  4.51     4
3  10.4   14.4   15.2   16.2    19.2  15.1  2.56     8
0
Antex

Hier ist eine Lösung, die eine Kombination aus dplyr, purrr und rlang verwendet:

library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
library(tidyr)
library(purrr)

# load data
data("mtcars")

# Percentiles used in calculation
p <- c(.25,.5,.75)

p_names <- paste0(p*100, "%")
p_funs <- map(p, ~partial(quantile, probs = .x, na.rm = TRUE)) %>% 
  set_names(nm = p_names)

# dplyr/purrr/rlang solution 
mtcars %>% 
  group_by(cyl) %>% 
  summarize_at(vars(mpg), funs(!!!p_funs))
#> # A tibble: 3 x 4
#>     cyl `25%` `50%` `75%`
#>   <dbl> <dbl> <dbl> <dbl>
#> 1     4  22.8  26    30.4
#> 2     6  18.6  19.7  21  
#> 3     8  14.4  15.2  16.2


#Especially useful if you want to summarize more variables
mtcars %>% 
  group_by(cyl) %>% 
  summarize_at(vars(mpg, drat), funs(!!!p_funs))
#> # A tibble: 3 x 7
#>     cyl `mpg_25%` `drat_25%` `mpg_50%` `drat_50%` `mpg_75%` `drat_75%`
#>   <dbl>     <dbl>      <dbl>     <dbl>      <dbl>     <dbl>      <dbl>
#> 1     4      22.8       3.81      26         4.08      30.4       4.16
#> 2     6      18.6       3.35      19.7       3.9       21         3.91
#> 3     8      14.4       3.07      15.2       3.12      16.2       3.22

Erstellt am 01.01.2018 vom reprex-Paket (v0.2.0).

0
tbradley

do() ist in der Tat die richtige Redewendung, da es für gruppenweise Transformationen konzipiert ist. Stellen Sie sich dies als eine lapply() vor, die Gruppen eines Datenrahmens abbildet. (Für eine solche spezialisierte Funktion ist ein allgemeiner Name wie "do" nicht ideal. Aber es ist wahrscheinlich zu spät, um ihn zu ändern.)

Moralisch möchten Sie in jeder cyl-Gruppe quantile() auf die mpg-Spalte anwenden:

library(dplyr)

p <- c(.2, .5, .75)

mtcars %>% 
  group_by(cyl) %>%
  do(quantile(.$mpg, p))

#> Error: Results 1, 2, 3 must be data frames, not numeric

Nur dass dies nicht funktioniert, da quantile() keinen Datenrahmen zurückgibt. Sie müssen die Ausgabe explizit konvertieren. Da diese Änderung der Umschaltung von quantile() mit einem Datenrahmen entspricht, können Sie den gestalt Funktionszusammensetzungsoperator %>>>% verwenden:

library(gestalt)
library(tibble)

quantile_tbl <- quantile %>>>% enframe("quantile")

mtcars %>% 
  group_by(cyl) %>%
  do(quantile_tbl(.$mpg, p))

#> # A tibble: 9 x 3
#> # Groups:   cyl [3]
#>     cyl quantile value
#>   <dbl> <chr>    <dbl>
#> 1     4 20%       22.8
#> 2     4 50%       26  
#> 3     4 75%       30.4
#> 4     6 20%       18.3
#> 5     6 50%       19.7
#> 6     6 75%       21  
#> 7     8 20%       13.9
#> 8     8 50%       15.2
#> 9     8 75%       16.2
0
egnha