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