Dplyr-vensterfuncties gebruiken om percentielen te berekenen

Ik heb een werkende oplossing, maar ben op zoek naar een schonere, beter leesbare oplossing die misschien gebruik maakt van enkele van de nieuwere dplyr-vensterfuncties.

Als ik de mtcars-dataset gebruik en wil kijken naar de 25e, 50e, 75e percentielen en het gemiddelde en aantal mijlen per gallon (“mpg”) door het aantal cilinders (“cyl”), gebruik ik het volgende 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

Is er een manier waarop ik dit op een schonere manier kan doen met dplyr met behulp van enkele van de samenvattingsfuncties (n_tiles, percent_rank, enz.)? Met netjes bedoel ik zonder de “do”-verklaring.

Bedankt


Antwoord 1, autoriteit 100%

In dplyr 1.0, summarisekan meerdere waarden retourneren, waardoor het volgende mogelijk is:

library(tidyverse)
mtcars %>% 
  group_by(cyl) %>%  
  summarise(quantile = scales::percent(c(0.25, 0.5, 0.75)),
            mpg = quantile(mpg, c(0.25, 0.5, 0.75)))

Of je kunt een aparte regel vermijden om de kwantielen een naam te geven door enframete gebruiken:

mtcars %>% 
  group_by(cyl) %>%  
  summarise(enframe(quantile(mpg, c(0.25, 0.5, 0.75)), "quantile", "mpg"))
   cyl quantile   mpg
  <dbl> <chr>    <dbl>
1     4 25%       22.8
2     4 50%       26  
3     4 75%       30.4
4     6 25%       18.6
5     6 50%       19.7
6     6 75%       21  
7     8 25%       14.4
8     8 50%       15.2
9     8 75%       16.2

Antwoord voor eerdere versies van dplyr

library(tidyverse)
mtcars %>% 
  group_by(cyl) %>% 
  summarise(x=list(enframe(quantile(mpg, probs=c(0.25,0.5,0.75)), "quantiles", "mpg"))) %>% 
  unnest(x)
   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

Dit kan worden omgezet in een meer algemene functie met behulp van propereval:

q_by_group = function(data, value.col, ..., probs=seq(0,1,0.25)) {
  groups=enquos(...)
  data %>% 
    group_by(!!!groups) %>% 
    summarise(x = list(enframe(quantile({{value.col}}, probs=probs), "quantiles", "mpg"))) %>% 
    unnest(x)
}
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)

Antwoord 2, autoriteit 52%

Als je klaar bent om purrr::mapte gebruiken, kun je het als volgt doen!

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

Gemaakt op 2018-11-10 door het reprex-pakket(v0.2.1)

Een leuk aspect van deze aanpak is dat de output netjes is, één observatie per rij.


Antwoord 3, autoriteit 21%

Dit is een dplyr-benadering die de functie tidy()van het pakket broomgebruikt, helaas vereist het nog steeds do(), maar het is een stuk eenvoudiger.

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

wat geeft:

   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

Let op het gebruik van t()aangezien het pakket broomgeen methode heeft voor benoemde numerieke tekens.

Dit is gebaseerd op mijn eerdere antwoord voor samenvatting() hier.


Antwoord 4, autoriteit 15%

Weet niet hoe u do()in dplyrmoet vermijden, maar u kunt dit doen met c()en as.list()met data.tableop een vrij eenvoudige manier:

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

Vervang bydoor keybyals u ze wilt bestellen per kolom cyl.


Antwoord 5, autoriteit 7%

Deze oplossing gebruikt alleen dplyren tidyr, laat u uw kwantielen specificeren in de dplyr-keten en maakt gebruik van tidyr::crossing()om meerdere kopieën van de dataset te “stapelen” voorafgaand aan het groeperen en samenvatten.

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

Resultaat:

# 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

De unique()is nodig om dplyr::summarise()te laten weten dat je maar één waarde per groep wilt.


Antwoord 6, autoriteit 6%

Veel verschillende manieren beantwoord. dplyr distinct maakte het verschil voor wat ik wilde doen..

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)

weergave

# 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

Antwoord 7, autoriteit 5%

Hier is een oplossing met een combinatie van dplyr, purrren rlang:

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

Gemaakt op 2018-10-01 door het reprex-pakket(v0.2.0).

Bewerken (2019-04-17):

Vanaf dplyr 0.8.0is de functie funsafgeschaft ten gunste van het gebruik van listom de gewenste functies door te geven aan scoped dplyrfuncties. Als gevolg hiervan wordt de bovenstaande implementatie iets eenvoudiger. We hoeven ons geen zorgen meer te maken over het verwijderen van de aanhalingstekens van de functies met de !!!. Zie de onderstaande reprex:

library(dplyr)
#> Warning: package 'dplyr' was built under R version 3.5.2
#> 
#> 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), 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), 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

Gemaakt op 17-04-2019 door het reprex-pakket(v0.2.0).


Antwoord 8

Hier is een redelijk leesbare oplossing die dplyren purrrgebruikt om kwantielen in een opgeruimd formaat terug te geven:

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

Resultaat

# 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

Antwoord 9

Nog een andere manier om dit te bereiken, met unnest_wider/longer

   mtcars %>%
       group_by(cyl) %>%
       summarise(quants = list(quantile(mpg, probs = c(.01, .1, .25, .5, .75, .90,.99)))) %>%
       unnest_wider(quants)

En als u het voor meerdere variabelen wilt doen, kunt u vóór de groepering het volgende verzamelen:

mtcars %>%
   gather(key = 'metric', value = 'value', -cyl) %>%
   group_by(cyl, metric) %>%
   summarise(quants = list(quantile(value, probs = c(.01, .1, .25, .5, .75, .90,.99)))) %>%
  unnest_wider(quants)

Antwoord 10

do()is in feite het juiste idioom, omdat het is ontworpen voor groepsgewijze transformaties. Zie het als een lapply()die over groepen van een dataframe heen wijst. (Voor zo’n gespecialiseerde functie is een generieke naam als “do” niet ideaal. Maar het is waarschijnlijk te laat om het te veranderen.)

Moreel gezien wil je binnen elke cyl-groep quantile()toepassen op de kolom mpg:

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

Behalve dat dit niet werkt omdat quantile()geen dataframe retourneert; je moet de uitvoer expliciet converteren. Aangezien deze wijziging neerkomt op het inpakken van quantile()met een dataframe, kunt u de gestalt gebruiken operator voor functiecompositie %>>>%:

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

Other episodes