R में data.frames को मर्ज करने / शामिल करने का सबसे तेज़ तरीका क्या है?


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 तरीके से करने का उचित तरीका गैबर द्वारा नीचे बताया गया है: चुनिंदा कथन में d1 के बजाय केवल एक इंडेक्स (d1 पर कहें) और d1.main का उपयोग करें (अन्यथा यह इंडेक्स का उपयोग नहीं करेगा)। समय इस मामले में 13.6 सेकंड है। दोनों तालिकाओं पर बिल्डिंग इंडेक्स वास्तव में आवश्यक नहीं हैं। डेटा या तो मामले में, बस "dt2 <- data.table (d2)" करें और समय 3.9 सेकंड होगा।
datasmurf

दोनों उत्तर मूल्यवान जानकारी प्रदान करते हैं, दोनों को पढ़ने के लायक है (हालांकि केवल एक "स्वीकार किया जा सकता है")।
डेटासर्मफ

आप अपने प्रश्न में शामिल होने के लिए लेफ्ट जॉइन की तुलना भीतर से कर रहे हैं
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 का चयन करने से पहले कभी भी लिखा गया था और भाग में, क्यों के लिए खाते हैं यह इतना धीमा है। Sqldf का विचार यह है कि आपके R सत्र में डेटा फ़्रेम डेटा बेस का निर्माण करता है, न कि sqlite में तालिकाएँ। इस प्रकार हर बार कोड एक अयोग्य तालिका के नाम को संदर्भित करता है जो आपके आर कार्यक्षेत्र में इसके लिए दिखेगा - न कि साइक्लाइट के मुख्य डेटाबेस में। इस प्रकार जो चुनिंदा कथन दिखाया गया था, वह कार्यक्षेत्र से d1 और d2 को sqlite के मुख्य डेटाबेस में शामिल करता है जो कि अनुक्रमणिका के साथ थे। परिणामस्वरूप यह बिना किसी इंडेक्स के साथ जुड़ता है। यदि आप d1 और d2 के संस्करणों का उपयोग करना चाहते थे, जो कि sqlite के मुख्य डेटाबेस में थे, तो आपको उन्हें main.d1 और main के रूप में संदर्भित करना होगा। d2 और d1 और d2 के रूप में नहीं। इसके अलावा, यदि आप इसे अधिक से अधिक तेजी से चलाने की कोशिश कर रहे हैं, तो ध्यान दें कि एक साधारण जोड़ दोनों तालिकाओं पर अनुक्रमित का उपयोग नहीं कर सकता है ताकि आप अनुक्रमित में से एक बनाने का समय बचा सकें। नीचे दिए गए कोड में हम इन बिंदुओं को चित्रित करते हैं।

यह ध्यान देने योग्य है कि सटीक गणना किस पैकेज पर सबसे तेज़ है, इससे बहुत अंतर हो सकता है। उदाहरण के लिए, हम नीचे एक मर्ज और एक समुच्चय करते हैं। हम देखते हैं कि परिणाम दोनों के लिए लगभग उलट हैं। पहले उदाहरण में सबसे तेज़ से सबसे धीमी गति से हम प्राप्त करते हैं: data.table, plyr, मर्ज और sqldf जबकि दूसरे उदाहरण में sqldf, समुच्चय, data.table और plyr - लगभग पहले वाले के विपरीत। पहले उदाहरण में sqldf data.table से 3x धीमा है और दूसरे में इसका 200x plyr से तेज और 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

धन्यवाद, गाबोर। उत्कृष्ट बिंदु, मैंने मूल प्रश्न पर टिप्पणियों के माध्यम से कुछ समायोजन किए। वास्तव में मुझे लगता है कि तालिकाओं के सापेक्ष आकार, कुंजियों की बहुलता आदि के आधार पर "मर्ज" मामले में भी क्रम बदल सकता है (इसीलिए मैंने कहा कि मुझे यकीन नहीं है कि मेरा उदाहरण प्रतिनिधि है)। बहरहाल, समस्या के सभी विभिन्न समाधानों को देखना अच्छा है।
डेट्समर्फ़

मैं "एकत्रीकरण" मामले के बारे में टिप्पणी की भी सराहना करता हूं। यद्यपि यह प्रश्न में "मर्ज" सेटअप से अलग है, यह बहुत प्रासंगिक है। मैंने वास्तव में एक अलग प्रश्न में इसके बारे में पूछा होगा, लेकिन यहां पहले से ही stackoverflow.com/questions/3685492/… है । आप ऊपर दिए गए परिणामों के आधार पर उस एक के रूप में अच्छी तरह से योगदान करना चाह सकते हैं, sqldf समाधान सभी मौजूदा उत्तरों को हरा सकता है;)
datasmurf

40

गैबोर के परिणामों के लिए रिपोर्ट किए गए 132 सेकंड data.tableवास्तव में टाइम बेस फ़ंक्शंस 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यहां के समय पर भरोसा करने से पहले हैडली के साथ जांच करें। यह भी ध्यान दें कि data.tableक्या करने के लिए data.tableऔर सेट करने के लिए समय निर्धारित करना शामिल है , दूरदर्शिता के लिए।


यह उत्तर मूल रूप से 2010 दिसंबर में उत्तर देने के बाद से अद्यतन किया गया है। पिछले बेंचमार्क परिणाम नीचे हैं। कृपया इस उत्तर को देखने के लिए इतिहास देखें कि क्या बदला है।

              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 केवल डेटा फ़्रेम के साथ काम करती है, यह उदाहरण है सबसे खराब स्थिति प्रदर्शन। मुझे उम्मीद है कि भविष्य के संस्करण में इस प्रकार के सामान्य ऑपरेशन के लिए एक बेहतर इंटरफ़ेस होगा।
हैडले

1
FYI करें: आप .InternalCRAN पैकेज में कॉल का उपयोग नहीं कर सकते , CRAN रिपोजिटरी नीति देखें
जोशुआ उलरिच

@JoshuaUlrich आप कर सकते हैं जब जवाब लगभग 2 साल पहले लिखा गया था, iirc। मैं इस उत्तर को data.tableस्वचालित रूप से अपडेट कर दूंगा क्योंकि meanअब यह ( .Internalआंतरिक रूप से कॉल किए बिना ) अनुकूलन करता है ।
मैट डॉवेल

@MatthewDowle: हाँ, मुझे यकीन नहीं है कि कब / अगर यह बदल गया। मैं सिर्फ यह जानता हूं कि अब यह मामला है। और यह आपके उत्तर में पूरी तरह से ठीक है, बस संकुल में काम नहीं करेगा।
जोशुआ उलरिच

1
@AleksandrBlekh धन्यवाद। मैंने आपकी टिप्पणियों को यहां मौजूदा सुविधा अनुरोध # 599 से जोड़ा है । चलो वहाँ चलते हैं। आपका उदाहरण कोड अच्छी तरह से forलूप दिखाता है , यह अच्छा है। क्या आप उस मुद्दे पर "SEM विश्लेषण" के बारे में अधिक जानकारी जोड़ सकते हैं? उदाहरण के लिए मैं अनुमान लगा रहा हूं कि SEM = इलेक्ट्रॉन माइक्रोस्कोप स्कैनिंग? एप्लिकेशन के बारे में अधिक जानना यह हमारे लिए अधिक दिलचस्प बनाता है और हमें प्राथमिकता देने में मदद करता है।
मैट डॉवेल

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

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

और डेटा तालिका के साथ डेटा के लिए डेटा सेटअप करें:

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

अपडेट किया गया: मैंने data.tableBad और plyr को हटा दिया और RStudio ओपन (i7, 16GB ram) के अलावा कुछ नहीं।

डेटा के साथ 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

डेटाटेबल 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'

सुसंगतता के लिए यहाँ सभी और 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

एक अरब की कोशिश की लेकिन राम को उड़ा दिया। 32GB यह कोई समस्या नहीं संभाल लेंगे।


[अरुण द्वारा संपादित करें] (डॉटकॉम, क्या आप इस कोड को चला सकते हैं और अपने बेंचमार्किंग परिणाम पेस्ट कर सकते हैं? धन्यवाद)।

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

अरुण के अनुरोध के अनुसार यहाँ पर आपने मुझे चलाने के लिए क्या दिया था:

        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 का उपयोग करना सारांश को संसाधित करने का कम कुशल तरीका है। क्या यह विधियाँ डेटा की सटीक कार्यक्षमता की तुलना करने के लिए हैं। अपने डेटा संरचना विधियों में शामिल हैं। मैं लगभग अलग करना पसंद करूंगा क्योंकि हमें group_by या data.table बनाने से पहले अधिकांश डेटा को साफ करना होगा। यह स्वाद की बात हो सकती है लेकिन मुझे लगता है कि सबसे महत्वपूर्ण हिस्सा यह है कि डेटा को कितनी कुशलता से मॉडल किया जा सकता है।


1
अच्छा अद्यतन। धन्यवाद। मुझे लगता है कि इस डेटा सेट की तुलना में आपकी मशीन एक जानवर है .. आपके L2 कैश का आकार क्या है (और अगर मौजूद है तो L3)?
अरुण

i7 L2 2x256 KB 8-वे, L3 4 एमबी 16-वे है। 128 जीबी एसएसडी, विन 7 ऑन

1
क्या आप अपना उदाहरण सुधार सकते हैं। मैं थोड़ा उलझन में हूँ। क्या डेटाप्लेयर (इस उदाहरण में) बेहतर है? यदि हां, तो किन परिस्थितियों में।
csgespespie

1

मर्ज फ़ंक्शन और इसके वैकल्पिक मापदंडों का उपयोग करके:

इनर ज्वाइन: मर्ज (df1, df2) इन उदाहरणों के लिए काम करेगा क्योंकि R स्वचालित रूप से सामान्य चर नामों से फ़्रेम में शामिल हो जाता है, लेकिन आप सबसे अधिक मर्ज निर्दिष्ट करना चाहेंगे (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)

सवाल प्रदर्शन को लेकर था। आपने केवल जोड़ के लिए सिंटैक्स प्रदान किया। सहायक होते समय, यह प्रश्न का उत्तर नहीं देता है। इस उत्तर में ओपी के उदाहरणों का उपयोग करके बेंचमार्क डेटा का अभाव है, यह दिखाने के लिए कि यह बेहतर प्रदर्शन करता है, या कम से कम अत्यधिक प्रतिस्पर्धी है।
माइकल टचमैन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.