Data.frame'leri R'de birleştirmenin / birleştirmenin en hızlı yolu nedir?


97

Örneğin (çoğu temsili örneğin olup olmadığından emin değil):

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

Şimdiye kadar sahip olduğum şey bu:

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 yolunu yapmanın doğru yolu Gabor tarafından aşağıda belirtilmiştir: sadece bir dizin oluşturun (d1'de diyelim) ve select deyiminde d1 yerine d1.main kullanın (aksi takdirde dizini kullanmaz). Bu durumda zamanlama 13.6 saniyedir. Her iki tablodaki indeksleri oluşturmak data.table durumunda aslında gerekli değildir, sadece "dt2 <- data.table (d2)" yapın ve zamanlama 3,9 saniye olacaktır.
datasmurf 01

Her iki yanıt da değerli bilgiler sağlar ve her ikisini de okumaya değerdir (ancak yalnızca biri "kabul edilebilir").
datasmurf

Yanıtlar:


46

Eşleştirme yaklaşımı, birinci veri çerçevesinde her bir anahtar değer için ikinci veri çerçevesinde benzersiz bir anahtar olduğunda çalışır. İkinci veri çerçevesinde kopyalar varsa, eşleştirme ve birleştirme yaklaşımları aynı değildir. Maç, elbette daha hızlıdır çünkü o kadar başarılı değildir. Özellikle, yinelenen anahtarları asla aramaz. (koddan sonra devam)

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

Soruya gönderilen sqldf kodunda, iki tabloda indeksler kullanılmış gibi görünebilir, ancak aslında, sql select çalıştırılmadan önce üzerine yazılan tablolara yerleştirilir ve kısmen nedenini açıklar. çok yavaş. Sqldf fikri, R oturumunuzdaki veri çerçevelerinin sqlite'daki tabloları değil, veri tabanını oluşturmasıdır. Bu nedenle, kod nitelenmemiş bir tablo adına her atıfta bulunduğunda, bunun için R çalışma alanınıza bakacaktır - sqlite'ın ana veritabanına değil. Böylece gösterilen select deyimi çalışma alanından d1 ve d2'yi sqlite'ın ana veritabanına okur ve indekslerle orada bulunanları bozar. Sonuç olarak, indeks içermeyen bir birleştirme yapar. Eğer sqlite'ın ana veritabanında bulunan d1 ve d2 sürümlerini kullanmak istiyorsanız, bunlara main.d1 ve main olarak başvurmanız gerekir. d2 ve d1 ve d2 olarak değil. Ayrıca, olabildiğince hızlı çalışmasını sağlamaya çalışıyorsanız, basit bir birleştirmenin her iki tablodaki dizinlerden yararlanamayacağını ve böylece dizinlerden birini oluştururken zamandan tasarruf edebileceğinizi unutmayın. Aşağıdaki kodda bu noktaları gösteriyoruz.

Kesin hesaplamanın hangi paketin en hızlı olduğu konusunda büyük bir fark yaratabileceğini fark etmek önemlidir. Örneğin, aşağıda bir birleştirme ve bir toplama yapıyoruz. İkisi için sonuçların neredeyse tersine döndüğünü görüyoruz. İlk örnekte en hızlıdan en yavaşa şunu elde ederiz: data.table, plyr, merge ve sqldf, oysa ikinci örnekte sqldf, aggregate, data.table ve plyr - neredeyse ilkinin tersi. İlk örnekte sqldf, data.table'dan 3 kat daha yavaştır ve ikincisinde, plyr'den 200 kat daha hızlı ve data.table'dan 100 kat daha hızlıdır. Aşağıda giriş kodunu, birleştirme için çıkış zamanlamalarını ve toplam için çıkış zamanlamalarını gösteriyoruz. Ayrıca, sqldf'nin bir veritabanına dayandığını ve bu nedenle R'nin işleyebileceğinden daha büyük nesneleri işleyebileceğini (sqldf'nin dbname argümanını kullanırsanız), diğer yaklaşımların ana bellekte işlemeyle sınırlı olduğunu belirtmek de önemlidir. Ayrıca sqldf'yi sqlite ile gösterdik ama aynı zamanda H2 ve PostgreSQL veritabanlarını da destekliyor.

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

Birleştirme hesaplamalarını karşılaştıran iki kıyaslama çağrısının çıktıları şunlardır:

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

Toplu hesaplamaları karşılaştıran karşılaştırma çağrısının çıktıları:

        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

Teşekkür ederim Gabor. Harika noktalar, orijinal soruya yorum yaparak bazı ayarlamalar yaptım. Aslında tabloların göreli boyutlarına, anahtarların çokluğuna vb. Bağlı olarak "birleştirme" durumunda bile sıra değişebileceğini tahmin ediyorum (bu yüzden örneğimin temsili olup olmadığından emin olmadığımı söyledim). Yine de, sorunun tüm farklı çözümlerini görmek güzel.
datasmurf

"Birleştirme" vakası hakkındaki yorumu da takdir ediyorum. Bu, sorudaki "birleştirme" kurulumundan farklı olsa da, oldukça önemlidir. Aslında bunu ayrı bir soruda sorardım, ancak burada zaten stackoverflow.com/questions/3685492/… var . Buna da katkıda bulunmak isteyebilirsiniz, çünkü yukarıdaki sonuçlara göre, sqldf çözümü oradaki tüm mevcut cevapları yenebilir;)
datasmurf

40

Gabor'un sonuçlarında bildirilen 132 saniye data.table, aslında zamanlama temel fonksiyonları colMeansve cbind(bu fonksiyonlar kullanılarak indüklenen hafıza tahsisi ve kopyalama). Kullanmanın iyi ve kötü yolları data.tableda var.

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)

Lütfen katlamayı iyi bilmediğimi unutmayın, bu nedenle lütfen buradaki plyrzamanlamalara güvenmeden önce Hadley ile görüşün . Ayrıca, data.tableyapmak için data.table, anahtarın dönüştürülmesi ve ayarlanması için gerekli zamanı içerdiğini unutmayın .


Bu yanıt, ilk olarak Aralık 2010'da yanıtlandıktan sonra güncellenmiştir. Önceki karşılaştırma sonuçları aşağıdadır. Lütfen nelerin değiştiğini görmek için bu cevabın düzeltme geçmişine bakın.

              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 yalnızca veri çerçeveleriyle çalıştığından, bu örnekte en kötü durum performansı elde edilir. Gelecekteki bir sürümde bu tür ortak işlemler için daha iyi bir arayüze sahip olmayı umuyorum.
hadley

1
Bilginize: .InternalCRAN paketlerinde çağrıları kullanamazsınız , CRAN Depo Politikası'na bakın .
Joshua Ulrich

@JoshuaUlrich Cevap yaklaşık 2 yıl önce yazıldığında bunu yapabilirdin, iirc. Bu yanıtı şimdi data.tableotomatik olarak optimize edecek şekilde güncelleyeceğim mean( .Internaldahili olarak aramadan ).
Matt Dowle

@MatthewDowle: Evet, ne zaman / değiştiğinden emin değilim. Artık durumun böyle olduğunu biliyorum. Ve cevabınızda gayet iyi, sadece paketler halinde çalışmayacak.
Joshua Ulrich

1
@AleksandrBlekh Teşekkürler. Yorumlarınızı burada 599 numaralı mevcut özellik isteğine bağladım . Oraya gidelim. Örnek kodunuz fordöngüyü güzel bir şekilde gösteriyor , bu iyi. Bu soruna "SEM analizi" hakkında daha fazla bilgi ekleyebilir misiniz? Örneğin, SEM = Taramalı elektron mikroskobu olduğunu tahmin ediyorum. Uygulama hakkında daha fazla bilgi sahibi olmak, uygulamayı bizim için daha ilginç hale getirir ve öncelik vermemize yardımcı olur.
Matt Dowle

16

Basit görev için (birleşimin her iki tarafındaki benzersiz değerler) kullanıyorum match:

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

Birleştirmekten çok daha hızlı (benim makinemde 0.13s'den 3.37s'ye).

Zamanlamalarım:

  • merge: 3.32s
  • plyr: 0,84 sn
  • match: 0.12s

4
Teşekkür ederim, Marek. Bunun neden bu kadar hızlı olduğuna dair bazı açıklamalar (bir dizin / karma tablosu oluşturur) burada bulunabilir: tolstoy.newcastle.edu.au/R/help/01c/2739.html
datasmurf

11

Karışımda dplyr ile bir kıyaslama yapmanın ilginç olacağını düşündüm: (çalışan birçok şey vardı)

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

Sadece ekledim:

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

ve dplyr için verileri bir veri tablosu ile ayarlayın:

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

Güncellendi: data.tableBad ve plyr'i kaldırdım ve RStudio açık (i7, 16GB ram) dışında hiçbir şey kalmadı.

Data.table 1.9 ve veri çerçeveli dplyr ile:

            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 ve veri tablosu ile dplyr ile:

            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'

Tutarlılık için burada all ve data.table 1.9 ve bir veri tablosu kullanan dplyr ile orijinaldir:

            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

Bu verilerin yeni data.table ve dplyr için çok küçük olduğunu düşünüyorum :)

Daha büyük veri seti:

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)

Karşılaştırmayı çalıştırmadan önce verileri tutmak için yaklaşık 10-13GB ram aldı.

Sonuçlar:

            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

1 milyar denedi ama koç patladı. 32GB sorun değil.


[Arun'a göre düzenleyin] (dotcomken, lütfen bu kodu çalıştırıp karşılaştırma sonuçlarınızı yapıştırır mısınız? Teşekkürler).

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'un isteğine göre, çalıştırmam için bana sağladığınız şeyin çıktısı:

        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

Karışıklık için özür dilerim, gece geç beni etkiledi.

Veri çerçevesi ile dplyr kullanmak, özetleri işlemenin daha az verimli yolu gibi görünüyor. Bu yöntemler, data.table ve dplyr'ın tam işlevselliğini, dahil edilen veri yapısı yöntemleriyle karşılaştırmak için mi? Data.table'ı group_by'den veya oluşturmadan önce çoğu verinin temizlenmesi gerekeceğinden neredeyse bunu ayırmayı tercih ederim. Bu bir zevk meselesi olabilir ama bence en önemli kısım verilerin ne kadar verimli bir şekilde modellenebileceğidir.


1
Güzel güncelleme. Teşekkürler. Sanırım makineniz bu veri kümesine kıyasla bir canavar. L2 önbelleğinizin (ve varsa L3'ün) boyutu nedir?
Arun

i7 L2 2x256 KB 8 yollu, L3 4 MB 16 yolludur. 128 GB SSD, Dell inspiron'da 7 kazanın
dotcomken

1
Örneğinizi yeniden biçimlendirebilir misiniz? Biraz kafam karıştı. Data.table (bu örnekte) dplyr'den daha mı iyi? Eğer öyleyse, hangi koşullar altında.
csgillespie

1

Birleştirme işlevini ve isteğe bağlı parametrelerini kullanarak:

İç birleştirme: birleştirme (df1, df2), bu örnekler için işe yarar çünkü R, çerçeveleri ortak değişken adlarıyla otomatik olarak birleştirir, ancak büyük olasılıkla, sadece istediğiniz alanlarla eşleşiyordu. Eşleşen değişkenlerin farklı veri çerçevelerinde farklı adları varsa, by.x ve by.y parametrelerini de kullanabilirsiniz.

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)

Soru performansla ilgiliydi. Yalnızca birleşimler için sözdizimi sağladınız. Yararlı olsa da soruyu cevaplamaz. Bu cevap, OP'nin örneklerini kullanarak daha iyi veya en azından oldukça rekabetçi bir şekilde performans gösterdiğini gösteren karşılaştırma verilerinden yoksundur.
Michael Tuchman
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.