Какой самый быстрый способ объединить/объединить data.frames в 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

Ответ 1

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

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. Таким образом, каждый раз, когда код ссылается на неквалифицированное имя таблицы, он будет выглядеть в вашей рабочей области 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"))
)

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

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

Ответ 2

132 секунды, указанные в результатах Gabor для 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 do включает время для преобразования в 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

Ответ 3

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

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

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

Мои тайминги:

  • merge: 3.32s
  • plyr: 0.84s
  • match: 0.12s

Ответ 4

Думал, что было бы интересно опубликовать сравнительный тест с 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, 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'

Для согласованности здесь используется оригинал со всеми и 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-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 миллиард, но взорвал баран. 32GB не справится с этим.


[Edit by 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 или создадим таблицу данных. Это может быть вопросом вкуса, но я думаю, что самая важная часть заключается в том, насколько эффективно можно моделировать данные.

Ответ 5

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

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