[英]What's the fastest way to merge/join data.frames in R?
例如(不確定是否最具代表性的例子):
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
當第一個中的每個鍵值在第二個數據幀中有唯一鍵時,匹配方法有效。 如果第二個數據幀中存在重復,則匹配和合並方法不同。 當然,匹配更快,因為它沒有那么多。 特別是它永遠不會尋找重復的鍵。 (代碼后繼續)
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中的表。 因此,每次代碼引用非限定表名時,它將在R工作空間中查找 - 而不是在sqlite的主數據庫中。 因此,顯示的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
在Gabor的data.table
結果中報告的132秒實際上是時基函數colMeans
和cbind
(使用這些函數引起的內存分配和復制)。 使用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
對於簡單的任務(連接兩側的唯一值)我使用match
:
system.time({
d <- d1
d$y2 <- d2$y2[match(d1$x,d2$x)]
})
它比合並快得多(在我的機器上0.13s到3.37s)。
我的時間:
merge
:3.32s plyr
:0.84秒 match
:0.12s 認為在混合中使用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的ram即可保存數據。
結果:
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將處理它沒問題。
[由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之前需要清理大多數數據。 這可能是一個品味問題,但我認為最重要的部分是如何有效地建模數據。
通過使用merge函數及其可選參數:
內部聯接: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)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.