Mam następującą ramkę danych
x <- read.table(text = " id1 id2 val1 val2
1 a x 1 9
2 a x 2 4
3 a y 3 5
4 a y 4 9
5 b x 1 7
6 b y 4 4
7 b x 3 9
8 b y 2 8", header = TRUE)
Chcę obliczyć średnią z wartości val1 i val2 pogrupowanych według id1 i id2 i jednocześnie policzyć liczbę wierszy dla każdej kombinacji id1-id2. Mogę wykonać każdą kalkulację osobno:
# calculate mean
aggregate(. ~ id1 + id2, data = x, FUN = mean)
# count rows
aggregate(. ~ id1 + id2, data = x, FUN = length)
Próbowałem wykonać oba obliczenia podczas jednej rozmowy
do.call("rbind", aggregate(. ~ id1 + id2, data = x, FUN = function(x) data.frame(m = mean(x), n = length(x))))
Jednak otrzymuję zniekształcony wynik wraz z ostrzeżeniem:
# m n
# id1 1 2
# id2 1 1
# 1.5 2
# 2 2
# 3.5 2
# 3 2
# 6.5 2
# 8 2
# 7 2
# 6 2
# Warning message:
# In rbind(id1 = c(1L, 2L, 1L, 2L), id2 = c(1L, 1L, 2L, 2L), val1 = list( :
# number of columns of result is not a multiple of vector length (arg 1)
Mógłbym skorzystać z pakietu plyr, ale mój zestaw danych jest dość duży, a plyr jest bardzo powolny (prawie bezużyteczny), gdy rośnie rozmiar zestawu danych.
Jak mogę użyć aggregate
lub innych funkcji, aby wykonać kilka obliczeń w jednym połączeniu?
aggregate
wymienionych w odpowiedziach są równieżby
itapply
.Odpowiedzi:
Możesz to wszystko zrobić w jednym kroku i uzyskać odpowiednie oznakowanie:
> aggregate(. ~ id1+id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) ) # id1 id2 val1.mn val1.n val2.mn val2.n # 1 a x 1.5 2.0 6.5 2.0 # 2 b x 2.0 2.0 8.0 2.0 # 3 a y 3.5 2.0 7.0 2.0 # 4 b y 3.0 2.0 6.0 2.0
Spowoduje to utworzenie ramki danych z dwiema kolumnami id i dwiema kolumnami macierzowymi:
str( aggregate(. ~ id1+id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) ) ) 'data.frame': 4 obs. of 4 variables: $ id1 : Factor w/ 2 levels "a","b": 1 2 1 2 $ id2 : Factor w/ 2 levels "x","y": 1 1 2 2 $ val1: num [1:4, 1:2] 1.5 2 3.5 3 2 2 2 2 ..- attr(*, "dimnames")=List of 2 .. ..$ : NULL .. ..$ : chr "mn" "n" $ val2: num [1:4, 1:2] 6.5 8 7 6 2 2 2 2 ..- attr(*, "dimnames")=List of 2 .. ..$ : NULL .. ..$ : chr "mn" "n"
Jak wskazano w @ lord.garbage poniżej, można to przekonwertować na ramkę danych z „prostymi” kolumnami, używając
do.call(data.frame, ...)
str( do.call(data.frame, aggregate(. ~ id1+id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) ) ) ) 'data.frame': 4 obs. of 6 variables: $ id1 : Factor w/ 2 levels "a","b": 1 2 1 2 $ id2 : Factor w/ 2 levels "x","y": 1 1 2 2 $ val1.mn: num 1.5 2 3.5 3 $ val1.n : num 2 2 2 2 $ val2.mn: num 6.5 8 7 6 $ val2.n : num 2 2 2 2
Oto składnia wielu zmiennych w LHS:
aggregate(cbind(val1, val2) ~ id1 + id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) )
źródło
d$val1[ , ""mn"]
spojrzeć na strukturę za pomocąstr
.agg <- aggregate(cbind(val1, val2) ~ id1 + id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x)))
używającagg_df <- do.call(data.frame, agg)
. Zobacz także tutaj .Biorąc to pod uwagę w pytaniu:
Następnie w
data.table
(1.9.4+
) możesz spróbować:> DT id1 id2 val1 val2 1: a x 1 9 2: a x 2 4 3: a y 3 5 4: a y 4 9 5: b x 1 7 6: b y 4 4 7: b x 3 9 8: b y 2 8 > DT[ , .(mean(val1), mean(val2), .N), by = .(id1, id2)] # simplest id1 id2 V1 V2 N 1: a x 1.5 6.5 2 2: a y 3.5 7.0 2 3: b x 2.0 8.0 2 4: b y 3.0 6.0 2 > DT[ , .(val1.m = mean(val1), val2.m = mean(val2), count = .N), by = .(id1, id2)] # named id1 id2 val1.m val2.m count 1: a x 1.5 6.5 2 2: a y 3.5 7.0 2 3: b x 2.0 8.0 2 4: b y 3.0 6.0 2 > DT[ , c(lapply(.SD, mean), count = .N), by = .(id1, id2)] # mean over all columns id1 id2 val1 val2 count 1: a x 1.5 6.5 2 2: a y 3.5 7.0 2 3: b x 2.0 8.0 2 4: b y 3.0 6.0 2
Do porównania czasów
aggregate
(zastosowanych w pytaniu i wszystkich 3 innych odpowiedzi), abydata.table
zobaczyć ten punkt odniesienia ( przypadkiagg
iagg.x
).źródło
Możesz dodać
count
kolumnę, zagregować jąsum
, a następnie skalować z powrotem, aby uzyskaćmean
:x$count <- 1 agg <- aggregate(. ~ id1 + id2, data = x,FUN = sum) agg # id1 id2 val1 val2 count # 1 a x 3 13 2 # 2 b x 4 16 2 # 3 a y 7 14 2 # 4 b y 6 12 2 agg[c("val1", "val2")] <- agg[c("val1", "val2")] / agg$count agg # id1 id2 val1 val2 count # 1 a x 1.5 6.5 2 # 2 b x 2.0 8.0 2 # 3 a y 3.5 7.0 2 # 4 b y 3.0 6.0 2
Ma tę zaletę, że zachowuje nazwy kolumn i tworzy jedną
count
kolumnę.źródło
Korzystając z
dplyr
pakietu, możesz to osiągnąć za pomocąsummarise_all
. Dzięki tej funkcji podsumowania możesz zastosować inne funkcje (w tym przypadkumean
in()
) do każdej z kolumn niegrupujących:co daje:
id1 id2 val1_mean val2_mean val1_n val2_n 1 a x 1.5 6.5 2 2 2 a y 3.5 7.0 2 2 3 b x 2.0 8.0 2 2 4 b y 3.0 6.0 2 2
Jeśli nie chcesz zastosować funkcji do wszystkich kolumn niegrupujących, określ kolumny, do których mają zostać zastosowane lub wykluczając niepotrzebne z minusem za pomocą
summarise_at()
funkcji:# inclusion x %>% group_by(id1, id2) %>% summarise_at(vars(val1, val2), funs(mean, n())) # exclusion x %>% group_by(id1, id2) %>% summarise_at(vars(-val2), funs(mean, n()))
źródło
Może chcesz się połączyć ?
x.mean <- aggregate(. ~ id1+id2, p, mean) x.len <- aggregate(. ~ id1+id2, p, length) merge(x.mean, x.len, by = c("id1", "id2")) id1 id2 val1.x val2.x val1.y val2.y 1 a x 1.5 6.5 2 2 2 a y 3.5 7.0 2 2 3 b x 2.0 8.0 2 2 4 b y 3.0 6.0 2 2
źródło
Możesz także użyć
plyr::each()
do wprowadzenia wielu funkcji:źródło
Inną
dplyr
opcją jest to,across
co jest częścią aktualnej wersji deweloperskiej#devtools::install_github("tidyverse/dplyr") library(dplyr) x %>% group_by(id1, id2) %>% summarise(across(starts_with("val"), list(mean = mean, n = length)))
Wynik
# A tibble: 4 x 4 # Groups: id1 [2] id1 id2 mean$val1 $val2 n$val1 $val2 <fct> <fct> <dbl> <dbl> <int> <int> 1 a x 1.5 6.5 2 2 2 a y 3.5 7 2 2 3 b x 2 8 2 2 4 b y 3 6 2 2
packageVersion("dplyr") [1] ‘0.8.99.9000’
źródło