Какой самый быстрый способ слияния / объединения data.frames в R? - PullRequest
94 голосов
/ 01 декабря 2010

Например (не уверен, что наиболее представительный пример):

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

Ответы [ 5 ]

43 голосов
/ 02 декабря 2010

Подход совпадения работает, когда во втором фрейме данных есть уникальный ключ для каждого значения ключа в первом.Если во втором фрейме данных есть дубликаты, то подходы совпадения и слияния не совпадают.Матч, конечно, быстрее, так как он не так много.В частности, он никогда не ищет дубликаты ключей.(продолжение после кода)

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, перекрывая те, которые были там с индексами.В результате он выполняет соединение без индексов.Если вы хотите использовать версии d1 и d2, которые были в основной базе данных sqlite, вы должны ссылаться на них как main.d1 и main.d2, а не как d1 и d2.Кроме того, если вы пытаетесь сделать его максимально быстрым, учтите, что простое объединение не может использовать индексы обеих таблиц, поэтому вы можете сэкономить время на создание одного из индексов.В приведенном ниже коде мы иллюстрируем эти моменты.

Стоит заметить, что точное вычисление может иметь огромное значение для наиболее быстрого пакета.Например, мы делаем слияние и совокупность ниже.Мы видим, что результаты почти противоположны для двух.В первом примере от самого быстрого до самого медленного мы получаем: data.table, plyr, merge и sqldf, тогда как во втором примере sqldf, aggregate, data.table и plyr - почти обратное первому.В первом примере sqldf в 3 раза медленнее, чем data.table, а во втором - в 200 раз быстрее, чем plyr, и в 100 раз быстрее, чем data.table.Ниже мы показываем входной код, время выхода для слияния и время выхода для агрегата.Стоит также отметить, что 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"))
)

Выводы из двух тестов сравнения, сравнивающих вычисления слияния, следующие:Результаты бенчмарка, сравнивающего совокупные вычисления:

        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
39 голосов
/ 19 декабря 2010

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
14 голосов
/ 01 декабря 2010

Для простой задачи (уникальные значения с обеих сторон соединения) я использую match:

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

Это гораздо быстрее, чем слияние (на моей машине от 0,13 до 3,37 с).

Мои сроки:

  • merge: 3,32
  • plyr: 0,84
  • match: 0,12 с
11 голосов
/ 27 февраля 2014

Подумал, что было бы интересно опубликовать бенчмарк с 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 open (i7, ram 16GB).

С 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'

Для согласованности вот оригинал со всеми и данными.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-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ГБ справится с этим без проблем.


[Редактировать Аруном] (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 с фреймом данных представляется менее эффективным способом обработки сводок.Включены ли эти методы для сравнения точных функций data.table и dplyr с их методами структуры данных?Я почти предпочел бы отделить это, так как большую часть данных нужно будет очистить перед тем, как мы создадим group_by или создадим data.table.Это может быть дело вкуса, но я думаю, что наиболее важной частью является то, насколько эффективно можно смоделировать данные.

2 голосов
/ 23 октября 2015

Используя функцию слияния и ее необязательные параметры:

Внутреннее объединение: слияние (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)
...