Какой самый быстрый способ объединить/объединить data.frames в R?

StackOverflow https://stackoverflow.com/questions/4322219

Вопрос

Например (хотя не уверен, что это наиболее репрезентативный пример):

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
Это было полезно?

Решение

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

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,greg, 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

Другие советы

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 и установите ключ для Faresse.


Этот ответ был обновлен с тех пор, как первоначально ответил в декабре 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)]
})

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

Мои времена:

  • merge: 3,32 с
  • plyr: 0,84 с
  • match: 0,12 с

Подумал, что было бы интересно опубликовать тест с 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, 16 ГБ ОЗУ).

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

Для согласованности здесь есть оригинал с All и 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

Я думаю, что эти данные слишком малы для новых данных. 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 ГБ будет справляться с этим без проблем.


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

С помощью функции слияния и ее дополнительных параметров:

Внутреннее соединение: Merge (DF1, DF2) будет работать для этих примеров, потому что R автоматически присоединяется к кадрам с помощью общих имен переменных, но вы, скорее всего соответствовали только желаемым полям. Вы также можете использовать параметры 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)
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top