Hoe voeg je dataframes samen (samenvoegen) (binnen, buiten, links, rechts)

Gegeven twee dataframes:

df1 = data.frame(CustomerId = c(1:6), Product = c(rep("Toaster", 3), rep("Radio", 3)))
df2 = data.frame(CustomerId = c(2, 4, 6), State = c(rep("Alabama", 2), rep("Ohio", 1)))
df1
#  CustomerId Product
#           1 Toaster
#           2 Toaster
#           3 Toaster
#           4   Radio
#           5   Radio
#           6   Radio
df2
#  CustomerId   State
#           2 Alabama
#           4 Alabama
#           6    Ohio

Hoe kan ik databasestijlen doen, d.w.z. sql-stijl, joins? Dat wil zeggen, hoe krijg ik:

  • Een innerlijke join van df1 en df2:
    Retourneer alleen de rijen waarin de linkertabel overeenkomende sleutels heeft in de rechtertabel.
  • Een outer join van df1 en df2:
    Retourneert alle rijen van beide tabellen, voeg records van links samen die overeenkomende sleutels hebben in de rechtertabel.
  • Een linker outer join (of gewoon left join) van df1 en df2
    Retourneer alle rijen uit de linkertabel en alle rijen met overeenkomende sleutels uit de rechtertabel.
  • Een rechter buitenste join van df1 en df2
    Retourneer alle rijen uit de rechtertabel en alle rijen met overeenkomende sleutels uit de linkertabel.

Extra tegoed:

Hoe kan ik een selectie-instructie in SQL-stijl uitvoeren?


Antwoord 1, autoriteit 100%

Door de functie merge en de optionele parameters te gebruiken:

Inner join: merge(df1, df2) werkt voor deze voorbeelden omdat R automatisch de frames samenvoegt met gemeenschappelijke variabelenamen, maar u zou hoogstwaarschijnlijk merge(df1, df2, by = "CustomerId") willen specificeren om er zeker van te zijn dat u alleen overeenkomt met de velden die u wenste. U kunt ook de parameters by.x en by.y gebruiken als de overeenkomende variabelen verschillende namen hebben in de verschillende gegevensframes.

Outer join: merge(x = df1, y = df2, by = "CustomerId", all = TRUE)

Links buiten: merge(x = df1, y = df2, by = "CustomerId", all.x = TRUE)

Rechts buiten: merge(x = df1, y = df2, by = "CustomerId", all.y = TRUE)

Cross join: merge(x = df1, y = df2, by = NULL)

Net als bij de inner join, zou je waarschijnlijk expliciet “CustomerId” willen doorgeven aan R als de overeenkomende variabele. Ik denk dat het bijna altijd het beste is om expliciet de ID’s te vermelden waarop je wilt samenvoegen; het is veiliger als de ingevoerde data.frames onverwachts veranderen en later gemakkelijker te lezen zijn.

Je kunt meerdere kolommen samenvoegen door by een vector te geven, bijvoorbeeld by = c("CustomerId", "OrderId").

Als de kolomnamen waarop u wilt samenvoegen niet dezelfde zijn, kunt u bijvoorbeeld by.x = "CustomerId_in_df1", by.y = "CustomerId_in_df2" specificeren waarbij CustomerId_in_df1 is de naam van de kolom in het eerste dataframe en CustomerId_in_df2 is de naam van de kolom in het tweede dataframe. (Dit kunnen ook vectoren zijn als u op meerdere kolommen moet samenvoegen.)


Antwoord 2, autoriteit 16%

Ik zou aanraden om Gabor Grothendieck’s sqldf-pakket te bekijken, waarmee u deze bewerkingen in SQL kunt uitdrukken.

library(sqldf)
## inner join
df3 <- sqldf("SELECT CustomerId, Product, State 
              FROM df1
              JOIN df2 USING(CustomerID)")
## left join (substitute 'right' for right join)
df4 <- sqldf("SELECT CustomerId, Product, State 
              FROM df1
              LEFT JOIN df2 USING(CustomerID)")

Ik vind de SQL-syntaxis eenvoudiger en natuurlijker dan zijn R-equivalent (maar dit weerspiegelt misschien mijn RDBMS-vooroordeel).

Zie Gabor’s sqldf GitHub voor meer informatie over joins.


Antwoord 3, autoriteit 15%

Er is de data.table-benadering voor een inner join, die zeer tijd- en geheugenefficiënt is (en noodzakelijk voor sommige grotere data.frames):

library(data.table)
dt1 <- data.table(df1, key = "CustomerId") 
dt2 <- data.table(df2, key = "CustomerId")
joined.dt1.dt.2 <- dt1[dt2]

merge werkt ook op data.tables (omdat het generiek is en merge.data.table aanroept)

merge(dt1, dt2)

data.table gedocumenteerd op stackoverflow:
Een data.table samenvoegbewerking uitvoeren< br>
SQL-joins op externe sleutels vertalen naar R-gegevens. tabelsyntaxis
Efficiënte alternatieven om samen te voegen voor grotere data.frames R
Hoe maak je een basislink outer join met data.table in R?

Nog een andere optie is de functie join die te vinden is in de plyr pakket

library(plyr)
join(df1, df2,
     type = "inner")
#   CustomerId Product   State
# 1          2 Toaster Alabama
# 2          4   Radio Alabama
# 3          6   Radio    Ohio

Opties voor type: inner, left, right, full.

Van ?join: in tegenstelling tot merge, behoudt [join] de volgorde van x, ongeacht het type join.


Antwoord 4, autoriteit 14%

Je kunt ook meedoen met het geweldige dplyr-pakket van Hadley Wickham.

library(dplyr)
#make sure that CustomerId cols are both type numeric
#they ARE not using the provided code in question and dplyr will complain
df1$CustomerId <- as.numeric(df1$CustomerId)
df2$CustomerId <- as.numeric(df2$CustomerId)

Joins muteren: kolommen toevoegen aan df1 met behulp van overeenkomsten in df2

#inner
inner_join(df1, df2)
#left outer
left_join(df1, df2)
#right outer
right_join(df1, df2)
#alternate right outer
left_join(df2, df1)
#full join
full_join(df1, df2)

Samenvoegingen filteren: rijen in df1 uitfilteren, kolommen niet wijzigen

semi_join(df1, df2) #keep only observations in df1 that match in df2.
anti_join(df1, df2) #drops all observations in df1 that match in df2.

Antwoord 5, autoriteit 7%

Er zijn enkele goede voorbeelden om dit te doen op de R Wiki. Ik zal er hier een paar stelen:

Samenvoegmethode

Aangezien uw sleutels dezelfde naam hebben, is de korte manier om een ​​inner join uit te voeren merge():

merge(df1,df2)

een volledige inner join (alle records van beide tabellen) kan worden gemaakt met het trefwoord “all”:

merge(df1,df2, all=TRUE)

een linker outer join van df1 en df2:

merge(df1,df2, all.x=TRUE)

een rechter outer join van df1 en df2:

merge(df1,df2, all.y=TRUE)

je kunt ze omdraaien, een klap geven en ze naar beneden wrijven om de andere twee buitenste joins te krijgen waar je naar vroeg 🙂

Subscriptmethode

Een left outer join met df1 aan de linkerkant met behulp van een subscript-methode zou zijn:

df1[,"State"]<-df2[df1[ ,"Product"], "State"]

De andere combinatie van outer joins kan worden gemaakt door het subscript-voorbeeld van de linker outer join te vermengen. (ja, ik weet dat dat hetzelfde is als zeggen: “Ik laat het als een oefening voor de lezer…”)


Antwoord 6, autoriteit 6%

Nieuw in 2014:

Vooral als u ook geïnteresseerd bent in gegevensmanipulatie in het algemeen (inclusief sorteren, filteren, subsetten, samenvatten enz.), moet u zeker eens kijken naar dplyr, dat wordt geleverd met een verscheidenheid aan functies die allemaal zijn ontworpen om uw werk met dataframes en bepaalde andere databasetypen te vergemakkelijken. Het biedt zelfs een behoorlijk uitgebreide SQL-interface en zelfs een functie om (de meeste) SQL-code direct naar R te converteren.

De vier samenvoegen-gerelateerde functies in het dplyr-pakket zijn (om te citeren):

  • inner_join(x, y, by = NULL, copy = FALSE, ...): retourneert alle rijen van
    x waar er overeenkomende waarden zijn in y, en alle kolommen van x en y
  • left_join(x, y, by = NULL, copy = FALSE, ...): retourneert alle rijen van x en alle kolommen van x en y
  • semi_join(x, y, by = NULL, copy = FALSE, ...): retourneert alle rijen van x waar overeenkomende waarden zijn in
    y, alleen kolommen van x behouden.
  • anti_join(x, y, by = NULL, copy = FALSE, ...): retourneert alle rijen van x
    waar er geen overeenkomende waarden in y zijn, alleen kolommen van x behouden

Het is allemaal hier in detail.

Kolommen selecteren kan met select(df,"column"). Als dat niet SQL-achtig genoeg voor je is, dan is er de functie sql(), waarin je SQL-code kunt invoeren zoals het is, en het zal de bewerking uitvoeren die je hebt opgegeven, net zoals je aan het schrijven was R altijd mee (raadpleeg voor meer informatie de dplyr/databases vignet). Als het bijvoorbeeld correct wordt toegepast, selecteert sql("SELECT * FROM hflights") alle kolommen uit de “hflights” dplyr-tabel (een “tbl”).


Antwoord 7, autoriteit 6%

Update over data.table-methoden voor het samenvoegen van datasets. Zie onderstaande voorbeelden voor elk type join. Er zijn twee methoden, een van [.data.table wanneer tweede data.table wordt doorgegeven als het eerste argument voor subset, een andere manier is om de functie merge te gebruiken die te snel verzendt data.table methode.

df1 = data.frame(CustomerId = c(1:6), Product = c(rep("Toaster", 3), rep("Radio", 3)))
df2 = data.frame(CustomerId = c(2L, 4L, 7L), State = c(rep("Alabama", 2), rep("Ohio", 1))) # one value changed to show full outer join
library(data.table)
dt1 = as.data.table(df1)
dt2 = as.data.table(df2)
setkey(dt1, CustomerId)
setkey(dt2, CustomerId)
# right outer join keyed data.tables
dt1[dt2]
setkey(dt1, NULL)
setkey(dt2, NULL)
# right outer join unkeyed data.tables - use `on` argument
dt1[dt2, on = "CustomerId"]
# left outer join - swap dt1 with dt2
dt2[dt1, on = "CustomerId"]
# inner join - use `nomatch` argument
dt1[dt2, nomatch=NULL, on = "CustomerId"]
# anti join - use `!` operator
dt1[!dt2, on = "CustomerId"]
# inner join - using merge method
merge(dt1, dt2, by = "CustomerId")
# full outer join
merge(dt1, dt2, by = "CustomerId", all = TRUE)
# see ?merge.data.table arguments for other cases

Hieronder benchmarktests base R, sqldf, dplyr en data.table.
Benchmark test niet-gecodeerde/niet-geïndexeerde datasets.
Benchmark wordt uitgevoerd op 50M-1 rijen datasets, er zijn 50M-2 gemeenschappelijke waarden in de join-kolom, zodat elk scenario (binnen, links, rechts, volledig) kan worden getest en join is nog steeds niet triviaal om uit te voeren. Het is een type join dat algoritmen voor het aansluiten van putspanningen. De tijden zijn vanaf sqldf:0.4.11, dplyr:0.7.8, data.table:1.12.0.

# inner
Unit: seconds
   expr       min        lq      mean    median        uq       max neval
   base 111.66266 111.66266 111.66266 111.66266 111.66266 111.66266     1
  sqldf 624.88388 624.88388 624.88388 624.88388 624.88388 624.88388     1
  dplyr  51.91233  51.91233  51.91233  51.91233  51.91233  51.91233     1
     DT  10.40552  10.40552  10.40552  10.40552  10.40552  10.40552     1
# left
Unit: seconds
   expr        min         lq       mean     median         uq        max 
   base 142.782030 142.782030 142.782030 142.782030 142.782030 142.782030     
  sqldf 613.917109 613.917109 613.917109 613.917109 613.917109 613.917109     
  dplyr  49.711912  49.711912  49.711912  49.711912  49.711912  49.711912     
     DT   9.674348   9.674348   9.674348   9.674348   9.674348   9.674348       
# right
Unit: seconds
   expr        min         lq       mean     median         uq        max
   base 122.366301 122.366301 122.366301 122.366301 122.366301 122.366301     
  sqldf 611.119157 611.119157 611.119157 611.119157 611.119157 611.119157     
  dplyr  50.384841  50.384841  50.384841  50.384841  50.384841  50.384841     
     DT   9.899145   9.899145   9.899145   9.899145   9.899145   9.899145     
# full
Unit: seconds
  expr       min        lq      mean    median        uq       max neval
  base 141.79464 141.79464 141.79464 141.79464 141.79464 141.79464     1
 dplyr  94.66436  94.66436  94.66436  94.66436  94.66436  94.66436     1
    DT  21.62573  21.62573  21.62573  21.62573  21.62573  21.62573     1

Houd er rekening mee dat er andere soorten joins zijn die u kunt uitvoeren met data.table:
update bij deelname – als u waarden van een andere tabel naar uw hoofdtabel wilt opzoeken
aggregeren bij deelname – als u wilt aggregeren op de sleutel waaraan u deelneemt, hoeft u niet alle deelnameresultaten te realiseren

overlappende join – als je wilt samenvoegen op bereik
rolling join – als je wilt dat mergen kan matchen met waarden uit voorgaande/volgende rijen door ze naar voren of naar achteren te rollen
niet-gelijkwaardige deelname – als uw deelnamevoorwaarde niet gelijk is

Code om te reproduceren:

library(microbenchmark)
library(sqldf)
library(dplyr)
library(data.table)
sapply(c("sqldf","dplyr","data.table"), packageVersion, simplify=FALSE)
n = 5e7
set.seed(108)
df1 = data.frame(x=sample(n,n-1L), y1=rnorm(n-1L))
df2 = data.frame(x=sample(n,n-1L), y2=rnorm(n-1L))
dt1 = as.data.table(df1)
dt2 = as.data.table(df2)
mb = list()
# inner join
microbenchmark(times = 1L,
               base = merge(df1, df2, by = "x"),
               sqldf = sqldf("SELECT * FROM df1 INNER JOIN df2 ON df1.x = df2.x"),
               dplyr = inner_join(df1, df2, by = "x"),
               DT = dt1[dt2, nomatch=NULL, on = "x"]) -> mb$inner
# left outer join
microbenchmark(times = 1L,
               base = merge(df1, df2, by = "x", all.x = TRUE),
               sqldf = sqldf("SELECT * FROM df1 LEFT OUTER JOIN df2 ON df1.x = df2.x"),
               dplyr = left_join(df1, df2, by = c("x"="x")),
               DT = dt2[dt1, on = "x"]) -> mb$left
# right outer join
microbenchmark(times = 1L,
               base = merge(df1, df2, by = "x", all.y = TRUE),
               sqldf = sqldf("SELECT * FROM df2 LEFT OUTER JOIN df1 ON df2.x = df1.x"),
               dplyr = right_join(df1, df2, by = "x"),
               DT = dt1[dt2, on = "x"]) -> mb$right
# full outer join
microbenchmark(times = 1L,
               base = merge(df1, df2, by = "x", all = TRUE),
               dplyr = full_join(df1, df2, by = "x"),
               DT = merge(dt1, dt2, by = "x", all = TRUE)) -> mb$full
lapply(mb, print) -> nul

Antwoord 8, autoriteit 2%

dplyr sinds 0.4 heeft al die joins geïmplementeerd, inclusief outer_join, maar het was vermeldenswaard dat voor de eerste paar releases vóór 0.4 het outer_join niet aanbood , en als gevolg daarvan was er een hele tijd daarna een hele slechte hacky workaround-gebruikerscode rondzwevend (je kunt dergelijke code nog steeds vinden in SO, Kaggle-antwoorden, github uit die periode. Daarom heeft dit antwoord nog steeds een nuttig doel. )

Deelname-gerelateerde hoogtepunten van releases:

v0.5 (6/2016)

  • Handling voor POSIXct-type, tijdzones, duplicaten, verschillende factorniveaus. Betere fouten en waarschuwingen.
  • Nieuw achtervoegselargument om te bepalen welk achtervoegsel dubbele variabelenamen ontvangen (#1296)

v0.4.0 (1/2015)

  • Implementeer de juiste join en outer join (#96)
  • Muterende joins, die nieuwe variabelen aan de ene tabel toevoegen uit overeenkomende rijen in een andere. Samenvoegingen filteren, waarmee waarnemingen uit de ene tabel worden gefilterd op basis van het al dan niet overeenkomen met een waarneming in de andere tabel.

v0.3 (10/2014)

  • Kan nu left_join door verschillende variabelen in elke tabel: df1 %>% left_join(df2, c(“var1” = “var2”))

v0.2 (5/2014)

  • *_join() herschikt niet langer kolomnamen (#324)

v0.1.3 (4/2014)

Oplossingen per hadley’s opmerkingen in dat nummer:

  • right_join(x,y) is hetzelfde als left_join(y,x) in termen van de rijen, alleen de kolommen zullen een andere volgorde hebben. Gemakkelijk omzeild met select(new_column_order)
  • outer_join is in feite union(left_join(x, y), right_join(x, y)) – d.w.z. behoud alle rijen in beide dataframes.

Antwoord 9, autoriteit 2%

Voor het geval van een left join met een 0..*:0..1 kardinaliteit of een right join met een 0..1:0..* kardinaliteit is het mogelijk om de unilaterale kolommen van de joiner (de 0..1 tabel) direct toe te wijzen aan de joinee (de 0..* tabel) , en vermijd zo het creëren van een geheel nieuwe gegevenstabel. Dit vereist het matchen van de sleutelkolommen van de joinee naar de joiner en het indexeren + ordenen van de rijen van de joiner dienovereenkomstig voor de toewijzing.

Als de sleutel een enkele kolom is, kunnen we een enkele aanroep gebruiken naar match() om de matching uit te voeren. Dit is het geval dat ik in dit antwoord zal behandelen.

Hier is een voorbeeld gebaseerd op de OP, behalve dat ik een extra rij heb toegevoegd aan df2 met een id van 7 om het geval van een niet-overeenkomende sleutel in de joiner te testen. Dit is in feite df1 left join df2:

df1 <- data.frame(CustomerId=1:6,Product=c(rep('Toaster',3L),rep('Radio',3L)));
df2 <- data.frame(CustomerId=c(2L,4L,6L,7L),State=c(rep('Alabama',2L),'Ohio','Texas'));
df1[names(df2)[-1L]] <- df2[match(df1[,1L],df2[,1L]),-1L];
df1;
##   CustomerId Product   State
## 1          1 Toaster    <NA>
## 2          2 Toaster Alabama
## 3          3 Toaster    <NA>
## 4          4   Radio Alabama
## 5          5   Radio    <NA>
## 6          6   Radio    Ohio

In het bovenstaande heb ik een aanname hard gecodeerd dat de sleutelkolom de eerste kolom is van beide invoertabellen. Ik zou willen stellen dat dit in het algemeen geen onredelijke veronderstelling is, aangezien, als je een data.frame met een sleutelkolom hebt, het vreemd zou zijn als het niet was ingesteld als de eerste kolom van het data.frame van het begin. En u kunt de kolommen altijd opnieuw ordenen om het zo te maken. Een voordelig gevolg van deze aanname is dat de naam van de sleutelkolom niet hard gecodeerd hoeft te zijn, hoewel ik veronderstel dat het slechts de ene aanname door de andere vervangt. Beknoptheid is een ander voordeel van integer indexeren, evenals snelheid. In de onderstaande benchmarks zal ik de implementatie wijzigen om stringnaamindexering te gebruiken om overeen te komen met de concurrerende implementaties.

Ik denk dat dit een bijzonder geschikte oplossing is als je meerdere tafels hebt die je wilt laten samenkomen tegen een enkele grote tafel. Het herhaaldelijk opnieuw opbouwen van de hele tabel voor elke samenvoeging zou onnodig en inefficiënt zijn.

Aan de andere kant, als u wilt dat de deelnemer om welke reden dan ook ongewijzigd blijft tijdens deze bewerking, dan kan deze oplossing niet worden gebruikt, omdat het de deelnemer direct wijzigt. Hoewel je in dat geval gewoon een kopie zou kunnen maken en de in-place opdracht(en) op de kopie zou kunnen uitvoeren.


Als een kanttekening heb ik kort gekeken naar mogelijke bijpassende oplossingen voor sleutels met meerdere kolommen. Helaas waren de enige overeenkomende oplossingen die ik vond:

  • inefficiënte aaneenschakelingen. bijv. match(interaction(df1$a,df1$b),interaction(df2$a,df2$b)), of hetzelfde idee met paste().
  • inefficiënte cartesische voegwoorden, b.v. outer(df1$a,df2$a,`==`) & outer(df1$b,df2$b,`==`).
  • base R merge() en gelijkwaardige pakketgebaseerde samenvoegfuncties, die altijd een nieuwe tabel toewijzen om het samengevoegde resultaat te retourneren, en dus niet geschikt zijn voor een op toewijzing gebaseerde oplossing.

Zie bijvoorbeeld Meerdere kolommen op verschillende dataframes matchen en een andere kolom als resultaat krijgen, twee kolommen vergelijken met twee andere kolommen, Overeenkomen met meerdere kolommen, en de dupe van deze vraag waar ik oorspronkelijk met de in-place oplossing kwam, Combineer twee dataframes met verschillend aantal rijen in R.


Benchmarking

Ik besloot om mijn eigen benchmarking uit te voeren om te zien hoe de aanpak van de in-place toewijzing zich verhoudt tot de andere oplossingen die in deze vraag zijn aangeboden.

Testcode:

library(microbenchmark);
library(data.table);
library(sqldf);
library(plyr);
library(dplyr);
solSpecs <- list(
    merge=list(testFuncs=list(
        inner=function(df1,df2,key) merge(df1,df2,key),
        left =function(df1,df2,key) merge(df1,df2,key,all.x=T),
        right=function(df1,df2,key) merge(df1,df2,key,all.y=T),
        full =function(df1,df2,key) merge(df1,df2,key,all=T)
    )),
    data.table.unkeyed=list(argSpec='data.table.unkeyed',testFuncs=list(
        inner=function(dt1,dt2,key) dt1[dt2,on=key,nomatch=0L,allow.cartesian=T],
        left =function(dt1,dt2,key) dt2[dt1,on=key,allow.cartesian=T],
        right=function(dt1,dt2,key) dt1[dt2,on=key,allow.cartesian=T],
        full =function(dt1,dt2,key) merge(dt1,dt2,key,all=T,allow.cartesian=T) ## calls merge.data.table()
    )),
    data.table.keyed=list(argSpec='data.table.keyed',testFuncs=list(
        inner=function(dt1,dt2) dt1[dt2,nomatch=0L,allow.cartesian=T],
        left =function(dt1,dt2) dt2[dt1,allow.cartesian=T],
        right=function(dt1,dt2) dt1[dt2,allow.cartesian=T],
        full =function(dt1,dt2) merge(dt1,dt2,all=T,allow.cartesian=T) ## calls merge.data.table()
    )),
    sqldf.unindexed=list(testFuncs=list( ## note: must pass connection=NULL to avoid running against the live DB connection, which would result in collisions with the residual tables from the last query upload
        inner=function(df1,df2,key) sqldf(paste0('select * from df1 inner join df2 using(',paste(collapse=',',key),')'),connection=NULL),
        left =function(df1,df2,key) sqldf(paste0('select * from df1 left join df2 using(',paste(collapse=',',key),')'),connection=NULL),
        right=function(df1,df2,key) sqldf(paste0('select * from df2 left join df1 using(',paste(collapse=',',key),')'),connection=NULL) ## can't do right join proper, not yet supported; inverted left join is equivalent
        ##full =function(df1,df2,key) sqldf(paste0('select * from df1 full join df2 using(',paste(collapse=',',key),')'),connection=NULL) ## can't do full join proper, not yet supported; possible to hack it with a union of left joins, but too unreasonable to include in testing
    )),
    sqldf.indexed=list(testFuncs=list( ## important: requires an active DB connection with preindexed main.df1 and main.df2 ready to go; arguments are actually ignored
        inner=function(df1,df2,key) sqldf(paste0('select * from main.df1 inner join main.df2 using(',paste(collapse=',',key),')')),
        left =function(df1,df2,key) sqldf(paste0('select * from main.df1 left join main.df2 using(',paste(collapse=',',key),')')),
        right=function(df1,df2,key) sqldf(paste0('select * from main.df2 left join main.df1 using(',paste(collapse=',',key),')')) ## can't do right join proper, not yet supported; inverted left join is equivalent
        ##full =function(df1,df2,key) sqldf(paste0('select * from main.df1 full join main.df2 using(',paste(collapse=',',key),')')) ## can't do full join proper, not yet supported; possible to hack it with a union of left joins, but too unreasonable to include in testing
    )),
    plyr=list(testFuncs=list(
        inner=function(df1,df2,key) join(df1,df2,key,'inner'),
        left =function(df1,df2,key) join(df1,df2,key,'left'),
        right=function(df1,df2,key) join(df1,df2,key,'right'),
        full =function(df1,df2,key) join(df1,df2,key,'full')
    )),
    dplyr=list(testFuncs=list(
        inner=function(df1,df2,key) inner_join(df1,df2,key),
        left =function(df1,df2,key) left_join(df1,df2,key),
        right=function(df1,df2,key) right_join(df1,df2,key),
        full =function(df1,df2,key) full_join(df1,df2,key)
    )),
    in.place=list(testFuncs=list(
        left =function(df1,df2,key) { cns <- setdiff(names(df2),key); df1[cns] <- df2[match(df1[,key],df2[,key]),cns]; df1; },
        right=function(df1,df2,key) { cns <- setdiff(names(df1),key); df2[cns] <- df1[match(df2[,key],df1[,key]),cns]; df2; }
    ))
);
getSolTypes <- function() names(solSpecs);
getJoinTypes <- function() unique(unlist(lapply(solSpecs,function(x) names(x$testFuncs))));
getArgSpec <- function(argSpecs,key=NULL) if (is.null(key)) argSpecs$default else argSpecs[[key]];
initSqldf <- function() {
    sqldf(); ## creates sqlite connection on first run, cleans up and closes existing connection otherwise
    if (exists('sqldfInitFlag',envir=globalenv(),inherits=F) && sqldfInitFlag) { ## false only on first run
        sqldf(); ## creates a new connection
    } else {
        assign('sqldfInitFlag',T,envir=globalenv()); ## set to true for the one and only time
    }; ## end if
    invisible();
}; ## end initSqldf()
setUpBenchmarkCall <- function(argSpecs,joinType,solTypes=getSolTypes(),env=parent.frame()) {
    ## builds and returns a list of expressions suitable for passing to the list argument of microbenchmark(), and assigns variables to resolve symbol references in those expressions
    callExpressions <- list();
    nms <- character();
    for (solType in solTypes) {
        testFunc <- solSpecs[[solType]]$testFuncs[[joinType]];
        if (is.null(testFunc)) next; ## this join type is not defined for this solution type
        testFuncName <- paste0('tf.',solType);
        assign(testFuncName,testFunc,envir=env);
        argSpecKey <- solSpecs[[solType]]$argSpec;
        argSpec <- getArgSpec(argSpecs,argSpecKey);
        argList <- setNames(nm=names(argSpec$args),vector('list',length(argSpec$args)));
        for (i in seq_along(argSpec$args)) {
            argName <- paste0('tfa.',argSpecKey,i);
            assign(argName,argSpec$args[[i]],envir=env);
            argList[[i]] <- if (i%in%argSpec$copySpec) call('copy',as.symbol(argName)) else as.symbol(argName);
        }; ## end for
        callExpressions[[length(callExpressions)+1L]] <- do.call(call,c(list(testFuncName),argList),quote=T);
        nms[length(nms)+1L] <- solType;
    }; ## end for
    names(callExpressions) <- nms;
    callExpressions;
}; ## end setUpBenchmarkCall()
harmonize <- function(res) {
    res <- as.data.frame(res); ## coerce to data.frame
    for (ci in which(sapply(res,is.factor))) res[[ci]] <- as.character(res[[ci]]); ## coerce factor columns to character
    for (ci in which(sapply(res,is.logical))) res[[ci]] <- as.integer(res[[ci]]); ## coerce logical columns to integer (works around sqldf quirk of munging logicals to integers)
    ##for (ci in which(sapply(res,inherits,'POSIXct'))) res[[ci]] <- as.double(res[[ci]]); ## coerce POSIXct columns to double (works around sqldf quirk of losing POSIXct class) ----- POSIXct doesn't work at all in sqldf.indexed
    res <- res[order(names(res))]; ## order columns
    res <- res[do.call(order,res),]; ## order rows
    res;
}; ## end harmonize()
checkIdentical <- function(argSpecs,solTypes=getSolTypes()) {
    for (joinType in getJoinTypes()) {
        callExpressions <- setUpBenchmarkCall(argSpecs,joinType,solTypes);
        if (length(callExpressions)<2L) next;
        ex <- harmonize(eval(callExpressions[[1L]]));
        for (i in seq(2L,len=length(callExpressions)-1L)) {
            y <- harmonize(eval(callExpressions[[i]]));
            if (!isTRUE(all.equal(ex,y,check.attributes=F))) {
                ex <<- ex;
                y <<- y;
                solType <- names(callExpressions)[i];
                stop(paste0('non-identical: ',solType,' ',joinType,'.'));
            }; ## end if
        }; ## end for
    }; ## end for
    invisible();
}; ## end checkIdentical()
testJoinType <- function(argSpecs,joinType,solTypes=getSolTypes(),metric=NULL,times=100L) {
    callExpressions <- setUpBenchmarkCall(argSpecs,joinType,solTypes);
    bm <- microbenchmark(list=callExpressions,times=times);
    if (is.null(metric)) return(bm);
    bm <- summary(bm);
    res <- setNames(nm=names(callExpressions),bm[[metric]]);
    attr(res,'unit') <- attr(bm,'unit');
    res;
}; ## end testJoinType()
testAllJoinTypes <- function(argSpecs,solTypes=getSolTypes(),metric=NULL,times=100L) {
    joinTypes <- getJoinTypes();
    resList <- setNames(nm=joinTypes,lapply(joinTypes,function(joinType) testJoinType(argSpecs,joinType,solTypes,metric,times)));
    if (is.null(metric)) return(resList);
    units <- unname(unlist(lapply(resList,attr,'unit')));
    res <- do.call(data.frame,c(list(join=joinTypes),setNames(nm=solTypes,rep(list(rep(NA_real_,length(joinTypes))),length(solTypes))),list(unit=units,stringsAsFactors=F)));
    for (i in seq_along(resList)) res[i,match(names(resList[[i]]),names(res))] <- resList[[i]];
    res;
}; ## end testAllJoinTypes()
testGrid <- function(makeArgSpecsFunc,sizes,overlaps,solTypes=getSolTypes(),joinTypes=getJoinTypes(),metric='median',times=100L) {
    res <- expand.grid(size=sizes,overlap=overlaps,joinType=joinTypes,stringsAsFactors=F);
    res[solTypes] <- NA_real_;
    res$unit <- NA_character_;
    for (ri in seq_len(nrow(res))) {
        size <- res$size[ri];
        overlap <- res$overlap[ri];
        joinType <- res$joinType[ri];
        argSpecs <- makeArgSpecsFunc(size,overlap);
        checkIdentical(argSpecs,solTypes);
        cur <- testJoinType(argSpecs,joinType,solTypes,metric,times);
        res[ri,match(names(cur),names(res))] <- cur;
        res$unit[ri] <- attr(cur,'unit');
    }; ## end for
    res;
}; ## end testGrid()

Hier is een benchmark van het voorbeeld op basis van het OP dat ik eerder heb gedemonstreerd:

## OP's example, supplemented with a non-matching row in df2
argSpecs <- list(
    default=list(copySpec=1:2,args=list(
        df1 <- data.frame(CustomerId=1:6,Product=c(rep('Toaster',3L),rep('Radio',3L))),
        df2 <- data.frame(CustomerId=c(2L,4L,6L,7L),State=c(rep('Alabama',2L),'Ohio','Texas')),
        'CustomerId'
    )),
    data.table.unkeyed=list(copySpec=1:2,args=list(
        as.data.table(df1),
        as.data.table(df2),
        'CustomerId'
    )),
    data.table.keyed=list(copySpec=1:2,args=list(
        setkey(as.data.table(df1),CustomerId),
        setkey(as.data.table(df2),CustomerId)
    ))
);
## prepare sqldf
initSqldf();
sqldf('create index df1_key on df1(CustomerId);'); ## upload and create an sqlite index on df1
sqldf('create index df2_key on df2(CustomerId);'); ## upload and create an sqlite index on df2
checkIdentical(argSpecs);
testAllJoinTypes(argSpecs,metric='median');
##    join    merge data.table.unkeyed data.table.keyed sqldf.unindexed sqldf.indexed      plyr    dplyr in.place         unit
## 1 inner  644.259           861.9345          923.516        9157.752      1580.390  959.2250 270.9190       NA microseconds
## 2  left  713.539           888.0205          910.045        8820.334      1529.714  968.4195 270.9185 224.3045 microseconds
## 3 right 1221.804           909.1900          923.944        8930.668      1533.135 1063.7860 269.8495 218.1035 microseconds
## 4  full 1302.203          3107.5380         3184.729              NA            NA 1593.6475 270.7055       NA microseconds

Hier vergelijk ik willekeurige invoergegevens, waarbij ik verschillende schalen en verschillende patronen van sleuteloverlap tussen de twee invoertabellen probeer. Deze benchmark is nog steeds beperkt tot het geval van een integer-sleutel met één kolom. Om er zeker van te zijn dat de interne oplossing zou werken voor zowel de linker- als de rechterjoins van dezelfde tabellen, gebruiken alle willekeurige testgegevens 0..1:0..1-kardinaliteit. Dit wordt geïmplementeerd door bemonstering zonder vervanging van de sleutelkolom van het eerste data.frame bij het genereren van de sleutelkolom van het tweede data.frame.

makeArgSpecs.singleIntegerKey.optionalOneToOne <- function(size,overlap) {
    com <- as.integer(size*overlap);
    argSpecs <- list(
        default=list(copySpec=1:2,args=list(
            df1 <- data.frame(id=sample(size),y1=rnorm(size),y2=rnorm(size)),
            df2 <- data.frame(id=sample(c(if (com>0L) sample(df1$id,com) else integer(),seq(size+1L,len=size-com))),y3=rnorm(size),y4=rnorm(size)),
            'id'
        )),
        data.table.unkeyed=list(copySpec=1:2,args=list(
            as.data.table(df1),
            as.data.table(df2),
            'id'
        )),
        data.table.keyed=list(copySpec=1:2,args=list(
            setkey(as.data.table(df1),id),
            setkey(as.data.table(df2),id)
        ))
    );
    ## prepare sqldf
    initSqldf();
    sqldf('create index df1_key on df1(id);'); ## upload and create an sqlite index on df1
    sqldf('create index df2_key on df2(id);'); ## upload and create an sqlite index on df2
    argSpecs;
}; ## end makeArgSpecs.singleIntegerKey.optionalOneToOne()
## cross of various input sizes and key overlaps
sizes <- c(1e1L,1e3L,1e6L);
overlaps <- c(0.99,0.5,0.01);
system.time({ res <- testGrid(makeArgSpecs.singleIntegerKey.optionalOneToOne,sizes,overlaps); });
##     user   system  elapsed
## 22024.65 12308.63 34493.19

Ik heb wat code geschreven om log-log plots van de bovenstaande resultaten te maken. Ik heb voor elk overlappercentage een aparte plot gegenereerd. Het is een beetje onoverzichtelijk, maar ik vind het leuk om alle oplossingstypen en join-typen in dezelfde plot weer te geven.

Ik heb spline-interpolatie gebruikt om een ​​vloeiende curve weer te geven voor elke combinatie van oplossing/verbindingstype, getekend met individuele pch-symbolen. Het join-type wordt weergegeven door het pch-symbool, met een punt voor binnen, linker- en rechterhoekhaken voor links en rechts, en een ruit voor vol. Het type oplossing wordt weergegeven door de kleur zoals weergegeven in de legenda.

plotRes <- function(res,titleFunc,useFloor=F) {
    solTypes <- setdiff(names(res),c('size','overlap','joinType','unit')); ## derive from res
    normMult <- c(microseconds=1e-3,milliseconds=1); ## normalize to milliseconds
    joinTypes <- getJoinTypes();
    cols <- c(merge='purple',data.table.unkeyed='blue',data.table.keyed='#00DDDD',sqldf.unindexed='brown',sqldf.indexed='orange',plyr='red',dplyr='#00BB00',in.place='magenta');
    pchs <- list(inner=20L,left='<',right='>',full=23L);
    cexs <- c(inner=0.7,left=1,right=1,full=0.7);
    NP <- 60L;
    ord <- order(decreasing=T,colMeans(res[res$size==max(res$size),solTypes],na.rm=T));
    ymajors <- data.frame(y=c(1,1e3),label=c('1ms','1s'),stringsAsFactors=F);
    for (overlap in unique(res$overlap)) {
        x1 <- res[res$overlap==overlap,];
        x1[solTypes] <- x1[solTypes]*normMult[x1$unit]; x1$unit <- NULL;
        xlim <- c(1e1,max(x1$size));
        xticks <- 10^seq(log10(xlim[1L]),log10(xlim[2L]));
        ylim <- c(1e-1,10^((if (useFloor) floor else ceiling)(log10(max(x1[solTypes],na.rm=T))))); ## use floor() to zoom in a little more, only sqldf.unindexed will break above, but xpd=NA will keep it visible
        yticks <- 10^seq(log10(ylim[1L]),log10(ylim[2L]));
        yticks.minor <- rep(yticks[-length(yticks)],each=9L)*1:9;
        plot(NA,xlim=xlim,ylim=ylim,xaxs='i',yaxs='i',axes=F,xlab='size (rows)',ylab='time (ms)',log='xy');
        abline(v=xticks,col='lightgrey');
        abline(h=yticks.minor,col='lightgrey',lty=3L);
        abline(h=yticks,col='lightgrey');
        axis(1L,xticks,parse(text=sprintf('10^%d',as.integer(log10(xticks)))));
        axis(2L,yticks,parse(text=sprintf('10^%d',as.integer(log10(yticks)))),las=1L);
        axis(4L,ymajors$y,ymajors$label,las=1L,tick=F,cex.axis=0.7,hadj=0.5);
        for (joinType in rev(joinTypes)) { ## reverse to draw full first, since it's larger and would be more obtrusive if drawn last
            x2 <- x1[x1$joinType==joinType,];
            for (solType in solTypes) {
                if (any(!is.na(x2[[solType]]))) {
                    xy <- spline(x2$size,x2[[solType]],xout=10^(seq(log10(x2$size[1L]),log10(x2$size[nrow(x2)]),len=NP)));
                    points(xy$x,xy$y,pch=pchs[[joinType]],col=cols[solType],cex=cexs[joinType],xpd=NA);
                }; ## end if
            }; ## end for
        }; ## end for
        ## custom legend
        ## due to logarithmic skew, must do all distance calcs in inches, and convert to user coords afterward
        ## the bottom-left corner of the legend will be defined in normalized figure coords, although we can convert to inches immediately
        leg.cex <- 0.7;
        leg.x.in <- grconvertX(0.275,'nfc','in');
        leg.y.in <- grconvertY(0.6,'nfc','in');
        leg.x.user <- grconvertX(leg.x.in,'in');
        leg.y.user <- grconvertY(leg.y.in,'in');
        leg.outpad.w.in <- 0.1;
        leg.outpad.h.in <- 0.1;
        leg.midpad.w.in <- 0.1;
        leg.midpad.h.in <- 0.1;
        leg.sol.w.in <- max(strwidth(solTypes,'in',leg.cex));
        leg.sol.h.in <- max(strheight(solTypes,'in',leg.cex))*1.5; ## multiplication factor for greater line height
        leg.join.w.in <- max(strheight(joinTypes,'in',leg.cex))*1.5; ## ditto
        leg.join.h.in <- max(strwidth(joinTypes,'in',leg.cex));
        leg.main.w.in <- leg.join.w.in*length(joinTypes);
        leg.main.h.in <- leg.sol.h.in*length(solTypes);
        leg.x2.user <- grconvertX(leg.x.in+leg.outpad.w.in*2+leg.main.w.in+leg.midpad.w.in+leg.sol.w.in,'in');
        leg.y2.user <- grconvertY(leg.y.in+leg.outpad.h.in*2+leg.main.h.in+leg.midpad.h.in+leg.join.h.in,'in');
        leg.cols.x.user <- grconvertX(leg.x.in+leg.outpad.w.in+leg.join.w.in*(0.5+seq(0L,length(joinTypes)-1L)),'in');
        leg.lines.y.user <- grconvertY(leg.y.in+leg.outpad.h.in+leg.main.h.in-leg.sol.h.in*(0.5+seq(0L,length(solTypes)-1L)),'in');
        leg.sol.x.user <- grconvertX(leg.x.in+leg.outpad.w.in+leg.main.w.in+leg.midpad.w.in,'in');
        leg.join.y.user <- grconvertY(leg.y.in+leg.outpad.h.in+leg.main.h.in+leg.midpad.h.in,'in');
        rect(leg.x.user,leg.y.user,leg.x2.user,leg.y2.user,col='white');
        text(leg.sol.x.user,leg.lines.y.user,solTypes[ord],cex=leg.cex,pos=4L,offset=0);
        text(leg.cols.x.user,leg.join.y.user,joinTypes,cex=leg.cex,pos=4L,offset=0,srt=90); ## srt rotation applies *after* pos/offset positioning
        for (i in seq_along(joinTypes)) {
            joinType <- joinTypes[i];
            points(rep(leg.cols.x.user[i],length(solTypes)),ifelse(colSums(!is.na(x1[x1$joinType==joinType,solTypes[ord]]))==0L,NA,leg.lines.y.user),pch=pchs[[joinType]],col=cols[solTypes[ord]]);
        }; ## end for
        title(titleFunc(overlap));
        readline(sprintf('overlap %.02f',overlap));
    }; ## end for
}; ## end plotRes()
titleFunc <- function(overlap) sprintf('R merge solutions: single-column integer key, 0..1:0..1 cardinality, %d%% overlap',as.integer(overlap*100));
plotRes(res,titleFunc,T);

R-merge-benchmark-enkele-kolom-integer-sleutel-optioneel-één-op-één-99

R-merge-benchmark-enkele-kolom-integer-sleutel-optioneel-één-op-één-50

R-merge-benchmark-enkele-kolom-integer-sleutel-optioneel-één-op-één-1


Hier is een tweede grootschalige benchmark die zwaarder is, zowel wat betreft het aantal en de typen sleutelkolommen als de kardinaliteit. Voor deze benchmark gebruik ik drie sleutelkolommen: één karakter, één geheel getal en één logisch, zonder beperkingen op kardinaliteit (dat wil zeggen, 0..*:0..*). (Over het algemeen is het niet aan te raden om sleutelkolommen met dubbele of complexe waarden te definiëren vanwege de complicaties bij de vergelijking met drijvende komma’s, en eigenlijk gebruikt niemand ooit het onbewerkte type, laat staan ​​voor sleutelkolommen, dus ik heb die typen niet in de sleutel opgenomen Ook heb ik ter informatie in eerste instantie geprobeerd vier sleutelkolommen te gebruiken door een POSIXct-sleutelkolom op te nemen, maar het POSIXct-type werkte om de een of andere reden niet goed met de oplossing sqldf.indexed, mogelijk vanwege anomalieën in de vergelijking met drijvende komma’s, dus ik heb het verwijderd.)

makeArgSpecs.assortedKey.optionalManyToMany <- function(size,overlap,uniquePct=75) {
    ## number of unique keys in df1
    u1Size <- as.integer(size*uniquePct/100);
    ## (roughly) divide u1Size into bases, so we can use expand.grid() to produce the required number of unique key values with repetitions within individual key columns
    ## use ceiling() to ensure we cover u1Size; will truncate afterward
    u1SizePerKeyColumn <- as.integer(ceiling(u1Size^(1/3)));
    ## generate the unique key values for df1
    keys1 <- expand.grid(stringsAsFactors=F,
        idCharacter=replicate(u1SizePerKeyColumn,paste(collapse='',sample(letters,sample(4:12,1L),T))),
        idInteger=sample(u1SizePerKeyColumn),
        idLogical=sample(c(F,T),u1SizePerKeyColumn,T)
        ##idPOSIXct=as.POSIXct('2016-01-01 00:00:00','UTC')+sample(u1SizePerKeyColumn)
    )[seq_len(u1Size),];
    ## rbind some repetitions of the unique keys; this will prepare one side of the many-to-many relationship
    ## also scramble the order afterward
    keys1 <- rbind(keys1,keys1[sample(nrow(keys1),size-u1Size,T),])[sample(size),];
    ## common and unilateral key counts
    com <- as.integer(size*overlap);
    uni <- size-com;
    ## generate some unilateral keys for df2 by synthesizing outside of the idInteger range of df1
    keys2 <- data.frame(stringsAsFactors=F,
        idCharacter=replicate(uni,paste(collapse='',sample(letters,sample(4:12,1L),T))),
        idInteger=u1SizePerKeyColumn+sample(uni),
        idLogical=sample(c(F,T),uni,T)
        ##idPOSIXct=as.POSIXct('2016-01-01 00:00:00','UTC')+u1SizePerKeyColumn+sample(uni)
    );
    ## rbind random keys from df1; this will complete the many-to-many relationship
    ## also scramble the order afterward
    keys2 <- rbind(keys2,keys1[sample(nrow(keys1),com,T),])[sample(size),];
    ##keyNames <- c('idCharacter','idInteger','idLogical','idPOSIXct');
    keyNames <- c('idCharacter','idInteger','idLogical');
    ## note: was going to use raw and complex type for two of the non-key columns, but data.table doesn't seem to fully support them
    argSpecs <- list(
        default=list(copySpec=1:2,args=list(
            df1 <- cbind(stringsAsFactors=F,keys1,y1=sample(c(F,T),size,T),y2=sample(size),y3=rnorm(size),y4=replicate(size,paste(collapse='',sample(letters,sample(4:12,1L),T)))),
            df2 <- cbind(stringsAsFactors=F,keys2,y5=sample(c(F,T),size,T),y6=sample(size),y7=rnorm(size),y8=replicate(size,paste(collapse='',sample(letters,sample(4:12,1L),T)))),
            keyNames
        )),
        data.table.unkeyed=list(copySpec=1:2,args=list(
            as.data.table(df1),
            as.data.table(df2),
            keyNames
        )),
        data.table.keyed=list(copySpec=1:2,args=list(
            setkeyv(as.data.table(df1),keyNames),
            setkeyv(as.data.table(df2),keyNames)
        ))
    );
    ## prepare sqldf
    initSqldf();
    sqldf(paste0('create index df1_key on df1(',paste(collapse=',',keyNames),');')); ## upload and create an sqlite index on df1
    sqldf(paste0('create index df2_key on df2(',paste(collapse=',',keyNames),');')); ## upload and create an sqlite index on df2
    argSpecs;
}; ## end makeArgSpecs.assortedKey.optionalManyToMany()
sizes <- c(1e1L,1e3L,1e5L); ## 1e5L instead of 1e6L to respect more heavy-duty inputs
overlaps <- c(0.99,0.5,0.01);
solTypes <- setdiff(getSolTypes(),'in.place');
system.time({ res <- testGrid(makeArgSpecs.assortedKey.optionalManyToMany,sizes,overlaps,solTypes); });
##     user   system  elapsed
## 38895.50   784.19 39745.53

De resulterende plots, met dezelfde plotcode als hierboven:

titleFunc <- function(overlap) sprintf('R merge solutions: character/integer/logical key, 0..*:0..* cardinality, %d%% overlap',as.integer(overlap*100));
plotRes(res,titleFunc,F);

R-merge-benchmark-assortiment-sleutel-optioneel-veel-naar-veel-99

R-merge-benchmark-assortiment-sleutel-optioneel-veel-naar-veel-50

R-samenvoegen-benchmark-assortiment-sleutel-optioneel-veel-naar-veel-1


Antwoord 10, autoriteit 2%

Bij het samenvoegen van twee dataframes met elk ongeveer 1 miljoen rijen, één met 2 kolommen en de andere met ~20, heb ik verrassend genoeg merge(..., all.x = TRUE, all.y = TRUE) om sneller te zijn dan dplyr::full_join(). Dit is met dplyr v0.4

Samenvoegen duurt ~17 seconden, full_join duurt ~65 seconden.

Echter wat voedsel voor, aangezien ik over het algemeen standaard dplyr gebruik voor manipulatietaken.


Antwoord 11

  1. Met de functie merge kunnen we de variabele van de linkertabel of de rechtertabel selecteren, op dezelfde manier als we allemaal bekend zijn met de select-instructie in SQL (EX: Selecteer a.* …of Selecteer b. * van …..)
  2. We moeten extra code toevoegen die een subset wordt van de nieuw samengevoegde tabel.

    • SQL:- select a.* from df1 a inner join df2 b on a.CustomerId=b.CustomerId

    • R :- merge(df1, df2, by.x = "CustomerId", by.y = "CustomerId")[,names(df1)]

Dezelfde manier

  • SQL:- select b.* from df1 a inner join df2 b on a.CustomerId=b.CustomerId

  • R :- merge(df1, df2, by.x = "CustomerId", by.y =
    "CustomerId")[,names(df2)]


Antwoord 12

Voor een inner join op alle kolommen kunt u ook fintersect uit het data.table-pakket of intersect uit de dplyr-package als alternatief voor merge zonder de by-kolommen te specificeren. dit geeft de rijen die gelijk zijn tussen twee dataframes:

merge(df1, df2)
#   V1 V2
# 1  B  2
# 2  C  3
dplyr::intersect(df1, df2)
#   V1 V2
# 1  B  2
# 2  C  3
data.table::fintersect(setDT(df1), setDT(df2))
#    V1 V2
# 1:  B  2
# 2:  C  3

Voorbeeldgegevens:

df1 <- data.frame(V1 = LETTERS[1:4], V2 = 1:4)
df2 <- data.frame(V1 = LETTERS[2:3], V2 = 2:3)

Antwoord 13

Update join. Een andere belangrijke join in SQL-stijl is een “update join” waar kolommen in de ene tabel worden bijgewerkt (of gemaakt) met een andere tabel.

De voorbeeldtabellen van de OP wijzigen…

sales = data.frame(
  CustomerId = c(1, 1, 1, 3, 4, 6), 
  Year = 2000:2005,
  Product = c(rep("Toaster", 3), rep("Radio", 3))
)
cust = data.frame(
  CustomerId = c(1, 1, 4, 6), 
  Year = c(2001L, 2002L, 2002L, 2002L),
  State = state.name[1:4]
)
sales
# CustomerId Year Product
#          1 2000 Toaster
#          1 2001 Toaster
#          1 2002 Toaster
#          3 2003   Radio
#          4 2004   Radio
#          6 2005   Radio
cust
# CustomerId Year    State
#          1 2001  Alabama
#          1 2002   Alaska
#          4 2002  Arizona
#          6 2002 Arkansas

Stel dat we de klantstatus van cust willen toevoegen aan de inkooptabel, sales, waarbij de kolom jaar wordt genegeerd. Met grondtal R kunnen we overeenkomende rijen identificeren en vervolgens waarden kopiëren over:

sales$State <- cust$State[ match(sales$CustomerId, cust$CustomerId) ]
# CustomerId Year Product    State
#          1 2000 Toaster  Alabama
#          1 2001 Toaster  Alabama
#          1 2002 Toaster  Alabama
#          3 2003   Radio     <NA>
#          4 2004   Radio  Arizona
#          6 2005   Radio Arkansas
# cleanup for the next example
sales$State <- NULL

Zoals hier te zien is, selecteert match de eerste overeenkomende rij uit de klantentabel.


Update samenvoeging met meerdere kolommen. De bovenstaande aanpak werkt goed wanneer we deelnemen aan slechts één kolom en tevreden zijn met de eerste overeenkomst. Stel dat we willen dat het meetjaar in de klantentabel overeenkomt met het jaar van verkoop.

Zoals het antwoord van @bgoldst vermeldt, kan match met interaction in dit geval een optie zijn. Eenvoudiger zou men data.table kunnen gebruiken:

library(data.table)
setDT(sales); setDT(cust)
sales[, State := cust[sales, on=.(CustomerId, Year), x.State]]
#    CustomerId Year Product   State
# 1:          1 2000 Toaster    <NA>
# 2:          1 2001 Toaster Alabama
# 3:          1 2002 Toaster  Alaska
# 4:          3 2003   Radio    <NA>
# 5:          4 2004   Radio    <NA>
# 6:          6 2005   Radio    <NA>
# cleanup for next example
sales[, State := NULL]

Doorlopende update-deelname. Als alternatief willen we misschien de laatste staat gebruiken waarin de klant werd gevonden:

sales[, State := cust[sales, on=.(CustomerId, Year), roll=TRUE, x.State]]
#    CustomerId Year Product    State
# 1:          1 2000 Toaster     <NA>
# 2:          1 2001 Toaster  Alabama
# 3:          1 2002 Toaster   Alaska
# 4:          3 2003   Radio     <NA>
# 5:          4 2004   Radio  Arizona
# 6:          6 2005   Radio Arkansas

De drie voorbeelden zijn vooral gericht op het maken/toevoegen van een nieuwe kolom. Zie de gerelateerde R FAQ voor een voorbeeld van het bijwerken/wijzigen van een bestaande kolom.

LEAVE A REPLY

Please enter your comment!
Please enter your name here

three × two =

Other episodes