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?

Jurij Pietrowski
źródło
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 
mpiktas
źródło
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.

ocram
źródło
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))
Bastiaan Quast
źródło
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
Matifou
źródło
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.

Jeromy Anglim
źródło
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
gung
źródło
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  ')
KalEl
źródło
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.

steffen
źródło
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.

Stéphane Laurent
źródło