Jak podsumować dane według grupy w R? [Zamknięte]


181

Mam ramkę danych R taką jak ta:

        age group
1   23.0883     1
2   25.8344     1
3   29.4648     1
4   32.7858     2
5   33.6372     1
6   34.9350     1
7   35.2115     2
8   35.2115     2
9   35.2115     2
10  36.7803     1
...

Potrzebuję uzyskać ramkę danych w następującej formie:

group mean     sd
1     34.5     5.6
2     32.3     4.2
...

Numer grupy może się różnić, ale ich nazwy i ilość można uzyskać dzwoniąc levels(factor(data$group))

Jakie manipulacje należy wykonać przy użyciu danych, aby uzyskać wynik?


przecinki w wynikowej ramce danych oznaczają coś specjalnego, czy to tylko kropka dziesiętna?
mpiktas

@mpiktas Dziękujemy za uwagę. Poprawione Były to problemy regionalne (jestem Rosjaninem) - używamy przecinka do separacji dziesiętnej.
Jurij Pietrowski

3
Podejrzewałem to. Cała Europa używa przecinka, z wyjątkiem Brytyjczyków.
mpiktas

4
Mimo że nie jestem Brytyjczykiem, wolę kropkę jako separator dziesiętny.
Roman Luštrik

1
Zobacz aggregate, tapplya następnie stackoverflow.com, aby uzyskać wszelkie pytania dotyczące tego typu kodowania.
conjugateprior

Odpowiedzi:


140

Oto wariant plyr z jedną linią przy użyciu ddply :

dt <- data.frame(age=rchisq(20,10),group=sample(1:2,20,rep=T))
ddply(dt,~group,summarise,mean=mean(age),sd=sd(age))

Oto kolejny wariant z jedną linią, wykorzystujący nowy pakiet data.table .

dtf <- data.frame(age=rchisq(100000,10),group=factor(sample(1:10,100000,rep=T)))
dt <- data.table(dtf)
dt[,list(mean=mean(age),sd=sd(age)),by=group]

Ten jest szybszy, chociaż jest to zauważalne tylko na stole z 100k rzędami. Czasy na moim Macbooku Pro z procesorem Core 2 Duo 2,53 Ghz i R 2.11.1:

> system.time(aa <- ddply(dtf,~group,summarise,mean=mean(age),sd=sd(age)))
utilisateur     système      écoulé 
      0.513       0.180       0.692 
> system.time(aa <- dt[,list(mean=mean(age),sd=sd(age)),by=group])
utilisateur     système      écoulé 
      0.087       0.018       0.103 

Dalsze oszczędności są możliwe, jeśli wykorzystamy setkey:

> setkey(dt,group)
> system.time(dt[,list(mean=mean(age),sd=sd(age)),by=group])
utilisateur     système      écoulé 
      0.040       0.007       0.048 

2
@chl, dał mi szansę wypróbowania tego nowego pakietu data.table . Wygląda naprawdę obiecująco.
mpiktas,

7
+6000 dla tabeli danych. To naprawdę jest o wiele szybsze niż ddply, nawet dla mnie w zestawach danych mniejszych niż 100k (mam taki z zaledwie 20k wierszami). To musi być coś wspólnego z funkcjami, które stosuję, ale ddply zajmie minuty i dane. Tabela kilka sekund.
atomowe

Prosta literówka: Myślę, że miałeś na myśli dt <- data.table(dtf)zamiast dt <- data.table(dt)w drugim bloku kodu. W ten sposób tworzysz tabelę danych z ramki danych zamiast z dtfunkcji z statspakietu. Próbowałem go edytować, ale nie mogę edytować mniej niż sześciu znaków.
Christopher Bottoms

Moim zdaniem (nie skromnym w tym przypadku) data.tablejest najlepszym sposobem na agregację danych i ta odpowiedź jest świetna, ale wciąż rysuje tylko powierzchnię. Oprócz tego, że jest syntaktycznie lepszy, jest także niezwykle elastyczny i ma wiele zaawansowanych funkcji, które obejmują połączenia i mechanikę wewnętrzną. Sprawdź FAQ, stronę github lub kurs, aby uzyskać więcej informacji.
genorama

97

Jedną z możliwości jest użycie funkcji agregującej . Na przykład,

aggregate(data$age, by=list(data$group), FUN=mean)[2]

daje drugą kolumnę pożądanego wyniku.


1
Nie linkuj do lokalnego serwera pomocy :-) +1, ale zobacz moje komentarze do odpowiedzi @ steffen.
chl

data.frame(group=levels(factor(data$group)),mean=(aggregate(data$age, by=list(data$group), FUN=mean)$x),sd=(aggregate(data$age, by=list(data$group), FUN=sd)$x))Zrobiłem to, dzwoniąc, ale nie jestem pewien, czy to właściwy sposób. Nie jestem pewien, co się stanie, wtedy wyniki powiązanych kolumn będą w innej kolejności (myślę, że jest to możliwe). Jakie jest twoje zdanie?
Jurij Pietrowski

9
@Yuriy Wiersze nie powinny być w porządku, ale oto sposób na zrobienie tego za pomocą jednego połączenia z aggregate():aggregate(age ~ group, data=dat, FUN = function(x) c(M=mean(x), SD=sd(x)))
zablokowanym

@lockedoff: Dziękujemy za wypełnienie mojej odpowiedzi!
ocram

27

Ponieważ manipulujesz ramką danych, dplyrpakiet jest prawdopodobnie najszybszym sposobem na zrobienie tego.

library(dplyr)
dt <- data.frame(age=rchisq(20,10), group=sample(1:2,20, rep=T))
grp <- group_by(dt, group)
summarise(grp, mean=mean(age), sd=sd(age))

lub równoważnie, używając operatora dplyr/ magrittrpipe:

library(dplyr)
dt <- data.frame(age=rchisq(20,10), group=sample(1:2,20, rep=T))
group_by(dt, group) %>%
 summarise(mean=mean(age), sd=sd(age))

EDYCJA pełnego wykorzystania operatora rur:

library(dplyr)
data.frame(age=rchisq(20,10), group=sample(1:2,20, rep=T)) %>%
  group_by(group) %>%
  summarise(mean=mean(age), sd=sd(age))

3
+1 dla dplyr. Sprawiło, że tak wiele zadań R stało się prostych, a wiele z tych metod stało się przestarzałych.
gregmacfarlane

Niestety pełne wykorzystanie wersji operatora rur nie działa
dagcilibili,

załadowałeś dplyr lub magrittr?
Bastiaan Quast,

dziękuję bardzo @bquast za wskazanie rozwiązania, wywołano funkcję podsumowania, z której plyrzamiast dplyrpowodował problem.
dagcilibili

12

Świetnie, dzięki bastast za dodanie rozwiązania dplyr!

Okazuje się, że wtedy dplyr i data.table są bardzo blisko:

library(plyr)
library(dplyr)
library(data.table)
library(rbenchmark)

dtf <- data.frame(age=rchisq(100000,10),group=factor(sample(1:10,100000,rep=T)))
dt <- data.table(dtf)

setkey(dt,group)

a<-benchmark(ddply(dtf,~group,plyr:::summarise,mean=mean(age),sd=sd(age)),
         dt[,list(mean=mean(age),sd=sd(age)),by=group],
         group_by(dt, group) %>% summarise(mean=mean(age),sd=sd(age) ),
         group_by(dtf, group) %>% summarise(mean=mean(age),sd=sd(age) )
)

a[, c(1,3,4)]

data.table jest wciąż najszybsza, po niej bardzo dokładnie następuje dplyr (), co, co ciekawe, wydaje się szybsze na data.frame niż data.table:

                                                              test elapsed relative
1 ddply(dtf, ~group, plyr:::summarise, mean = mean(age), sd = sd(age))   1.689    4.867
2               dt[, list(mean = mean(age), sd = sd(age)), by = group]   0.347    1.000
4   group_by(dtf, group) %>% summarise(mean = mean(age), sd = sd(age))   0.369    1.063
3    group_by(dt, group) %>% summarise(mean = mean(age), sd = sd(age))   0.580    1.671

Na początku myślałem, że musisz przenieść setkey do benchmarku, ale okazuje się, że prawie nie zajmuje to wcale czasu.
kasterma

10

Oprócz istniejących sugestii możesz chcieć sprawdzić describe.byfunkcję w psychpakiecie.

Zapewnia szereg statystyk opisowych, w tym średnią i odchylenie standardowe na podstawie zmiennej grupującej.


jest miły, ale nieco trudny do wyeksportowania do LaTeX IME.
richiemorrisroe

10

Odkryłem, że funkcja summaryByw pakiecie doBy jest najwygodniejsza do tego:

library(doBy)

age    = c(23.0883, 25.8344, 29.4648, 32.7858, 33.6372,
           34.935,  35.2115, 35.2115,  5.2115, 36.7803)
group  = c(1, 1, 1, 2, 1, 1, 2, 2, 2, 1)
dframe = data.frame(age=age, group=group)

summaryBy(age~group, data=dframe, FUN=c(mean, sd))
# 
#   group age.mean    age.sd
# 1     1 30.62333  5.415439
# 2     2 27.10507 14.640441

9

Skorzystaj z sqldfpakietu. Dzięki temu możesz teraz używać SQL do podsumowywania danych. Po załadowaniu możesz napisać coś takiego -

sqldf('  select group,avg(age) from data group by group  ')

8

Edytowane: Zgodnie z sugestiami chl

Funkcja, której szukasz, nazywa się „tapply”, która stosuje funkcję na grupę określoną przez współczynnik.

# create some artificial data
set.seed(42)
groups <- 5

agedat <- c()
groupdat <- c()

for(group in 1:groups){
    agedat <- c(agedat,rnorm(100,mean=0 + group,1/group))
    groupdat <- c(groupdat,rep(group,100))
}
dat <- data.frame("age"=agedat,"group"=factor(groupdat))

# calculate mean and stdev age per group
res <- rbind.data.frame(group=1:5, with(dat, tapply(age, group, function(x) c(mean(x), sd(x)))))
names(res) <- paste("group",1:5)
row.names(res)[2:3] <- c("mean","sd")

Naprawdę proponuję przejść przez podstawowy samouczek R wyjaśniający wszystkie powszechnie stosowane struktury danych i metody. W przeciwnym razie utkniesz w każdym calu podczas programowania. Zobacz to pytanie, aby uzyskać zbiór bezpłatnych dostępnych zasobów.


2
@steffen +1, ale nie ma potrzeby tworzenia forpętli tutaj, możesz skonstruować wbudowaną ramkę danych, IMO. W przypadku tapplypołączenia użyj function(x) c(mean(x),sd(x)))i cbindwyniku, ponieważ PO poprosił o obie statystyki. Również ddplyz pakietu plyr można to zrobić płynnie.
chl

@steffen Problem polega na tym, że potrzebuję dokładnie takiej struktury tabeli, jaką opisałem. Nie ma problemu z uzyskaniem środków i SD. Problem dotyczy struktury.
Jurij Pietrowski

@chl: Dziękuję za komentarz, nie wiedziałem o plyr :). Dodałem cbind, ale resztę pozostawiłem nietkniętą. Niech jeszcze ktoś uzna, że ​​odpowiedź ta pozostanie mniej optymalnym przykładem.
steffen

@Yuriy: Dodano cbind. Jeśli już wiesz, jak zastosować funkcje dla grupy, możesz przeformułować swoje pytanie (tylko dla jasności;)).
steffen

@steffen cbind("mean"=mperage,"stdev"=stperage) gives no 'group' column. Will be joining by cbind (grupa = poziomy (czynnik (dane $ grupa)), „mean” = mperage, „stdev” = stperage) „prawda?
Jurij Pietrowski

7

Oto przykład z funkcją, aggregates()którą zrobiłem sobie jakiś czas temu:

# simulates data
set.seed(666)
( dat <- data.frame(group=gl(3,6), level=factor(rep(c("A","B","C"), 6)), 
                    y=round(rnorm(18,10),1)) )

> dat
   group level    y
1      1     A 10.8
2      1     B 12.0
3      1     C  9.6
4      1     A 12.0
5      1     B  7.8
6      1     C 10.8
7      2     A  8.7
8      2     B  9.2
9      2     C  8.2
10     2     A 10.0
11     2     B 12.2
12     2     C  8.2
13     3     A 10.9
14     3     B  8.3
15     3     C 10.1
16     3     A  9.9
17     3     B 10.9
18     3     C 10.3

# aggregates() function
aggregates <- function(formula, data=NULL, FUNS){ 
    if(class(FUNS)=="list"){ 
        f <- function(x) sapply(FUNS, function(fun) fun(x)) 
    }else{f <- FUNS} 
    temp <- aggregate(formula, data, f) 
    out <- data.frame(temp[,-ncol(temp)], temp[,ncol(temp)]) 
    colnames(out)[1] <- colnames(temp)[1] 
return(out) 
} 

# example 
FUNS <- function(x) c(mean=round(mean(x),0), sd=round(sd(x), 0)) 
( ag <- aggregates(y~group:level, data=dat, FUNS=FUNS) ) 

Daje następujący wynik:

> ag
  group level mean sd
1     1     A   11  1
2     2     A    9  1
3     3     A   10  1
4     1     B   10  3
5     2     B   11  2
6     3     B   10  2
7     1     C   10  1
8     2     C    8  0
9     3     C   10  0

Może możesz uzyskać ten sam wynik, zaczynając od funkcji R split ():

> with(dat, sapply( split(y, group:level), FUNS ) )
     1:A 1:B 1:C 2:A 2:B 2:C 3:A 3:B 3:C
mean  11  10  10   9  11   8  10  10  10
sd     1   3   1   1   2   0   1   2   0

Wróćmy do wyjścia aggregatesfunkcji. Można przekształcić go w pięknym tabeli przy użyciu reshape(), xtabs()i ftable():

rag <- reshape(ag, varying=list(3:4), direction="long", v.names="y") 
rag$time <- factor(rag$time) 
ft <- ftable(xtabs(y~group+level+time, data=rag)) 
attributes(ft)$col.vars <- list(c("mean","sd")) 

To daje:

> ft 
             mean sd
group level         
1     A        11  1
      B        10  3
      C        10  1
2     A         9  1
      B        11  2
      C         8  0
3     A        10  1
      B        10  2
      C        10  0

Piękne, prawda? Możesz wyeksportować tę tabelę do pliku pdf z textplot()funkcją gplotspakietu.

Zobacz tutaj rozwiązania innych osób.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.