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?

Yuriy Petrovskiy
sumber
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 
mpiktas
sumber
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.

okram
sumber
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))
Bastiaan Quast
sumber
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
Matifou
sumber
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.

Jeromy Anglim
sumber
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
gung
sumber
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  ')
KalEl
sumber
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.

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

Stéphane Laurent
sumber