programing

그룹별 평균 계산

stoneblock 2023. 10. 21. 09:54

그룹별 평균 계산

다음과 유사한 대형 데이터 프레임이 있습니다.

df <- data.frame(dive = factor(sample(c("dive1","dive2"), 10, replace=TRUE)),
                 speed = runif(10)
                 )
> df
    dive      speed
1  dive1 0.80668490
2  dive1 0.53349584
3  dive2 0.07571784
4  dive2 0.39518628
5  dive1 0.84557955
6  dive1 0.69121443
7  dive1 0.38124950
8  dive2 0.22536126
9  dive1 0.04704750
10 dive2 0.93561651

나의 목표는 다른 열이 어떤 값과 같을 때 한 열에 있는 값들의 평균을 구하고 모든 값에 대해 이를 반복하는 것입니다. 즉 위의 예에서 나는 열에 대한 평균을 반환하고 싶습니다.speed열의 모든 고유 값에 대해dive. 그래서 언제dive==dive1, 에 대한 평균speed이것은 각각의 가치에 대한 것입니까?dive.

R에서 이것을 하는 방법은 여러 가지가 있습니다.구체적으로.by,aggregate,split,그리고.plyr,cast,tapply,data.table,dplyr

대체로, 이러한 문제는 분할 적용 결합 형태입니다.해들리 위컴은 문제의 모든 범주에 대해 더 깊은 통찰력을 줄 아름다운 을 썼으며, 이 글은 읽을 가치가 있습니다.plyr패키지는 일반적인 데이터 구조에 대한 전략을 구현합니다.dplyr는 데이터 프레임에 맞게 조정된 최신 구현 성능입니다.이들은 같은 형태이지만 이 문제보다 훨씬 더 복잡한 문제를 해결할 수 있습니다.데이터 조작 문제를 해결하는 일반적인 도구로서 충분히 배울 가치가 있습니다.

성능은 매우 큰 데이터셋에서 문제가 되며, 이 때문에 다음을 기반으로 한 솔루션을 능가하기가 어렵습니다.data.table. 그러나 중간 규모 이하의 데이터셋만 다루면 시간을 들여 학습할 수 있습니다.data.table노력할 가치가 없는 것 같습니다dplyr또한 속도가 빠를 수 있으므로 작업 속도를 높이고 싶지만 확장성이 필요하지 않은 경우에는 좋은 선택입니다.data.table.

아래의 다른 솔루션들 중 대부분은 추가 패키지를 필요로 하지 않습니다.그 중 일부는 중 대규모 데이터 세트에서도 상당히 빠릅니다.그들의 주된 단점은 은유 혹은 유연성 중 하나입니다.은유적으로 말하면, 저는 이 특정 유형의 문제를 '재치 있는' 방법으로 해결하기 위해 다른 것을 강요하기 위해 고안된 도구라는 것을 의미합니다.유연성이 있다면, 이들은 유사한 문제를 광범위하게 해결하거나 깔끔한 결과물을 쉽게 만들어낼 수 있는 능력이 부족하다는 것을 의미합니다.


base기능들

tapply:

tapply(df$speed, df$dive, mean)
#     dive1     dive2 
# 0.5419921 0.5103974

aggregate:

aggregatedata.frame을 가져오고 data.frame을 출력하며 공식 인터페이스를 사용합니다.

aggregate( speed ~ dive, df, mean )
#    dive     speed
# 1 dive1 0.5790946
# 2 dive2 0.4864489

by:

가장 사용자 친화적인 형태로 벡터를 받아들여 함수를 적용합니다.그러나 출력은 조작 가능한 형태가 아닙니다.

res.by <- by(df$speed, df$dive, mean)
res.by
# df$dive: dive1
# [1] 0.5790946
# ---------------------------------------
# df$dive: dive2
# [1] 0.4864489

이 문제를 해결하기 위해, 간단한 사용을 위해byas.data.frame메소드 인 더taRifx라이브러리 작업:

library(taRifx)
as.data.frame(res.by)
#    IDX1     value
# 1 dive1 0.6736807
# 2 dive2 0.4051447

split:

이름에서 알 수 있듯이 분할 적용 결합 전략의 "분할" 부분만 수행합니다.나머지가 잘 될 수 있도록 다음과 같은 작은 함수를 쓰겠습니다.sapplycombine용으로sapply결과를 최대한 자동으로 단순화합니다.우리의 경우, 결과가 1차원 밖에 없기 때문에, 그것은 data.frame이 아닌 벡터를 의미합니다.

splitmean <- function(df) {
  s <- split( df, df$dive)
  sapply( s, function(x) mean(x$speed) )
}
splitmean(df)
#     dive1     dive2 
# 0.5790946 0.4864489 

외부 패키지

data.table:

library(data.table)
setDT(df)[ , .(mean_speed = mean(speed)), by = dive]
#    dive mean_speed
# 1: dive1  0.5419921
# 2: dive2  0.5103974

dplyr:

library(dplyr)
group_by(df, dive) %>% summarize(m = mean(speed))

(의 cursor 전에dplyr)

여기 공식 페이지에서 말하는 내용이 있습니다.plyr:

이것은 이미 가능합니다.baseR수크)와 )split그리고.apply 함수군),,plyr다음을 통해 모든 것을 좀 더 쉽게 해줍니다.

  • 완전히 일관된 이름, 인수 및 결과
  • 를 통한 편리한 병렬화foreach꾸러미
  • 데이터에 입력 및 출력.프레임, 행렬 및 목록
  • 장기간 실행 중인 작업을 추적하는 프로그레스 막대
  • 기본 제공 오류 복구 및 유용한 오류 메시지
  • 모든 변환에 걸쳐 유지되는 레이블

다시 말해, 분할 적용-결합 조작을 위한 하나의 도구를 배운다면 다음과 같이 해야 합니다.plyr.

library(plyr)
res.plyr <- ddply( df, .(dive), function(x) mean(x$speed) )
res.plyr
#    dive        V1
# 1 dive1 0.5790946
# 2 dive2 0.4864489

모양 변경2:

reshape2라이브러리는 분할 apply-combine를 주된 초점으로 하여 설계되지 않았습니다.대신, 2부 멜트/캐스트 전략을 사용하여 다양한 데이터 재구성 작업을 수행합니다.그러나 집계 기능을 허용하기 때문에 이 문제에 사용할 수 있습니다.분할 적용-결합 작업의 첫 번째 선택은 아니지만 재구성 기능이 강력하므로 이 패키지도 학습해야 합니다.

library(reshape2)
dcast( melt(df), variable ~ dive, mean)
# Using dive as id variables
#   variable     dive1     dive2
# 1    speed 0.5790946 0.4864489

벤치마크

열개의 행, 두 그룹

library(microbenchmark)
m1 <- microbenchmark(
  by( df$speed, df$dive, mean),
  aggregate( speed ~ dive, df, mean ),
  splitmean(df),
  ddply( df, .(dive), function(x) mean(x$speed) ),
  dcast( melt(df), variable ~ dive, mean),
  dt[, mean(speed), by = dive],
  summarize( group_by(df, dive), m = mean(speed) ),
  summarize( group_by(dt, dive), m = mean(speed) )
)

> print(m1, signif = 3)
Unit: microseconds
                                           expr  min   lq   mean median   uq  max neval      cld
                    by(df$speed, df$dive, mean)  302  325  343.9    342  362  396   100  b      
              aggregate(speed ~ dive, df, mean)  904  966 1012.1   1020 1060 1130   100     e   
                                  splitmean(df)  191  206  249.9    220  232 1670   100 a       
  ddply(df, .(dive), function(x) mean(x$speed)) 1220 1310 1358.1   1340 1380 2740   100      f  
         dcast(melt(df), variable ~ dive, mean) 2150 2330 2440.7   2430 2490 4010   100        h
                   dt[, mean(speed), by = dive]  599  629  667.1    659  704  771   100   c     
 summarize(group_by(df, dive), m = mean(speed))  663  710  774.6    744  782 2140   100    d    
 summarize(group_by(dt, dive), m = mean(speed)) 1860 1960 2051.0   2020 2090 3430   100       g 

autoplot(m1)

benchmark 10 rows

평소처럼.data.table오버헤드가 조금 더 크기 때문에 소규모 데이터셋의 경우 평균적으로 제공됩니다.그러나 이들은 마이크로초이므로 차이점은 사소한 것입니다.여기서는 모든 접근 방식이 정상적으로 작동하며, 다음을 기준으로 선택해야 합니다.

  • 이미 잘 알고 있거나 알고 싶은 것 (plyr유연성에 대해서는 항상 배울 가치가 있습니다.data.table거대한 데이터셋을 분석할 계획이라면 배울 가치가 있습니다.by그리고.aggregate그리고.split모든 기본 R 함수이므로 보편적으로 사용할 수 있습니다.
  • 반환하는 출력(숫자, data.frame 또는 data.table -- 후자가 data.frame에서 상속됨)

천만 줄, 열 그룹

하지만 우리가 큰 데이터 세트를 가지고 있다면 어떨까요?10개의 그룹으로 나누어진 10^7개의 열을 시도해 보겠습니다.

df <- data.frame(dive=factor(sample(letters[1:10],10^7,replace=TRUE)),speed=runif(10^7))
dt <- data.table(df)
setkey(dt,dive)

m2 <- microbenchmark(
  by( df$speed, df$dive, mean),
  aggregate( speed ~ dive, df, mean ),
  splitmean(df),
  ddply( df, .(dive), function(x) mean(x$speed) ),
  dcast( melt(df), variable ~ dive, mean),
  dt[,mean(speed),by=dive],
  times=2
)

> print(m2, signif = 3)
Unit: milliseconds
                                           expr   min    lq    mean median    uq   max neval      cld
                    by(df$speed, df$dive, mean)   720   770   799.1    791   816   958   100    d    
              aggregate(speed ~ dive, df, mean) 10900 11000 11027.0  11000 11100 11300   100        h
                                  splitmean(df)   974  1040  1074.1   1060  1100  1280   100     e   
  ddply(df, .(dive), function(x) mean(x$speed))  1050  1080  1110.4   1100  1130  1260   100      f  
         dcast(melt(df), variable ~ dive, mean)  2360  2450  2492.8   2490  2520  2620   100       g 
                   dt[, mean(speed), by = dive]   119   120   126.2    120   122   212   100 a       
 summarize(group_by(df, dive), m = mean(speed))   517   521   531.0    522   532   620   100   c     
 summarize(group_by(dt, dive), m = mean(speed))   154   155   174.0    156   189   321   100  b      

autoplot(m2)

benchmark 1e7 rows, 10 groups

그리고나서data.table아니면dplyr조작을 사용하여data.tables는 분명히 가야할 길입니다.특정 접근 방식(aggregate그리고.dcast매우 느리게 보이기 시작했습니다.

천만 줄, 천 그룹

더 많은 그룹이 있으면 차이가 더 두드러집니다.1,000개의 그룹과 동일한 10^7개의 행이 있는 경우:

df <- data.frame(dive=factor(sample(seq(1000),10^7,replace=TRUE)),speed=runif(10^7))
dt <- data.table(df)
setkey(dt,dive)

# then run the same microbenchmark as above
print(m3, signif = 3)
Unit: milliseconds
                                           expr   min    lq    mean median    uq   max neval    cld
                    by(df$speed, df$dive, mean)   776   791   816.2    810   828   925   100  b    
              aggregate(speed ~ dive, df, mean) 11200 11400 11460.2  11400 11500 12000   100      f
                                  splitmean(df)  5940  6450  7562.4   7470  8370 11200   100     e 
  ddply(df, .(dive), function(x) mean(x$speed))  1220  1250  1279.1   1280  1300  1440   100   c   
         dcast(melt(df), variable ~ dive, mean)  2110  2190  2267.8   2250  2290  2750   100    d  
                   dt[, mean(speed), by = dive]   110   111   113.5    111   113   143   100 a     
 summarize(group_by(df, dive), m = mean(speed))   625   630   637.1    633   644   701   100  b    
 summarize(group_by(dt, dive), m = mean(speed))   129   130   137.3    131   142   213   100 a     

autoplot(m3)

enter image description here

그렇게data.table계속해서 잘 확장되고 있습니다.dplyr작동하는data.table또한 잘 작동합니다.dplyr위에data.frame거의 10배 가까이 더 느린 속도입니다.split/sapply전략은 그룹의 수에서 형편없이 확장되는 것처럼 보입니다(즉,split()느릴 가능성이 높고 그리고sapply빠름).by5초면 사용자에게 확실히 눈에 띄지만 이 정도 크기의 데이터셋의 경우에는 여전히 합리적이지 않습니다.하지만 이 정도 크기의 데이터셋을 일상적으로 사용한다면data.table100% data.table을 통해 최고의 성능을 발휘할 수 있습니다.dplyr와 함께dplyrdata.table실행 가능한 대안으로

dplyr를 사용한 2015 업데이트:

df %>% group_by(dive) %>% summarise(percentage = mean(speed))
Source: local data frame [2 x 2]

   dive percentage
1 dive1  0.4777462
2 dive2  0.6726483
aggregate(speed~dive,data=df,FUN=mean)
   dive     speed
1 dive1 0.7059729
2 dive2 0.5473777

다양한 경우에도 빠르게 유지되는 대체 베이스 R 접근법을 추가합니다.

rowsummean <- function(df) {
  rowsum(df$speed, df$dive) / tabulate(df$dive)
}

@Ari에서 벤치마크를 빌리는 중:

열개의 행, 두 그룹

res1

천만 줄, 열 그룹

res2

1000만 행, 1000개

res3

새로운 기능으로across:

df %>% 
  group_by(dive) %>% 
  summarise(across(speed, mean, na.rm = TRUE))

우리는 이미 그룹별로 비열한 선택을 할 수 있는 수많은 옵션을 가지고 있고, 여기에 하나를 더 추가했습니다.mosaic꾸러미의

mosaic::mean(speed~dive, data = df)
#dive1 dive2 
#0.579 0.440 

이것은 명명된 숫자 벡터를 반환합니다. 필요한 경우 데이터 프레임으로 감쌀 수 있습니다.stack

stack(mosaic::mean(speed~dive, data = df))

#  values   ind
#1  0.579 dive1
#2  0.440 dive2

데이터.

set.seed(123)
df <- data.frame(dive=factor(sample(c("dive1","dive2"),10,replace=TRUE)),
                 speed=runif(10))

으로.collapse

library(collapse)
library(magrittr)
df %>% 
   fgroup_by(dive) %>%
   fsummarise(speed = fmean(speed))
#   dive     speed
#1 dive1 0.5788479
#2 dive2 0.4401514

데이터.

set.seed(123)
df <- data.frame(dive=factor(sample(c("dive1","dive2"),10,replace=TRUE)),
             speed=runif(10))

RCchelsie에서 제공하는 답변 확장 - 평균을 구하려면 데이터 프레임의 모든 열에 대해 그룹별로 계산합니다.

df %>% 
  group_by(dive) %>% 
  summarise(across(.cols=everything(), mean, na.rm=TRUE))

1.1.0(그리고 위)를 사용하여 일시적으로 그룹화할 수 있습니다..by논쟁.

이렇게 하면 코드가 짧아집니다(피합니다).group_by그리고.ungroup및문)를 포함합니다..by그룹화되지 않은 데이터 프레임을 항상 반환합니다.

library(dplyr)

df %>% summarise(speed = mean(speed), .by = dive)

#   dive     speed
#1 dive1 0.5788479
#2 dive2 0.4401514

와 함께timeplyr케이크도 먹고 먹을 수 있습니다.

케이크로 말입니다.tidy구문과 그것을 먹는다는 것은 케이크가 엄청 빠르다는 것을 의미합니다.

stat_summarise()혼합하여 사용합니다.collapse,data.table그리고.dplyr희생하지 않고 최적의 성능을 발휘합니다.tidy통사론

가장 효율적인 방법은 다음과 같습니다.data.table타임플라이어와 동등한 속도를 볼 수 있습니다.

# remotes::install_github("NicChr/timeplyr")
library(plyr)
library(dplyr)
library(ggplot2)
library(timeplyr)
library(data.table)

dt <- data.table(dive = factor(sample.int(10^6, size = 10^7, replace=TRUE)),
                 speed = runif(10^7))
setkey(dt, dive)

m2 <- microbenchmark::microbenchmark(
  dt[,mean(speed),by=dive],
  stat_summarise(dt, .cols = "speed", 
                 .by = dive, stat = "mean", sort = F),
  times = 15
)

print(m2, signif = 3)
#> Unit: milliseconds
#>                                                                           expr
#>                                                   dt[, mean(speed), by = dive]
#>  stat_summarise(dt, .cols = "speed", .by = dive, stat = "mean",      sort = F)
#>  min  lq mean median  uq max neval cld
#>  148 184  272    261 344 499    15   a
#>  139 197  283    221 328 540    15   a
autoplot(m2)
#> Coordinate system already present. Adding new coordinate system, which will
#> replace the existing one.

천만 행, ~ 백만 그룹

메모리 사용량을 비교할 때,stat_summarise()실제로 보다 훨씬 더 효율적입니다.data.table.

# Memory comparison
bench::mark(
  DT = dt[, list(speed = mean(speed)), by = dive],
  TP = stat_summarise(dt, .cols = "speed", 
                 .by = dive, stat = "mean", sort = F),
  check = FALSE
)
#> Warning: Some expressions had a GC in every iteration; so filtering is disabled.
#> # A tibble: 2 x 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 DT            194ms    308ms      3.25     248MB     4.87
#> 2 TP            133ms    277ms      3.61    68.7MB     1.81

2023-05-19에 repex v2.0.2로 생성됨

언급URL : https://stackoverflow.com/questions/11562656/calculate-the-mean-by-group