Wie fasse ich Daten nach Gruppen in R zusammen? [geschlossen]

181

Ich habe R Datenrahmen wie folgt:

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

Ich muss den Datenrahmen in der folgenden Form erhalten:

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

Die Gruppennummer kann variieren, aber ihre Namen und Anzahl können durch einen Anruf erhalten werden levels(factor(data$group))

Welche Manipulationen sollten mit den Daten durchgeführt werden, um das Ergebnis zu erhalten?

Yuriy Petrovskiy
quelle
Die Kommas im Ergebnisdatenrahmen bedeuten etwas Besonderes, oder ist es nur der Dezimalpunkt?
mpiktas
@mpiktas Vielen Dank für die Kenntnisnahme. Korrigiert Dies waren Probleme mit der Ländereinstellung (ich bin Russe) - wir verwenden Kommas für die Dezimaltrennung.
Yuriy Petrovskiy
3
Ich ahnte das. In ganz Europa wird Komma verwendet, mit Ausnahme der Briten.
mpiktas
4
Obwohl ich kein Brite bin, bevorzuge ich Punkt als Dezimaltrennzeichen.
Roman Luštrik
1
Sehen Sie aggregate, tapplyund dann stackoverflow.com für nachfolgende Codierung Fragen dieser Art.
Conjugateprior

Antworten:

140

Hier ist die plyr eine Zeile Variante 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))

Hier ist eine weitere einzeilige Variante mit der neuen Paketdatei 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]

Dieser ist schneller, obwohl dies nur bei Tabellen mit 100.000 Zeilen auffällt. Timings auf meinem Macbook Pro mit 2.53 Ghz Core 2 Duo Prozessor und 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 

Weitere Einsparungen sind möglich, wenn wir verwenden 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
quelle
2
@chl, es gab mir die Möglichkeit, dieses neue data.table- Paket auszuprobieren . Es sieht sehr vielversprechend aus.
mpiktas
7
+6000 für Datentabelle. Es ist wirklich so viel schneller als ddply, auch für mich auf Datensätzen kleiner als 100k (ich habe eine mit nur 20k Zeilen). Muss etwas mit den Funktionen zu tun haben, die ich anwende, wird aber ein paar Sekunden dauern.
Atomicules
Einfacher Tippfehler: Ich denke du meintest dt <- data.table(dtf)statt dt <- data.table(dt)im zweiten Codeblock. Auf diese Weise erstellen Sie die Datentabelle aus einem Datenrahmen anstelle der dtFunktion aus dem statsPaket. Ich habe versucht, es zu bearbeiten, aber ich kann keine Änderungen unter sechs Zeichen vornehmen.
Christopher Bottoms
Meiner Meinung nach (in diesem Fall nicht bescheiden) data.tableist der beste Weg, Daten zu aggregieren, und diese Antwort ist großartig, kratzt aber immer noch nur die Oberfläche. Es ist nicht nur syntaktisch überlegen, sondern auch äußerst flexibel und verfügt über viele erweiterte Funktionen, die Verknüpfungen und interne Mechanismen umfassen. Weitere Informationen finden Sie in den FAQ, auf der Github-Seite oder im Kurs.
Genorama
97

Eine Möglichkeit ist die Verwendung der Aggregatfunktion . Zum Beispiel,

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

gibt Ihnen die zweite Spalte des gewünschten Ergebnisses.

Ocram
quelle
1
Verlinken Sie nicht auf Ihren lokalen Hilfeserver :-) +1, sondern lesen Sie meine Kommentare zu @ steffens Antwort.
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))Ich habe die Sache telefonisch erledigt , aber ich bin nicht sicher, ob es der richtige Weg ist. Ich bin nicht sicher, was passieren wird, wenn die Ergebnisse der gebundenen Spalten in einer anderen Reihenfolge vorliegen (ich denke, es ist möglich). Was ist Ihre Meinung?
Yuriy Petrovskiy
9
@Yuriy Die Zeilen sollten nicht außer Betrieb sein, aber hier ist eine Möglichkeit, dies mit einem Aufruf zu tun aggregate():aggregate(age ~ group, data=dat, FUN = function(x) c(M=mean(x), SD=sd(x)))
14.
@lockedoff: Danke, dass du meine Antwort abgeschlossen hast!
15.
27

Da Sie einen Datenrahmen bearbeiten, ist das dplyrPaket wahrscheinlich der schnellste Weg, dies zu tun.

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

oder gleichwertig mit dem Operator 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))

BEARBEITEN Sie die vollständige Verwendung des Pipe-Operators:

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
quelle
3
+1 für dplyr. Es hat so viele R-Aufgaben einfach und viele dieser Methoden überholt.
Gregmacfarlane
Die vollständige Nutzung der Pipe-Operator-Version funktioniert bei mir leider nicht
dagcilibili
hast du dplyr oder magrittr geladen?
Bastiaan Quast
Vielen Dank @bquast, dass Sie auf die Lösung hingewiesen haben. Die Funktion "Summieren" wurde aufgerufen, plyranstatt dplyrdas Problem zu verursachen.
Dagcilibili
12

Toll, danke bquast für das Hinzufügen der Dplyr-Lösung!

Es stellt sich heraus, dass dplyr und data.table sehr nahe beieinander liegen:

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 ist immer noch die schnellste, gefolgt von dplyr (), die auf dem data.frame interessanterweise schneller zu sein scheint als die 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
quelle
Zuerst dachte ich, Sie müssten setkey in den Benchmark verschieben, aber es stellt sich heraus, dass dies fast keine Zeit in Anspruch nimmt.
Kasterma
10

Zusätzlich zu vorhandenen Vorschlägen möchten Sie möglicherweise die describe.byFunktion im psychPaket überprüfen .

Es bietet eine Reihe von beschreibenden Statistiken, einschließlich des Mittelwerts und der Standardabweichung basierend auf einer Gruppierungsvariablen.

Jeromy Anglim
quelle
Es ist nett, aber etwas schwierig, nach LaTeX IME zu exportieren.
Richiemorrisroe
10

Ich habe festgestellt, dass die Funktion summaryByim doBy-Paket hierfür am bequemsten ist:

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
quelle
9

Verwenden Sie das sqldfPaket. Auf diese Weise können Sie jetzt SQL verwenden, um die Daten zusammenzufassen. Sobald Sie es geladen haben, können Sie Folgendes schreiben:

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

Bearbeitet: Nach den Vorschlägen von chl

Die gesuchte Funktion heißt "tapply" und wendet eine Funktion pro Gruppe an, die durch einen Faktor angegeben wird.

# 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")

Ich empfehle dringend, ein grundlegendes R-Tutorial durchzuarbeiten, in dem alle gängigen Datenstrukturen und Methoden erläutert werden. Andernfalls stecken Sie bei der Programmierung jeden Zentimeter fest. In dieser Frage finden Sie eine Sammlung frei verfügbarer Ressourcen.

steffen
quelle
2
@steffen +1, aber hier ist keine forSchleife erforderlich. Sie können Ihren Datenrahmen inline konstruieren, IMO. tapplyVerwenden Sie für den Aufruf function(x) c(mean(x),sd(x)))und cbinddas Ergebnis als OP, um beide Statistiken anzufordern . Auch ddplyaus dem Plyr- Paket könnte dies reibungslos funktionieren.
Chl
@steffen Das Problem ist, dass ich genau die Tabellenstruktur brauche, die ich beschrieben habe. Es ist kein Problem, Mittel und sd zu bekommen. Das Problem liegt in der Struktur.
Yuriy Petrovskiy
@chl: Vielen Dank für Ihren Kommentar, wusste nicht über Plyr :). Ich fügte cbind hinzu, ließ aber den Rest unberührt. Möge sich ein anderer die Ehre machen, so bleibt diese Antwort ein weniger optimales Beispiel.
Steffen
@Yuriy: cbind hinzugefügt. Wenn Sie bereits gewusst haben, wie man Funktionen pro Gruppe anwendet, können Sie Ihre Frage neu formulieren (nur aus Gründen der Klarheit;)).
Steffen
@steffen cbind("mean"=mperage,"stdev"=stperage) gives no 'group' column. Will be joining by cbind (group = levels (factor (data $ group)), "mean" = mperage, "stdev" = stperage) `richtig?
Yuriy Petrovskiy
7

Hier ist ein Beispiel mit der Funktion, aggregates()die ich vor einiger Zeit selbst gemacht habe:

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

Es ergibt sich folgendes Ergebnis:

> 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

Vielleicht können Sie das gleiche Ergebnis ausgehend von der R-Funktion split () erhalten:

> 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

Lassen Sie mich noch einmal auf die Ausgabe der aggregatesFunktion zurückkommen . Sie können es in einem schönen Tisch - Transformation reshape(), xtabs()und 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")) 

Das gibt:

> 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

Schön, nicht wahr? Sie können diese Tabelle mit der textplot()Funktion des gplotsPakets in ein PDF exportieren .

Sehen Sie hier für andere Lösungen.

Stéphane Laurent
quelle