Каков самый быстрый способ объединить / объединить данные.кадров в 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
5 ответов:
подход соответствия работает, когда есть уникальный ключ во втором фрейме данных для каждого значения ключа в первом. Если во втором фрейме данных есть дубликаты, то подходы совпадения и слияния не совпадают. Матч, конечно, быстрее, так как он не делает так много. В частности, он никогда не ищет дубликаты ключей. (продолжение после кода)
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 и главная.d2 а не как d1 и d2. Кроме того, если вы пытаетесь заставить его работать как можно быстрее, обратите внимание, что простое соединение не может использовать индексы в обеих таблицах, поэтому вы можете сэкономить время на создании одного из индексов. В приведенном ниже коде мы проиллюстрируем это точки.
стоит отметить, что точное вычисление может иметь огромное значение для того, какой пакет является самым быстрым. Например, мы делаем слияние и агрегат ниже. Мы видим, что результаты почти обратные для обоих. В первом примере от самого быстрого к самому медленному, мы получаем: сведения.таблица, plyr, merge и sqldf тогда как во втором примере sqldf, aggregate, data.стол и плир-почти обратная сторона первого. В первом примере sqldf в 3 раза медленнее, чем данные.стол а во втором его 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и установить ключ для fareness.
этот ответ был обновлен с момента первоначального ответа в декабре 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 splyr: 0.84 smatch: 0.12 s
подумал, что было бы интересно опубликовать бенчмарк с 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)обновление: я удалил данные.tableBad и plyr и ничего, кроме RStudio open (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'для согласованности вот оригинал со всеми и данными.таблица 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Я думаю, что эти данные слишком малы для новых данных.стол и 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пробовал 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)) )согласно запросу Аруна здесь вывод того, что вы предоставили мне для запуска:
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 с фреймом данных кажется менее эффективным способом обработки сводок. Это методы для сравнения точной функциональности данных.стол и dplyr с их методами структуры данных включена? Я бы почти предпочел отделить это, поскольку большинство данных нужно будет очистить, прежде чем мы group_by или создадим данные.стол. Это может быть вопросом вкуса, но я думаю, что самая важная часть-это то, насколько эффективно данные могут быть смоделированы.
С помощью функции слияния и ее необязательных параметров:
Inner join: merge(df1, df2) будет работать для этих примеров, потому что R автоматически соединяет фреймы по общим именам переменных, но вы, скорее всего, захотите указать merge (df1, df2, by = "CustomerId"), чтобы убедиться, что вы соответствовали только тем полям, которые вы хотели. Вы также можете использовать по.х и по.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)
Comments