Bagaimana cara meringkas data berdasarkan grup dalam R? [Tutup]


181

Saya memiliki bingkai data R seperti ini:

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

Saya perlu mendapatkan bingkai data dalam formulir berikut:

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

Nomor grup dapat bervariasi, tetapi nama dan jumlahnya dapat diperoleh dengan menelepon levels(factor(data$group))

Manipulasi apa yang harus dilakukan dengan data untuk mendapatkan hasilnya?


koma dalam bingkai data hasil berarti sesuatu yang istimewa, atau hanya titik desimal?
mpiktas

@mpikta Terima kasih telah mencatat. Dikoreksi. Ini adalah masalah lokal (saya orang Rusia) - kami menggunakan koma untuk pemisahan desimal.
Yuriy Petrovskiy

3
Saya curiga. Semua Eropa menggunakan koma kecuali Inggris.
mpiktas

4
Meskipun bukan orang Inggris, saya lebih suka titik untuk pemisah desimal.
Roman Luštrik

1
Lihat aggregate,, tapplylalu stackoverflow.com untuk pertanyaan koding selanjutnya dari jenis ini.
conjugateprior

Jawaban:


140

Berikut adalah varian satu baris plyr menggunakan 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))

Berikut ini varian satu baris lainnya menggunakan data.table paket baru .

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]

Yang ini lebih cepat, meskipun ini hanya terlihat di atas meja dengan 100 ribu baris. Pengaturan waktu pada Macbook Pro saya dengan prosesor 2,53 Ghz Core 2 Duo dan 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 

Penghematan lebih lanjut dimungkinkan jika kita menggunakan 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, itu memberi saya kesempatan untuk mencoba paket data.table baru ini . Terlihat sangat menjanjikan.
mpiktas

7
+6000 untuk data.table. Ini benar-benar jauh lebih cepat daripada ddply, bahkan bagi saya pada dataset lebih kecil dari 100k (saya punya satu dengan hanya 20k baris). Pasti ada hubungannya dengan fungsi yang saya terapkan, tetapi ddply akan memakan waktu beberapa menit dan data. Tabel beberapa detik.
atomic

Kesalahan ketik sederhana: Saya pikir maksud Anda dt <- data.table(dtf)alih-alih dt <- data.table(dt)di blok kode kedua. Dengan begitu, Anda membuat tabel data dari bingkai data alih-alih dari dtfungsi dari statspaket. Saya mencoba mengeditnya, tetapi saya tidak dapat melakukan pengeditan di bawah enam karakter.
Christopher Bottoms

Menurut saya (tidak rendah hati dalam hal ini) pendapat data.tableadalah cara terbaik untuk mengumpulkan data dan jawaban ini bagus, tetapi masih hanya menggores permukaan. Selain unggul secara sintaksis, ini juga sangat fleksibel dan memiliki banyak fitur canggih yang melibatkan sambungan dan mekanisme internal. Lihat FAQ, halaman github, atau kursus untuk info lebih lanjut.
geneorama

98

Salah satu kemungkinan adalah menggunakan fungsi agregat . Misalnya,

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

memberi Anda kolom kedua dari hasil yang diinginkan.


1
Jangan menautkan ke server bantuan lokal Anda :-) +1 tetapi lihat komentar saya untuk tanggapan @ steffen.
chl

Selesai dengan menelepon 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))tetapi saya tidak yakin itu adalah cara yang benar. Saya tidak yakin apa yang akan terjadi maka hasil kolom yang diikat akan berada dalam urutan yang berbeda (saya pikir itu mungkin). Apa pendapat Anda?
Yuriy Petrovskiy

9
@ Yuriy Baris tidak boleh rusak, tapi di sini ada satu cara untuk melakukannya aggregate():aggregate(age ~ group, data=dat, FUN = function(x) c(M=mean(x), SD=sd(x)))
lockoff

@lockedoff: Terima kasih telah menyelesaikan jawaban saya!
ocram

27

Karena Anda memanipulasi bingkai data, dplyrpaket mungkin merupakan cara tercepat untuk melakukannya.

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

atau setara, menggunakan operator dplyr/ magrittrpipa:

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

EDIT penggunaan penuh operator pipa:

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 untuk dplyr. Ini telah membuat begitu banyak tugas R sederhana dan banyak dari metode ini menjadi usang.
gregmacfarlane

Penggunaan penuh versi operator pipa sayangnya tidak bekerja untuk saya
dagcilibili

apakah Anda memuat dplyr atau magrittr?
Bastiaan Quast

terima kasih banyak @bquast untuk menunjukkan ke arah solusi, meringkas fungsi dipanggil dari plyrbukan dplyryang menyebabkan masalah.
dagcilibili

12

Hebat, terima kasih bquast untuk menambahkan solusi dplyr!

Ternyata itu, dplyr dan data.table sangat dekat:

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 masih yang tercepat, diikuti dengan sangat erat oleh dplyr (), yang menariknya tampak lebih cepat pada data.frame daripada 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

Awalnya saya pikir Anda perlu memindahkan setkey ke benchmark, tetapi ternyata itu hampir tidak membutuhkan waktu sama sekali.
kasterma

10

Selain saran yang ada, Anda mungkin ingin memeriksa describe.byfungsi dalam psychpaket.

Ini menyediakan sejumlah statistik deskriptif termasuk rata-rata dan standar deviasi berdasarkan variabel pengelompokan.


itu bagus, tapi agak sulit untuk mengekspor ke LaTeX IME.
richiemorrisroe

10

Saya telah menemukan fungsi summaryBydalam paket doBy menjadi yang paling nyaman untuk ini:

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

Gunakan sqldfpaket. Ini memungkinkan Anda sekarang menggunakan SQL untuk meringkas data. Setelah Anda memuatnya, Anda dapat menulis sesuatu seperti -

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

8

Diedit: Sesuai dengan saran chl

Fungsi yang Anda cari disebut "tapply" yang menerapkan fungsi per grup yang ditentukan oleh faktor.

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

Saya benar-benar menyarankan untuk bekerja melalui tutorial R dasar yang menjelaskan semua datastructures dan metode yang umum digunakan. Kalau tidak, Anda akan terjebak setiap inci selama pemrograman. Lihat pertanyaan ini untuk koleksi sumber daya gratis yang tersedia.


2
@steffen +1 tetapi tidak perlu untuk forloop di sini, Anda dapat membuat inframe dataframe Anda, IMO. Untuk tapplypanggilan, gunakan function(x) c(mean(x),sd(x)))dan cbindhasilnya sebagai OP meminta kedua statistik. Juga, ddplydari paket plyr dapat melakukan ini dengan lancar.
chl

@steffen Masalahnya adalah saya membutuhkan struktur tabel yang saya jelaskan. Tidak ada masalah dengan cara mendapatkan dan sd. Masalahnya adalah dengan stucture.
Yuriy Petrovskiy

@chl: Terima kasih atas komentar Anda, tidak tahu tentang plyr :). Saya menambahkan cbind, tetapi sisanya tidak tersentuh. Semoga orang lain menerima pujian, jawaban ini akan tetap menjadi contoh yang kurang optimal.
steffen

@ Yuriy: Menambahkan cbind. Jika Anda sudah tahu cara menerapkan fungsi per grup, Anda dapat merumuskan kembali pertanyaan Anda (hanya untuk kejelasan;)).
steffen

@steffen cbind("mean"=mperage,"stdev"=stperage) gives no 'group' column. Will be joining by cbind (grup = level (faktor (data $ grup)), "berarti" = mperage, "stdev" = stperage) `benar?
Yuriy Petrovskiy

7

Berikut adalah contoh dengan fungsi yang aggregates()saya lakukan sendiri beberapa waktu lalu:

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

Ini memberikan hasil sebagai berikut:

> 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

Mungkin Anda bisa mendapatkan hasil yang sama mulai dari fungsi 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

Biarkan saya kembali ke output aggregatesfungsi. Anda bisa mengubahnya dalam tabel yang indah menggunakan reshape(), xtabs()dan 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")) 

Ini memberi:

> 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

Cantik bukan? Anda dapat mengekspor tabel ini ke pdf dengan textplot()fungsi gplotspaket.

Lihat di sini untuk solusi orang lain.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.