R에서 data.frame을 병합 / 결합하는 가장 빠른 방법은 무엇입니까?


97

예 (대부분의 대표적인 예인지 확실하지 않음) :

N <- 1e6
d1 <- data.frame(x=sample(N,N), y1=rnorm(N))
d2 <- data.frame(x=sample(N,N), y2=rnorm(N))

이것이 내가 지금까지 얻은 것입니다.

d <- merge(d1,d2)
# 7.6 sec

library(plyr)
d <- join(d1,d2)
# 2.9 sec

library(data.table)
dt1 <- data.table(d1, key="x")
dt2 <- data.table(d2, key="x")
d <- data.frame( dt1[dt2,list(x,y1,y2=dt2$y2)] )
# 4.9 sec

library(sqldf)
sqldf()
sqldf("create index ix1 on d1(x)")
sqldf("create index ix2 on d2(x)")
d <- sqldf("select * from d1 inner join d2 on d1.x=d2.x")
sqldf()
# 17.4 sec

sqldf 방식을 수행하는 적절한 방법은 Gabor가 아래에서 지적합니다. 하나의 인덱스 (예 : d1) 만 만들고 select 문에서 d1 대신 d1.main을 사용합니다 (그렇지 않으면 인덱스를 사용하지 않습니다). 이 경우 타이밍은 13.6 초입니다. 두 테이블 모두에 인덱스를 빌드하는 것은 실제로 data.table 경우에도 필요하지 않습니다. "dt2 <-data.table (d2)"만 수행하면 타이밍이 3.9 초가됩니다.
datasmurf 2010

두 답변 모두 가치있는 정보를 제공하며 둘 다 읽을 가치가 있습니다 (하나만 "허용"될 수 있음).
datasmurf 2010

당신은 당신의 질문에서 왼쪽 조인과 내부 조인을 비교하고 있습니다
jangorecki

답변:


46

일치 접근 방식은 첫 번째 데이터 프레임의 각 키 값에 대해 두 번째 데이터 프레임에 고유 한 키가있을 때 작동합니다. 두 번째 데이터 프레임에 중복이있는 경우 일치 및 병합 방식이 동일하지 않습니다. 물론 매치는 그렇게 많이하지 않기 때문에 더 빠릅니다. 특히 중복 키를 찾지 않습니다. (코드 뒤에 계속)

DF1 = data.frame(a = c(1, 1, 2, 2), b = 1:4)
DF2 = data.frame(b = c(1, 2, 3, 3, 4), c = letters[1:5])
merge(DF1, DF2)
    b a c
  1 1 1 a
  2 2 1 b
  3 3 2 c
  4 3 2 d
  5 4 2 e
DF1$c = DF2$c[match(DF1$b, DF2$b)]
DF1$c
[1] a b c e
Levels: a b c d e

> DF1
  a b c
1 1 1 a
2 1 2 b
3 2 3 c
4 2 4 e

질문에 게시 된 sqldf 코드에서 인덱스가 두 테이블에 사용 된 것처럼 보일 수 있지만 실제로는 SQL select가 실행되기 전에 덮어 쓴 테이블에 배치되며 부분적으로 그 이유를 설명합니다. 너무 느립니다. sqldf의 아이디어는 R 세션의 데이터 프레임이 sqlite의 테이블이 아닌 데이터베이스를 구성한다는 것입니다. 따라서 코드가 정규화되지 않은 테이블 이름을 참조 할 때마다 sqlite의 기본 데이터베이스가 아닌 R 작업 공간에서 찾습니다. 따라서 표시된 select 문은 작업 공간에서 d1 및 d2를 인덱스와 함께 있던 데이터베이스를 막는 sqlite의 주 데이터베이스로 읽습니다. 결과적으로 인덱스가없는 조인을 수행합니다. sqlite의 주 데이터베이스에있는 d1 및 d2 버전을 사용하려면 해당 버전을 main.d1 및 main으로 참조해야합니다. d2는 d1 및 d2가 아닙니다. 또한 가능한 한 빨리 실행하려는 경우 간단한 조인은 두 테이블 모두에서 인덱스를 사용할 수 없으므로 인덱스 중 하나를 만드는 시간을 절약 할 수 있습니다. 아래 코드에서 이러한 점을 설명합니다.

정확한 계산이 어떤 패키지가 가장 빠른지 큰 차이를 만들 수 있다는 것을 알아 차릴 가치가 있습니다. 예를 들어, 아래에서 병합 및 집계를 수행합니다. 우리는 두 결과가 거의 반대임을 알 수 있습니다. 가장 빠른 것에서 가장 느린 것까지의 첫 번째 예에서는 data.table, plyr, merge 및 sqldf를 얻는 반면 두 번째 예에서는 sqldf, aggregate, data.table 및 plyr-첫 번째 예와 거의 반대입니다. 첫 번째 예에서 sqldf는 data.table보다 3 배 느리고 두 번째 예에서는 plyr보다 200 배 빠르며 data.table보다 100 배 빠릅니다. 아래에서는 입력 코드, 병합에 대한 출력 타이밍 및 집계에 대한 출력 타이밍을 보여줍니다. 또한 sqldf가 데이터베이스를 기반으로하므로 R이 처리 할 수있는 것보다 큰 개체를 처리 할 수 ​​있다는 점도 주목할 가치가 있습니다 (sqldf의 dbname 인수를 사용하는 경우). 다른 접근 방식은 주 메모리에서 처리하는 것으로 제한됩니다. 또한 sqlite로 sqldf를 설명했지만 H2 및 PostgreSQL 데이터베이스도 지원합니다.

library(plyr)
library(data.table)
library(sqldf)

set.seed(123)
N <- 1e5
d1 <- data.frame(x=sample(N,N), y1=rnorm(N))
d2 <- data.frame(x=sample(N,N), y2=rnorm(N))

g1 <- sample(1:1000, N, replace = TRUE)
g2<- sample(1:1000, N, replace = TRUE)
d <- data.frame(d1, g1, g2)

library(rbenchmark)

benchmark(replications = 1, order = "elapsed",
   merge = merge(d1, d2),
   plyr = join(d1, d2),
   data.table = { 
      dt1 <- data.table(d1, key = "x")
      dt2 <- data.table(d2, key = "x")
      data.frame( dt1[dt2,list(x,y1,y2=dt2$y2)] )
      },
   sqldf = sqldf(c("create index ix1 on d1(x)",
      "select * from main.d1 join d2 using(x)"))
)

set.seed(123)
N <- 1e5
g1 <- sample(1:1000, N, replace = TRUE)
g2<- sample(1:1000, N, replace = TRUE)
d <- data.frame(x=sample(N,N), y=rnorm(N), g1, g2)

benchmark(replications = 1, order = "elapsed",
   aggregate = aggregate(d[c("x", "y")], d[c("g1", "g2")], mean), 
   data.table = {
      dt <- data.table(d, key = "g1,g2")
      dt[, colMeans(cbind(x, y)), by = "g1,g2"]
   },
   plyr = ddply(d, .(g1, g2), summarise, avx = mean(x), avy=mean(y)),
   sqldf = sqldf(c("create index ix on d(g1, g2)",
      "select g1, g2, avg(x), avg(y) from main.d group by g1, g2"))
)

병합 계산을 비교하는 두 벤치 마크 호출의 출력은 다음과 같습니다.

Joining by: x
        test replications elapsed relative user.self sys.self user.child sys.child
3 data.table            1    0.34 1.000000      0.31     0.01         NA        NA
2       plyr            1    0.44 1.294118      0.39     0.02         NA        NA
1      merge            1    1.17 3.441176      1.10     0.04         NA        NA
4      sqldf            1    3.34 9.823529      3.24     0.04         NA        NA

집계 계산을 비교하는 벤치 마크 호출의 출력은 다음과 같습니다.

        test replications elapsed  relative user.self sys.self user.child sys.child
4      sqldf            1    2.81  1.000000      2.73     0.02         NA        NA
1  aggregate            1   14.89  5.298932     14.89     0.00         NA        NA
2 data.table            1  132.46 47.138790    131.70     0.08         NA        NA
3       plyr            1  212.69 75.690391    211.57     0.56         NA        NA

고마워, 가버. 좋은 점, 원래 질문에 대한 의견을 통해 일부 조정했습니다. 실제로 나는 테이블의 상대적인 크기, 키의 다양성 등에 따라 "병합"케이스에서도 순서가 바뀔 수 있다고 생각합니다. (그것이 내 예제가 대표적인지 확실하지 않다고 말한 이유입니다). 그럼에도 불구하고 문제에 대한 모든 다른 해결책을 보는 것은 좋습니다.
datasmurf 2010-12-01

"집계"사례에 대한 의견도 감사드립니다. 이것은 질문의 "병합"설정과 다르지만 매우 관련이 있습니다. 나는 실제로 그것에 대해 별도의 질문으로 물었을 것입니다. 그러나 이미 여기에 하나가 있습니다 . stackoverflow.com/questions/3685492/… . 위의 결과를 기반으로하여 sqldf 솔루션이 기존의 모든 답변을 능가 할 수 있습니다.)
datasmurf 2010

40

Gabor의 결과에보고 된 132 초 data.table는 실제로 타이밍 기본 기능 colMeanscbind(해당 기능을 사용하여 유도 된 메모리 할당 및 복사)입니다. 를 사용하는 좋은 방법과 나쁜 방법 data.table도 있습니다.

benchmark(replications = 1, order = "elapsed", 
  aggregate = aggregate(d[c("x", "y")], d[c("g1", "g2")], mean),
  data.tableBad = {
     dt <- data.table(d, key = "g1,g2") 
     dt[, colMeans(cbind(x, y)), by = "g1,g2"]
  }, 
  data.tableGood = {
     dt <- data.table(d, key = "g1,g2") 
     dt[, list(mean(x),mean(y)), by = "g1,g2"]
  }, 
  plyr = ddply(d, .(g1, g2), summarise, avx = mean(x), avy=mean(y)),
  sqldf = sqldf(c("create index ix on d(g1, g2)",
      "select g1, g2, avg(x), avg(y) from main.d group by g1, g2"))
  ) 

            test replications elapsed relative user.self sys.self
3 data.tableGood            1    0.15    1.000      0.16     0.00
5          sqldf            1    1.01    6.733      1.01     0.00
2  data.tableBad            1    1.63   10.867      1.61     0.01
1      aggregate            1    6.40   42.667      6.38     0.00
4           plyr            1  317.97 2119.800    265.12    51.05

packageVersion("data.table")
# [1] ‘1.8.2’
packageVersion("plyr")
# [1] ‘1.7.1’
packageVersion("sqldf")
# [1] ‘0.4.6.4’
R.version.string
# R version 2.15.1 (2012-06-22)

나는 플라이어를 잘 모르므로 plyr여기 에서 타이밍에 의존하기 전에 Hadley와 확인 하십시오. 또한 운임 data.table을 위해 변환 data.table하고 키를 설정하는 시간이 포함됩니다 .


이 답변은 2010 년 12 월에 처음 답변 된 이후 업데이트되었습니다. 이전 벤치 마크 결과는 다음과 같습니다. 이 답변의 개정 내역을 참조하여 변경된 사항을 확인하십시오.

              test replications elapsed   relative user.self sys.self
4   data.tableBest            1   0.532   1.000000     0.488    0.020
7            sqldf            1   2.059   3.870301     2.041    0.008
3 data.tableBetter            1   9.580  18.007519     9.213    0.220
1        aggregate            1  14.864  27.939850    13.937    0.316
2  data.tableWorst            1 152.046 285.800752   150.173    0.556
6 plyrwithInternal            1 198.283 372.712406   189.391    7.665
5             plyr            1 225.726 424.296992   208.013    8.004

ddply는 데이터 프레임에서만 작동하므로이 예제는 최악의 성능을 제공합니다. 향후 버전에서 이러한 유형의 일반적인 작업을위한 더 나은 인터페이스를 갖기를 바랍니다.
hadley

1
참고 : .InternalCRAN 패키지에서 호출을 사용할 수 없습니다 . CRAN 리포지토리 정책을 참조하세요 .
Joshua Ulrich

@JoshuaUlrich 거의 2 년 전에 답변이 작성되었을 때 iirc. 내부적으로 호출하지 않고 지금 data.table자동으로 최적화되도록 이 답변을 업데이트하겠습니다 . mean.Internal
Matt Dowle

@MatthewDowle : 네, 언제 변경되었는지 잘 모르겠습니다. 이제 그럴 뿐이라는 걸 알아요. 그리고 그것은 당신의 대답에서 완벽하게 괜찮습니다. 패키지에서는 작동하지 않을 것입니다.
Joshua Ulrich는

1
@AleksandrBlekh 감사합니다. 여기에 귀하의 의견을 기존 기능 요청 # 599에 연결했습니다 . 거기로 이동합시다. 귀하의 예제 코드는 for루프를 멋지게 보여줍니다 . 해당 문제에 "SEM 분석"에 대한 추가 정보를 추가 할 수 있습니까? 예를 들어 SEM = 주사 전자 현미경이라고 추측하고 있습니다. 응용 프로그램에 대해 더 많이 알면 더 흥미로워지고 우선 순위를 정하는 데 도움이됩니다.
Matt Dowle 2014 년

16

간단한 작업 (조인 양쪽의 고유 값)의 경우 match다음을 사용합니다 .

system.time({
    d <- d1
    d$y2 <- d2$y2[match(d1$x,d2$x)]
})

병합보다 훨씬 빠릅니다 (내 컴퓨터에서 0.13s ~ 3.37s).

내 타이밍 :

  • merge: 3.32 초
  • plyr: 0.84 초
  • match: 0.12 초

4
고마워, 마렉. 이것이 왜 그렇게 빠른지 (인덱스 / 해시 테이블 작성)에 대한 설명은 여기에서 찾을 수 있습니다. tolstoy.newcastle.edu.au/R/help/01c/2739.html
datasmurf 2010

11

dplyr을 사용하여 벤치 마크를 게시하는 것이 흥미로울 것이라고 생각했습니다.

            test replications elapsed relative user.self sys.self
5          dplyr            1    0.25     1.00      0.25     0.00
3 data.tableGood            1    0.28     1.12      0.27     0.00
6          sqldf            1    0.58     2.32      0.57     0.00
2  data.tableBad            1    1.10     4.40      1.09     0.01
1      aggregate            1    4.79    19.16      4.73     0.02
4           plyr            1  186.70   746.80    152.11    30.27

packageVersion("data.table")
[1]1.8.10’
packageVersion("plyr")
[1]1.8’
packageVersion("sqldf")
[1]0.4.7’
packageVersion("dplyr")
[1]0.1.2’
R.version.string
[1] "R version 3.0.2 (2013-09-25)"

방금 추가 :

dplyr = summarise(dt_dt, avx = mean(x), avy = mean(y))

데이터 테이블을 사용하여 dplyr에 대한 데이터를 설정합니다.

dt <- tbl_dt(d)
dt_dt <- group_by(dt, g1, g2)

업데이트 : data.tableBad 및 plyr를 제거하고 RStudio 만 열었습니다 (i7, 16GB ram).

data.table 1.9 및 dplyr (데이터 프레임 포함) :

            test replications elapsed relative user.self sys.self
2 data.tableGood            1    0.02      1.0      0.02     0.00
3          dplyr            1    0.04      2.0      0.04     0.00
4          sqldf            1    0.46     23.0      0.46     0.00
1      aggregate            1    6.11    305.5      6.10     0.02

data.table 1.9 및 dplyr (데이터 테이블 포함) :

            test replications elapsed relative user.self sys.self
2 data.tableGood            1    0.02        1      0.02     0.00
3          dplyr            1    0.02        1      0.02     0.00
4          sqldf            1    0.44       22      0.43     0.02
1      aggregate            1    6.14      307      6.10     0.01

packageVersion("data.table")
[1] '1.9.0'
packageVersion("dplyr")
[1] '0.1.2'

일관성을 위해 데이터 테이블을 사용하는 all 및 data.table 1.9 및 dplyr 원본이 있습니다.

            test replications elapsed relative user.self sys.self
5          dplyr            1    0.01        1      0.02     0.00
3 data.tableGood            1    0.02        2      0.01     0.00
6          sqldf            1    0.47       47      0.46     0.00
1      aggregate            1    6.16      616      6.16     0.00
2  data.tableBad            1   15.45     1545     15.38     0.01
4           plyr            1  110.23    11023     90.46    19.52

이 데이터는 새 data.table 및 dplyr에 비해 너무 작다고 생각합니다. :)

더 큰 데이터 세트 :

N <- 1e8
g1 <- sample(1:50000, N, replace = TRUE)
g2<- sample(1:50000, N, replace = TRUE)
d <- data.frame(x=sample(N,N), y=rnorm(N), g1, g2)

벤치 마크를 실행하기 전에 데이터를 저장하기 위해 약 10-13GB의 램을 사용했습니다.

결과 :

            test replications elapsed relative user.self sys.self
1          dplyr            1   14.88        1      6.24     7.52
2 data.tableGood            1   28.41        1     18.55      9.4

10 억을 시도했지만 숫양을 날려 버렸습니다. 32GB는 문제를 처리하지 않습니다.


[Edit by Arun] (dotcomken,이 코드를 실행하고 벤치마킹 결과를 붙여 주시겠습니까? 감사합니다).

require(data.table)
require(dplyr)
require(rbenchmark)

N <- 1e8
g1 <- sample(1:50000, N, replace = TRUE)
g2 <- sample(1:50000, N, replace = TRUE)
d <- data.frame(x=sample(N,N), y=rnorm(N), g1, g2)

benchmark(replications = 5, order = "elapsed", 
  data.table = {
     dt <- as.data.table(d) 
     dt[, lapply(.SD, mean), by = "g1,g2"]
  }, 
  dplyr_DF = d %.% group_by(g1, g2) %.% summarise(avx = mean(x), avy=mean(y))
) 

여기에 Arun의 요청에 따라 실행하도록 제공 한 출력은 다음과 같습니다.

        test replications elapsed relative user.self sys.self
1 data.table            5   15.35     1.00     13.77     1.57
2   dplyr_DF            5  137.84     8.98    136.31     1.44

혼란스러워서 미안 해요. 늦은 밤이 왔어요.

데이터 프레임과 함께 dplyr을 사용하는 것은 요약을 처리하는 데 덜 효율적인 방법 인 것 같습니다. 이 메서드는 data.table 및 dplyr의 정확한 기능을 포함 된 데이터 구조 메서드와 비교합니까? group_by 또는 data.table을 만들기 전에 대부분의 데이터를 정리해야하므로 거의 분리하는 것이 좋습니다. 맛의 문제 일 수 있지만 가장 중요한 부분은 데이터를 얼마나 효율적으로 모델링 할 수 있는지입니다.


1
멋진 업데이트입니다. 감사. 당신의 머신은이 데이터 세트와 비교했을 때 짐승이라고 생각합니다. 당신의 L2 캐시 (존재하는 경우 L3)의 크기는 얼마입니까?
Arun

i7 L2는 2x256KB 8 방향이고 L3은 4MB 16 방향입니다. 128GB SSD, Win 7, Dell inspiron
dotcomken 2014

1
예를 다시 형식화 할 수 있습니까? 조금 혼란 스러워요. data.table이 dplyr보다 (이 예에서) 더 나은가요? 그렇다면 어떤 상황에서.
csgillespie 2014 년

1

병합 기능 및 선택적 매개 변수 사용 :

내부 조인 : merge (df1, df2)는 R이 공통 변수 이름으로 프레임을 자동으로 조인하기 때문에 이러한 예제에 대해 작동하지만 merge (df1, df2, by = "CustomerId")를 지정하여 원하는 필드에서만 일치했습니다. 일치하는 변수의 이름이 서로 다른 데이터 프레임에서 다른 경우 by.x 및 by.y 매개 변수를 사용할 수도 있습니다.

Outer join: merge(x = df1, y = df2, by = "CustomerId", all = TRUE)

Left outer: merge(x = df1, y = df2, by = "CustomerId", all.x = TRUE)

Right outer: merge(x = df1, y = df2, by = "CustomerId", all.y = TRUE)

Cross join: merge(x = df1, y = df2, by = NULL)

문제는 성능에 관한 것이 었습니다. 조인에 대한 구문 만 제공했습니다. 도움이되지만 질문에 대한 답은 아닙니다. 이 답변에는 OP의 예를 사용한 벤치 마크 데이터가 부족하여 성능이 더 우수하거나 적어도 경쟁력이 있음을 보여줍니다.
Michael Tuchman
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.