سؤال

على سبيل المثال (لست متأكدًا مما إذا كان أكثر مثال تمثيلا):

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. وبالتالي ، في كل مرة يشير الرمز إلى اسم جدول غير مؤهل ، سيبحث في مساحة العمل الخاصة بك - وليس في قاعدة البيانات الرئيسية لـ SQLite. وبالتالي فإن عبارة SELECT التي تم عرضها تقرأ D1 و D2 من مساحة العمل إلى قاعدة البيانات الرئيسية لـ SQLite التي تتجول في تلك التي كانت هناك مع الفهارس. نتيجة لذلك ، يقوم بالانضمام مع عدم وجود فهارس. إذا كنت ترغب في الاستفادة من إصدارات D1 و D2 التي كانت في قاعدة البيانات الرئيسية لـ SQLite ، فسيتعين عليك الرجوع إليها على أنها Main.d1 و Main.d2 وليس كـ D1 و D2. أيضًا ، إذا كنت تحاول تشغيلها بأسرع ما يمكن ، لاحظ أن هناك انضمامًا بسيطًا لا يمكن أن يستفيد من الفهارس على كلا الجدولين حتى تتمكن من توفير وقت إنشاء أحد الفهارس. في الكود أدناه ، نوضح هذه النقاط.

من المفيد ملاحظة أن الحساب الدقيق يمكن أن يحدث فرقًا كبيرًا على الحزمة الأسرع. على سبيل المثال ، نقوم بدمج وتجميع أدناه. نرى أن النتائج قد تم عكسها تقريبًا للاثنين. في المثال الأول من الأسرع إلى الأبطأ ، نحصل على: data.table ، plyr ، merge و sqldf بينما في المثال الثاني sqldf ، ectregate ، data.table و plyr - ما يقرب من عكس الأول. في المثال الأول ، يكون SQLDF أبطأ 3x من البيانات. وفي الثانية أسرع 200x من PLYR وأسرع 100 مرة من البيانات. نوضح أدناه رمز الإدخال ، وتوقيت الإخراج لدمج وتوقيت الإخراج للتجميع. من المفيد أيضًا الإشارة إلى أن SQLDF يعتمد على قاعدة بيانات ، وبالتالي يمكنه التعامل مع الكائنات الأكبر من R يمكنه التعامل معها (إذا كنت تستخدم وسيطة DBName لـ SQLDF) بينما تقتصر الأساليب الأخرى على المعالجة في الذاكرة الرئيسية. كما أوضحنا SQLDF مع SQLite ولكنه يدعم أيضًا قواعد بيانات 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

نصائح أخرى

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 حسنًا ، لذا يرجى التحقق من هادلي قبل الاعتماد على 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

لمهمة بسيطة (قيم فريدة على جانبي الانضمام) أستخدمها match:

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

إنها أسرع بكثير من الدمج (على جهاز My Manual من 0.13 إلى 3.37s).

توقياتي:

  • merge: 3.32S
  • plyr: 0.84S
  • 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.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'

للاتساق هنا هو الأصل مع الكل والبيانات. 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

أعتقد أن هذه البيانات صغيرة جدًا بالنسبة للبيانات الجديدة.

مجموعة بيانات أكبر:

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-13 جيجابايت من ذاكرة الوصول العشوائي لمجرد الاحتفاظ بالبيانات قبل تشغيل المعيار.

نتائج:

            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 مليار ولكن فجر ذاكرة الوصول العشوائي. 32 جيجابايت سوف تتعامل معها لا مشكلة.


تحرير بواسطة 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))
) 

وفقًا لطلب آرون هنا ، فإن إخراج ما قدمته لي لتشغيله:

        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 أو إنشاء البيانات. قد يكون ذلك مسألة ذوق ، لكنني أعتقد أن الجزء الأكثر أهمية هو كيف يمكن تصميم البيانات بكفاءة.

باستخدام دالة الدمج والمعلمات الاختيارية:

Inner Join: سوف يعمل Merge (DF1 ، DF2) على هذه الأمثلة لأن R ينضم تلقائيًا إلى الإطارات بأسماء متغيرة شائعة ، لكن من المرجح أن ترغب في تحديد الدمج (DF1 ، DF2 ، بواسطة = "CustomerId") للتأكد من أنك كانوا مطابقة فقط للحقول التي تريدها. يمكنك أيضًا استخدام المعلمات By.x و.

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 مع الإسناد
لا تنتمي إلى StackOverflow
scroll top