web-dev-qa-db-fra.com

Utilisation des fonctions de la fenêtre dplyr pour calculer les centiles

J'ai une solution qui fonctionne mais je recherche une solution plus propre et plus lisible qui tire peut-être parti des nouvelles fonctions de la fenêtre dplyr.

En utilisant le jeu de données mtcars, si je veux regarder les 25e, 50e, 75e centiles et la moyenne et le nombre de milles par gallon ("mpg") par le nombre de cylindres ("cyl"), j'utilise le code suivant:

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

Existe-t-il un moyen de le faire plus proprement avec dplyr en utilisant certaines des fonctions de synthèse (n_tiles, percent_rank, etc.)? Par proprement, je veux dire sans la déclaration "do".

Je vous remercie

37
dreww2

Si vous êtes prêt à utiliser purrr::map, vous pouvez le faire comme ceci!

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

Créé le 2018-11-10 par le paquet reprex (v0.2.1)

Une bonne chose à propos de cette approche est que la sortie est soignée, une observation par ligne.

22
Julia Silge

UPDATE 2: Encore une mise à jour pour transformer la summarise() de la version précédente en une ligne avec enframe:

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

Cela peut être transformé en une fonction plus générale en utilisant tidyeval:

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: Voici une variante de la réponse de @ JuliaSilge qui utilise l'imbrication pour obtenir les quantiles, mais sans l'utilisation de map. Cependant, il faut une ligne de code supplémentaire pour ajouter une colonne répertoriant les niveaux de quantiles, car je ne sais pas comment (ou si cela est possible) pour capturer les noms des quantiles dans une colonne distincte directement à partir de l'appel de quantile .

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

REPONSE ORIGINALE

Voici une approche dplyr qui évite do mais nécessite un appel séparé à quantile pour chaque valeur de quantile. 

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

Il serait préférable que summarise puisse renvoyer plusieurs valeurs avec un seul appel à quantile, mais cela semble être un problème en suspens in dplyr développement.

48
eipi10

C'est une approche dplyr qui utilise la fonction tidy() du paquet broom; malheureusement, elle nécessite toujours do(), mais elle est beaucoup plus simple.

library(dplyr)
library(broom)

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

qui donne:

    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

Notez l'utilisation de t() car le package broom n'a pas de méthode pour les nombres nommés.

Ceci est basé sur mon réponse précédente pour summary () ici .

14
Bastiaan Quast

Vous ne savez pas comment éviter do() dans dplyr, mais vous pouvez le faire avec c() et as.list() avec data.table de manière assez simple:

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

Remplacez by par keyby si vous souhaitez les classer par la colonne cyl.

9
Arun

Cette solution utilise uniquement dplyr et tidyr, vous permet de spécifier vos quantiles dans la chaîne dplyr et tire parti de tidyr::crossing() pour "empiler" plusieurs copies du jeu de données avant le regroupement et la synthèse.

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

Résultat:

# 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

La unique() est nécessaire pour que dplyr::summarise() sache que vous ne voulez qu'une valeur par groupe.

3
isDotR

Voici une solution relativement lisible qui utilise dplyr et purrr pour renvoyer des quantiles dans un format ordonné:

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

Résultat

# 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

Voici une solution utilisant une combinaison de dplyr, purrr et 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

Créé le 2018-10-01 par le paquet reprex (v0.2.0).

0
tbradley

do() est en fait le bon idiome, car il est conçu pour les transformations par groupe. Considérez-le comme une lapply() qui mappe sur des groupes d'un bloc de données. (Pour une fonction aussi spécialisée, un nom générique tel que "do" n’est pas idéal. Mais il est probablement trop tard pour le changer.)

Moralement, au sein de chaque groupe cyl, vous souhaitez appliquer quantile() à la colonne 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

Sauf que cela ne fonctionne pas car quantile() ne renvoie pas de trame de données; vous devez convertir sa sortie, explicitement. Étant donné que cette modification revient à encapsuler quantile() avec un bloc de données, vous pouvez utiliser l'opérateur gestalt function composition %>>>%:

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