Kaip sujungti (sujungti) duomenų rėmus (vidinius, išorinius, kairius, dešinius)?

Pateikiami du duomenų rėmeliai:

 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 

Kaip sukurti duomenų bazės stilių, t.y. stiliaus SQL, prisijungia ? Tai yra, kaip gauti:

  • vidinis ryšys df1 ir df2 :
    Grąžinkite tik tas eilutes, kuriose kairėje lentelėje yra atitinkami raktai dešinėje lentelėje.
  • išorinis ryšys df1 ir df2 :
    Grąžina visas eilutes abiejose lentelėse, sujungia kairėje esančius įrašus, kuriuose yra atitinkami raktai dešinėje lentelėje.
  • Kairysis išorinis ryšys (arba tiesiog kairysis ryšys) df1 ir df2
    Iš dešinės lentelės grąžinkite visas eilutes iš kairiojo stalo ir visas eilutes su atitinkamais klavišais.
  • Teisė išorinė jungtis df1 ir df2
    Iš kairės lentelės grąžinkite visas eilutes iš dešiniojo stalo ir visas eilutes su atitinkamais klavišais.

Papildomas kreditas:

Kaip galiu atlikti SQL stiliaus pasirinkimo pareiškimą?

1019
19 авг. nustatė Dan Goldstein 19 rug. 2009-08-19 16:18 '09, 16:18, 2009-08-19 16:18
@ 13 atsakymų

Naudojant merge funkciją ir jos pasirinktinius parametrus:

Vidinis ryšys: merge(df1, df2) veiks šiems pavyzdžiams, nes R automatiškai sujungia rėmus bendrais kintamųjų pavadinimais, bet tikriausiai norite nurodyti merge(df1, df2, by = "CustomerId") , kad įsitikintumėte, jog Jūs susieti tik su laukais, kuriuos norėjote. Taip pat galite naudoti by.x ir by.y jei atitikimo kintamieji turi skirtingus pavadinimus skirtinguose duomenų rėmeliuose.

Išorinis prisijungimas: merge(x = df1, y = df2, by = "CustomerId", all = TRUE)

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

Dešinė: merge(x = df1, y = df2, by = "CustomerId", all.y = TRUE)

Kryžminis ryšys: merge(x = df1, y = df2, by = NULL)

Kaip ir vidinio ryšio atveju, tikriausiai norite aiškiai perduoti „CustomerId“ į R kaip atitinkamą kintamąjį. Manau, kad beveik visada geriau aiškiai nurodyti identifikatorius, pagal kuriuos norite sujungti; ji yra saugesnė, jei įvesties duomenys netikėtai keičiasi ir yra lengviau perskaityti vėliau.

Galite sujungti kelis stulpelius suteikdami vektorių, pvz., by = c("CustomerId", "OrderId") .

Jei prisijungimo stulpelių pavadinimai nesutampa, galite nurodyti, pvz., by.x = "CustomerId_in_df1", by.y = "CustomerId_in_df2" kur CustomerId_in_df1 yra pirmojo duomenų rėmelio stulpelio pavadinimas ir CustomerId_in_df2 yra antrojo duomenų rėmelio stulpelio pavadinimas . (Tai taip pat gali būti vektoriai, jei reikia derinti kelis stulpelius.)

1110
19 авг. Atsakyti Matt Parker 19 rug. 2009-08-19 18:15 '09, 18:15, 2009-08-19 18:15

Norėčiau rekomenduoti „Gabor Grothendieck sqldf“ paketą , kuris leidžia jums išreikšti šias operacijas SQL.

 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)") 
border=0

Rasti SQL sintaksę paprastesnę ir natūralesnę nei jos ekvivalentas R (bet tai gali tiesiog atspindėti RDBMS kompensaciją).

Daugiau informacijos apie prisijungimus žr. Gabor sqldf GitHub .

190
20 авг. atsakymas pateikiamas medriscoll 20 rug . 2009-08-20 20:54 '09 ne 20:54 2009-08-20 20:54

Vidiniam ryšiui yra „ data.table“ metodas, kuris yra labai veiksmingas laiko ir atminties atžvilgiu (ir reikalingas kai kuriems didesniems duomenims.framams):

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

merge taip pat veikia su data.tables (nes tai yra įprasta ir skambina merge.data.table )

 merge(dt1, dt2) 

data.table, užregistruotas stackoverflow:
Kaip atlikti duomenų sujungimo operaciją
SQL jungčių vertimas užsienio raktais į R data.table sintaksę
Veiksmingos sujungimo alternatyvos dideliems duomenims. R rėmai
Kaip padaryti pagrindinį kairįjį išorinį ryšį su data.table R?

Kitas variantas yra join funkcija, nustatyta plyr pakete .

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

Parametrų type : inner , left , right , full .

Nuo ?join : Skirtingai nuo merge , [ join ] išsaugo x tvarką, nepriklausomai nuo to, kokio tipo prisijungimas naudojamas.

171
11 марта '12 в 9:24 2012-03-11 09:24 atsakymą pateikė Etienne Low-Décarie kovo 11 d., 12 val. 9:24 2012-03-11 09:24

Taip pat galite prisijungti naudodami Hadley Wickham awesome dplyr .

 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) 

Mutacijos junginiai: pridėti stulpelius į df1, naudodami atitikmenis 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) 

Ryšio filtravimas: filtrų eilutės df1, nekeiskite stulpelių

 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. 
147
07 февр. Atsakymą pateikė Andrew Barr 07 vasaris. 2014-02-07 00:35 '14 at 0:35 2014-02-07 00:35

Yra keletas gerų pavyzdžių R Wiki . Čia pavogsiu porą:

Sujungimo metodas

Kadangi jūsų raktai vadinami tuo pačiu, trumpas būdas sujungti vidinį ryšį ():

 merge(df1,df2) 

Visą vidinį prisijungimą (visus įrašus iš abiejų lentelių) galima sukurti naudojant „visus“ raktinius žodžius:

 merge(df1,df2, all=TRUE) 

kairė išorinė jungtis df1 ir df2:

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

dešinė išorinė jungtis df1 ir df2:

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

galite juos perjungti, juos pakviesti ir nuvalyti, kad gautumėte kitus du išorinius ryšius, kuriuos paklausėte :)

Pagrindo metodas

Kairė išorinė jungtis su df1 kairėje, naudojant pagrindinio metodo metodą:

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

Kitas išorinių jungčių derinys gali būti sukurtas keičiant išorinio išorinio prisijungimo pavyzdį. (Taip, žinau, kad „aš paliksiu ją kaip skaitytojo pratybas ...“)

73
19 авг. atsakymas duotas JD Long 19 rug. 2009-08-19 18:15 '09, 18:15, 2009-08-19 18:15

2014 m.

Ypač jei jus domina ir manipuliavimas duomenimis apskritai (įskaitant rūšiavimą, filtravimą, pogrupį, apibendrinimą ir pan.), dplyr tikrai pažvelgti į dplyr , kuriame yra daug funkcijų, skirtų lengviau dirbti su rėmeliais duomenų ir kai kurių kitų tipų duomenų bazių. Jis netgi siūlo gana sudėtingą SQL sąsają ir netgi funkciją konvertuoti (labiausiai) SQL kodą tiesiai į R.

Keturios funkcijos, susijusios su prijungimu „dplyr“ pakete, (citata):

  • inner_join(x, y, by = NULL, copy = FALSE, ...) : grąžinkite visas eilutes iš x, kur y yra atitinkamos reikšmės, ir visos stulpeliai iš x ir y
  • left_join(x, y, by = NULL, copy = FALSE, ...) : grąžinkite visas eilutes iš x ir visų stulpelių iš x ir y
  • semi_join(x, y, by = NULL, copy = FALSE, ...) : grąžinkite visas eilutes iš x, kur y yra atitinkamos reikšmės, išlaikant tik stulpelius iš x.
  • anti_join(x, y, by = NULL, copy = FALSE, ...) : grąžinkite visas eilutes iš x, kur y nėra atitinkamų reikšmių, išlaikant tik stulpelius iš x

Viskas čia yra labai išsami.

Stulpelių pasirinkimas gali būti atliktas naudojant select(df,"column") . Jei to neužtenka SQL-ish, t. Y. Sql sql() funkcijai, į kurią galite įvesti SQL kodą, ir atliks tą operaciją, kurią nurodėte tuo pačiu būdu, kaip rašėte R visam laikui (daugiau informacijos žr. Dplyr / duomenų bazės vinjetė ). Pavyzdžiui, jei teisingai taikomas, sql("SELECT * FROM hflights") pasirinks visus stulpelius iš dplyr lentelės „hflights“ („tbl“).

64
29 янв. atsakymas pateiktas sausio 29 d . 2014-01-29 20:43 '14, 20:43, 2014-01-29 20:43

Atnaujinkite duomenų lentelės metodus duomenų rinkinių sujungimui. Toliau pateikiami kiekvieno tipo junginio pavyzdžiai. Yra du būdai: vienas iš [.data.table perduodant antrą duomenų lentelę kaip pirmąjį argumentą į pogrupį, kitas būdas yra naudoti merge funkciją, kuri siunčia greitą duomenų lentelės metodą.

 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 

Toliau lyginamoji analizė tikrina bazę R, sqldf, dplyr ir data.table.
Keyless duomenų rinkinio valdymo testai. Sqldf ir data.table atveju indeksai taip pat išbandomi kaip atskiri laikai. R ir dplyr neturi indeksų.
Bandymai atliekami duomenų rinkiniams 50M-1, prisijungimo stulpelyje yra 50M-2 bendrų verčių, todėl kiekvienas scenarijus (vidinis, kairysis, dešinysis, pilnas) gali būti išbandytas, o prisijungimas vis dar nėra trivialus. Tai yra sąjungos tipas, kuris gerai pabrėžia kombinacinius algoritmus. Nuo 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 isqldf 614.69304 614.69304 614.69304 614.69304 614.69304 614.69304 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 IDT 10.24206 10.24206 10.24206 10.24206 10.24206 10.24206 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 isqldf 624.641976 624.641976 624.641976 624.641976 624.641976 624.641976 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 IDT 11.913815 11.913815 11.913815 11.913815 11.913815 11.913815 # 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 isqldf 617.698158 617.698158 617.698158 617.698158 617.698158 617.698158 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 IDT 9.402034 9.402034 9.402034 9.402034 9.402034 9.402034 # 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 IDT 20.59082 20.59082 20.59082 20.59082 20.59082 20.59082 1 

Laikykite data.table , kad yra kitų tipų prisijungimų, kuriuos galite atlikti su data.table :
- atnaujinti susijungimo metu - jei norite ieškoti vertybių iš kitos lentelės pagrindinėje lentelėje
- suvestinė jungiant - jei norite suvienyti pagal prisijungimo raktą, jums nereikia įvykdyti visų sujungimo rezultatų
- persidengiantis ryšys - jei norite derinti įvairius diapazonus
- stumdomasis ryšys - jei norite, kad sąjunga atitiktų ankstesnių / kitų eilučių reikšmes, slinkite juos į priekį arba atgal
- nevienodas ryšys - jei jūsų ryšio sąlyga nėra vienoda

Žaidimo kodas:

58
11 дек. Atsakymas pateikiamas jangorecki 11 d. 2015-12-11 12:23 '15, 12:23 AM 2015-12-11 12:23

Dplyr su 0,4 įgyvendino visas tas sąjungas, įskaitant išorinį, bet verta paminėti, kad pirmuosius keletą išleidimų jis nepasiūlė išorinio_prie, ir todėl buvo labai daug labai blogų krekingo naudotojo laikinų kodų, kurie gana ilgai plaukiojo (jūs tai galite rasti atsakymuose į SO ir Kaggle iš to laikotarpio).

Su leidimais susiję klausimai :

v0.5 (6/2016)

  • „POSIXct“ tipo apdorojimas, laiko juostos, dublikatai, skirtingi veiksnių lygiai. Geriausios klaidos ir įspėjimai.
  • Naujas sufikso argumentas, skirtas valdyti, kurie priesagai pasikartoja kintamųjų pavadinimų (# 1296)

v.0.4.0 (1/2015)

v0.3 ( 10/2014 )

  • Dabar galite palikti skirtingus kintamuosius kiekvienoje lentelėje: df1%>% left_join (df2, c ("var1" = "var2"))

v0.2 ( 5/2014 )

  • * _join () nebegali redaguoti stulpelių pavadinimų (# 324)

v0.1.3 (4/2014)

Šio klausimo vieno komentaro šalinimas:

  • right_join (x, y) yra tokia pati, kaip kairėje (y, x) eilutėse, tik stulpeliuose bus skirtingi užsakymai. Lengva dirbti su pasirinkimu (naujas_pagrindinis užsakymas)
  • external_join iš esmės yra sąjunga (left_join (x, y), right_join (x, y)) - tai yra, kad išsaugotumėte visas eilutes abiejuose duomenų rėmeliuose.
23
13 апр. atsakymas pateiktas smci 13 Bal 2014-04-13 13:39 '14, 13:39 2014-04-13 13:39

Sujungus du duomenų rėmelius su ~ 1 milijonu eilučių, po vieną su dviem stulpeliais, o kitą - su ~ 20, netikėtai rado merge(..., all.x = TRUE, all.y = TRUE) greičiau nei dplyr::full_join() . Tai yra su dplyr v0.4

Sujungimas trunka ~ 17 sekundžių, full_join trunka ~ 65 sekundes.

Vis dėlto kai kurie maisto produktai, nes paprastai būsiu pasiruošęs manipuliuoti.

21
26 февр. Atsakymas pateikiamas BradP 26 vasario mėn. 2015-02-26 21:11 '15, 21:11 2015-02-26 21:11

Jei yra kairioji jungtis su galia 0..*:0..1 arba teisingas ryšys su galia 0..1:0..* , galite priskirti vienpusius stulpelius iš staliaus (lentelė 0..1 ) tiesiai į stalą ( 0..* lentelė) 0..* ) ir taip venkite sukurti visiškai naują duomenų lentelę. Tam reikia suderinti pagrindinius stulpelius nuo staliaus staliaus ir indeksuoti + užsakyti stalių eilutes atitinkamai priskyrimui.

Jei raktas yra vienas stulpelis, galime naudoti vieną match() kvietimą, kad įvykdytume rungtynes. Šiuo atveju apie tai pasakysiu atsakydamas.

Štai pavyzdys, pagrįstas OP, išskyrus tai, kad pridėjau papildomą eilutę prie df2 su ID 7, kad df2 patikrinti, ar dailidė neatitinka raktų. Tai veiksminga df1 kairėje prisijungti prie 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 

Pirmiau pateiktu atveju aš sunkiai kodavau prielaidą, kad pagrindinis stulpelis yra pirmasis abiejų įvesties lentelių stulpelis. Norėčiau pasakyti, kad tai paprastai nėra nepagrįsta prielaida, nes jei turite duomenų rinkmeną su pagrindiniu stulpeliu, būtų keista, jei ji nebūtų sukonfigūruota kaip pirmoji duomenų skiltis. Ir visada galite pertvarkyti stulpelius, kad tai padarytumėte. Šios prielaidos pranašumas yra tas, kad pagrindinio stulpelio pavadinimas neturi būti sunkiai koduojamas, nors manau, kad jis paprasčiausiai pakeičia vieną prielaidą su kitu. Konkretumas yra dar vienas sveiko skaičiaus indeksavimo ir greičio privalumas. Toliau pateiktuose bandymuose aš pakeitiau įgyvendinimą, kad panaudotų eilutės pavadinimo indeksavimą, kad atitiktų konkuruojančius diegimus.

Manau, kad tai yra ypač tinkamas sprendimas, jei turite kelias lenteles, kurias norite išlaikyti, prisijungti prie vieno stalo. Viso lentelės atkūrimas kiekvienam sujungimui būtų nereikalingas ir neefektyvus.

Kita vertus, jei dėl kokios nors priežasties jums reikia, kad ši operacija liktų nepakitusi, tuomet šis sprendimas negali būti naudojamas, nes jis tiesiogiai pakeičia stalinį. Nors šiuo atveju galite kopijuoti ir susitarti dėl kopijos vietoje.


Atkreipkite dėmesį, kad trumpai apžvelgiau galimus daugelio stulpelių raktų sprendimus. Deja, vieninteliai atrasti sprendimai buvo:

  • neefektyvus susiliejimas. pavyzdžiui, match(interaction(df1$a,df1$b),interaction(df2$a,df2$b)) arba idėja su paste() .
  • neefektyvios Dekarto jungtys. outer(df1$a,df2$a,`==`) outer(df1$b,df2$b,`==`) .
  • base R ( merge() ir lygiavertes paketines sujungimo funkcijas, kurios visuomet skiria naują lentelę, kad sugrąžintų bendrą rezultatą, ir todėl netinka vietos nustatymui.

Pavyzdžiui, žr. Kelių stulpelių išlyginimas skirtinguose rėmeliuose ir kito stulpelio gavimas , atitinka du stulpelius su dviem kitais stulpeliais , Atitikimas keliuose stulpeliuose ir šis klausimas, kai aš iš pradžių pateikiau sprendimą, sujungti du duomenų rėmelius su skirtingas linijų skaičius R.


Lyginamoji analizė

Nusprendžiau atlikti savo lyginamąją analizę, kad pamatytumėme, kaip įdarbinimo metodas lyginamas su kitais pasiūlymais, pasiūlytais šiuo klausimu.

Bandymų kodas:

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