Was ist der schnellste Weg, um data.frames in R zusammenzuführen / zu verbinden?


97

Zum Beispiel (nicht sicher, ob das repräsentativste Beispiel):

N <- 1e6
d1 <- data.frame(x=sample(N,N), y1=rnorm(N))
d2 <- data.frame(x=sample(N,N), y2=rnorm(N))

Das habe ich bisher:

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

Der richtige Weg, um den sqldf-Weg zu machen, wird unten von Gabor aufgezeigt: Erstellen Sie nur einen Index (z. B. auf d1) und verwenden Sie d1.main anstelle von d1 in der select-Anweisung (andernfalls wird der Index nicht verwendet). Das Timing beträgt in diesem Fall 13,6 Sekunden. Das Erstellen von Indizes für beide Tabellen ist auch im Fall data.table nicht erforderlich. Führen Sie einfach "dt2 <- data.table (d2)" aus, und das Timing beträgt 3,9 Sekunden.
Datasmurf

Beide Antworten liefern wertvolle Informationen, die es wert sind, beide zu lesen (obwohl nur eine "akzeptiert" werden kann).
Datasmurf

Sie vergleichen die linke Verbindung mit der inneren Verbindung in Ihrer Frage
jangorecki

Antworten:


46

Der Übereinstimmungsansatz funktioniert, wenn im zweiten Datenrahmen für jeden Schlüsselwert im ersten ein eindeutiger Schlüssel vorhanden ist. Wenn der zweite Datenrahmen Duplikate enthält, sind die Übereinstimmungs- und Zusammenführungsansätze nicht identisch. Das Match ist natürlich schneller, da es nicht so viel bewirkt. Insbesondere wird nie nach doppelten Schlüsseln gesucht. (Fortsetzung nach Code)

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

In dem in der Frage veröffentlichten sqldf-Code scheint es, dass Indizes für die beiden Tabellen verwendet wurden, aber tatsächlich werden sie in Tabellen platziert, die überschrieben wurden, bevor die sql-Auswahl jemals ausgeführt wird, und dies erklärt teilweise, warum Es ist so langsam. Die Idee von sqldf ist, dass die Datenrahmen in Ihrer R-Sitzung die Datenbank bilden, nicht die Tabellen in sqlite. Daher sucht der Code jedes Mal, wenn er auf einen nicht qualifizierten Tabellennamen verweist, in Ihrem R-Arbeitsbereich danach - nicht in der Hauptdatenbank von sqlite. Daher wird in der angezeigten select-Anweisung d1 und d2 aus dem Arbeitsbereich in die Hauptdatenbank von sqlite eingelesen, wobei diejenigen mit den Indizes überlastet werden. Infolgedessen wird ein Join ohne Indizes ausgeführt. Wenn Sie die Versionen von d1 und d2 verwenden möchten, die sich in der Hauptdatenbank von sqlite befinden, müssen Sie sie als main.d1 und main bezeichnen. d2 und nicht als d1 und d2. Wenn Sie versuchen, die Ausführung so schnell wie möglich zu gestalten, beachten Sie, dass bei einem einfachen Join keine Indizes für beide Tabellen verwendet werden können, sodass Sie Zeit beim Erstellen eines der Indizes sparen können. Im folgenden Code veranschaulichen wir diese Punkte.

Es lohnt sich zu bemerken, dass die genaue Berechnung einen großen Unterschied machen kann, welches Paket am schnellsten ist. Zum Beispiel führen wir unten eine Zusammenführung und ein Aggregat durch. Wir sehen, dass die Ergebnisse für beide nahezu umgekehrt sind. Im ersten Beispiel vom schnellsten zum langsamsten erhalten wir: data.table, plyr, merge und sqldf, während im zweiten Beispiel sqldf, aggregat, data.table und plyr - fast das Gegenteil des ersten. Im ersten Beispiel ist sqldf 3x langsamer als data.table und im zweiten 200x schneller als plyr und 100-mal schneller als data.table. Unten zeigen wir den Eingabecode, die Ausgabezeiten für die Zusammenführung und die Ausgabezeiten für das Aggregat. Es ist auch erwähnenswert, dass sqldf auf einer Datenbank basiert und daher Objekte verarbeiten kann, die größer als R sind (wenn Sie das Argument dbname von sqldf verwenden), während die anderen Ansätze auf die Verarbeitung im Hauptspeicher beschränkt sind. Wir haben auch sqldf mit sqlite illustriert, aber es unterstützt auch die H2- und PostgreSQL-Datenbanken.

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"))
)

Die Ergebnisse der beiden Benchmark-Aufrufe, die die Zusammenführungsberechnungen vergleichen, sind:

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

Die Ausgabe des Benchmark-Aufrufs zum Vergleich der aggregierten Berechnungen lautet:

        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

Danke, Gabor. Hervorragende Punkte, ich habe einige Anpassungen über Kommentare zur ursprünglichen Frage vorgenommen. Eigentlich denke ich, dass sich die Reihenfolge auch im Fall "Zusammenführen" ändern kann, abhängig von der relativen Größe der Tabellen, der Vielzahl der Schlüssel usw. (deshalb habe ich gesagt, ich bin mir nicht sicher, ob mein Beispiel repräsentativ ist). Trotzdem ist es schön, all die verschiedenen Lösungen für das Problem zu sehen.
Datasmurf

Ich schätze auch den Kommentar zum Fall "Aggregation". Dies unterscheidet sich zwar von der Einrichtung "Zusammenführen" in der Frage, ist jedoch sehr relevant. Ich hätte es tatsächlich in einer separaten Frage gestellt, aber hier gibt es bereits eine. Stackoverflow.com/questions/3685492/… . Vielleicht möchten Sie auch dazu beitragen, da die sqldf-Lösung basierend auf den obigen Ergebnissen möglicherweise alle dort vorhandenen Antworten
übertrifft

40

Die 132 Sekunden, die in Gabors Ergebnissen für angegeben sind, data.tablesind tatsächlich Timing-Basisfunktionen colMeansund cbind(die Speicherzuweisung und das Kopieren, die durch die Verwendung dieser Funktionen induziert werden). Es gibt auch gute und schlechte Verwendungsmöglichkeiten 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)

Bitte beachten Sie, dass ich Plyr nicht gut kenne. Bitte erkundigen Sie sich bei Hadley, bevor Sie sich auf die plyrZeitangaben hier verlassen. Beachten Sie auch, dass in data.tabledo die Zeit zum Konvertieren data.tableund Festlegen des Schlüssels für die Fareness enthalten ist.


Diese Antwort wurde aktualisiert, seit sie ursprünglich im Dezember 2010 beantwortet wurde. Die vorherigen Benchmark-Ergebnisse sind unten aufgeführt. Bitte sehen Sie im Versionsverlauf dieser Antwort nach, was sich geändert hat.

              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

Da ddply nur mit Datenrahmen funktioniert, liefert dieses Beispiel die schlechteste Leistung. Ich hoffe, in einer zukünftigen Version eine bessere Schnittstelle für diese Art von allgemeiner Operation zu haben.
Hadley

1
Zu Ihrer Information: Sie können keine .InternalAufrufe in CRAN-Paketen verwenden (siehe CRAN-Repository-Richtlinie) .
Joshua Ulrich

@JoshuaUlrich Sie konnten, als die Antwort vor fast 2 Jahren geschrieben wurde, iirc. Ich werde diese Antwort aktualisieren, da sie jetzt data.tableautomatisch optimiert wird mean(ohne .Internalintern anzurufen ).
Matt Dowle

@MatthewDowle: Ja, ich bin mir nicht sicher, wann / ob es sich geändert hat. Ich weiß nur, dass es jetzt der Fall ist. Und es ist vollkommen in Ordnung in Ihrer Antwort, funktioniert einfach nicht in Paketen.
Joshua Ulrich

1
@AleksandrBlekh Danke. Ich habe Ihre Kommentare hier mit der vorhandenen Funktionsanforderung Nr. 599 verknüpft . Gehen wir dorthin. Ihr Beispielcode zeigt die forSchleife gut, das ist gut. Könnten Sie diesem Problem weitere Informationen zur "SEM-Analyse" hinzufügen? Zum Beispiel vermute ich, dass SEM = Rasterelektronenmikroskop? Wenn wir mehr über die Anwendung wissen, wird sie für uns interessanter und hilft uns, Prioritäten zu setzen.
Matt Dowle

16

Für einfache Aufgaben (eindeutige Werte auf beiden Seiten der Verknüpfung) verwende ich match:

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

Es ist weitaus schneller als das Zusammenführen (auf meinem Computer 0,13 bis 3,37 Sekunden).

Meine Timings:

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

4
Danke, Marek. Eine Erklärung, warum dies so schnell geht (erstellt eine Index- / Hash-Tabelle), finden Sie hier: tolstoy.newcastle.edu.au/R/help/01c/2739.html
datasmurf

11

Ich dachte, es wäre interessant, einen Benchmark mit dplyr in der Mischung zu veröffentlichen: (viele Dinge liefen)

            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)"

Gerade hinzugefügt:

dplyr = summarise(dt_dt, avx = mean(x), avy = mean(y))

und richten Sie die Daten für dplyr mit einer Datentabelle ein:

dt <- tbl_dt(d)
dt_dt <- group_by(dt, g1, g2)

Aktualisiert: Ich habe data.tableBad und plyr entfernt und nichts als RStudio geöffnet (i7, 16 GB RAM).

Mit data.table 1.9 und dplyr mit Datenrahmen:

            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

Mit data.table 1.9 und dplyr mit Datentabelle:

            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'

Aus Gründen der Konsistenz ist hier das Original mit all und data.table 1.9 und dplyr unter Verwendung einer Datentabelle:

            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

Ich denke, diese Daten sind zu klein für die neuen data.table und dplyr :)

Größerer Datensatz:

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)

Es dauerte ungefähr 10-13 GB RAM, um die Daten zu speichern, bevor der Benchmark ausgeführt wurde.

Ergebnisse:

            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

Versuchte eine Milliarde, sprengte aber Widder. 32 GB werden es problemlos bewältigen.


[Edit by Arun] (dotcomken, könnten Sie bitte diesen Code ausführen und Ihre Benchmarking-Ergebnisse einfügen? Danke).

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))
) 

Gemäß Aruns Anfrage hier die Ausgabe dessen, was Sie mir zur Verfügung gestellt haben:

        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

Entschuldigung für die Verwirrung, spät in der Nacht kam zu mir.

Die Verwendung von dplyr mit Datenrahmen scheint die weniger effiziente Methode zur Verarbeitung von Zusammenfassungen zu sein. Ist diese Methode, um die genaue Funktionalität von data.table und dplyr mit den enthaltenen Datenstrukturmethoden zu vergleichen? Ich würde es fast vorziehen, das zu trennen, da die meisten Daten bereinigt werden müssen, bevor wir group_by oder die data.table erstellen. Es könnte Geschmackssache sein, aber ich denke, der wichtigste Teil ist, wie effizient die Daten modelliert werden können.


1
Schönes Update. Vielen Dank. Ich denke, Ihre Maschine ist im Vergleich zu diesem Datensatz ein Biest. Wie groß ist Ihr L2-Cache (und L3, falls vorhanden)?
Arun

i7 L2 ist 2x256 KB 8-Wege, L3 ist 4 MB 16-Wege. 128 GB SSD, Win 7 auf einem Dell Inspiron
Dotcomken

1
Könnten Sie Ihr Beispiel neu formatieren? Ich bin ein bisschen verwirrt. Ist data.table (in diesem Beispiel) besser als dplyr? Wenn ja, unter welchen Umständen.
Csgillespie

1

Mithilfe der Zusammenführungsfunktion und ihrer optionalen Parameter:

Innerer Join: Merge (df1, df2) funktioniert für diese Beispiele, da R die Frames automatisch über allgemeine Variablennamen verbindet. Sie möchten jedoch höchstwahrscheinlich Merge (df1, df2, by = "CustomerId") angeben, um sicherzustellen, dass Sie dies tun wurden nur auf den von Ihnen gewünschten Feldern abgeglichen. Sie können auch die Parameter by.x und by.y verwenden, wenn die übereinstimmenden Variablen in den verschiedenen Datenrahmen unterschiedliche Namen haben.

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)

Die Frage war nach Leistung. Sie haben lediglich die Syntax für die Joins angegeben. Es ist zwar hilfreich, beantwortet aber nicht die Frage. Dieser Antwort fehlen Benchmark-Daten, die anhand der Beispiele des OP zeigen, dass die Leistung besser oder zumindest wettbewerbsfähig ist.
Michael Tuchman
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.