unire i frame di dati in base a più colonne e soglie


11

Ho due data.frames con più colonne comuni (qui: date, city, ctry, e ( other_) number).

Vorrei ora unirli sulle colonne precedenti ma tollerare un certo livello di differenza:

threshold.numbers <- 3
threshold.date <- 5  # in days

Se la differenza tra le datevoci è > threshold.date(in giorni) o > threshold.numbers , non voglio che le linee vengano unite. Allo stesso modo, se la voce in cityè una sottostringa della dfvoce dell'altra nella citycolonna, voglio che le linee vengano unite. [Se qualcuno ha un'idea migliore per verificare la somiglianza dei nomi di città reali, sarei felice di saperlo.] (E mantieni le prime dfvoci di date, citye countryma entrambe ( other_) le numbercolonne e tutte le altre colonne nella df.

Considera il seguente esempio:

df1 <- data.frame(date = c("2003-08-29", "1999-06-12", "2000-08-29", "1999-02-24", "2001-04-17",
                           "1999-06-30", "1999-03-16", "1999-07-16", "2001-08-29", "2002-07-30"),
                  city = c("Berlin", "Paris", "London", "Rome", "Bern",
                           "Copenhagen", "Warsaw", "Moscow", "Tunis", "Vienna"),
                  ctry = c("Germany", "France", "UK", "Italy", "Switzerland",
                           "Denmark", "Poland", "Russia", "Tunisia", "Austria"),
                  number = c(10, 20, 30, 40, 50, 60, 70, 80, 90, 100),
                  col = c("apple", "banana", "pear", "banana", "lemon", "cucumber", "apple", "peach", "cherry", "cherry"))


df2 <- data.frame(date = c("2003-08-29", "1999-06-12", "2000-08-29", "1999-02-24", "2001-04-17", # all identical to df1
                           "1999-06-29", "1999-03-14", "1999-07-17", # all 1-2 days different
                           "2000-01-29", "2002-07-01"), # all very different (> 2 weeks)
                  city = c("Berlin", "East-Paris", "near London", "Rome", # same or slight differences
                           "Zurich", # completely different
                           "Copenhagen", "Warsaw", "Moscow", "Tunis", "Vienna"), # same
                  ctry = c("Germany", "France", "UK", "Italy", "Switzerland", # all the same 
                           "Denmark", "Poland", "Russia", "Tunisia", "Austria"),
                  other_number = c(13, 17, 3100, 45, 51, 61, 780, 85, 90, 101), # slightly different to very different
                  other_col = c("yellow", "green", "blue", "red", "purple", "orange", "blue", "red", "black", "beige"))

Ora, vorrei unire il data.framese ricevere un punto in dfcui le linee vengono unite se le condizioni di cui sopra sono soddisfatte.

(La prima colonna è solo per comodità: dietro la prima cifra, che indica il caso originale, mostra se le linee dove sono state unite ( .) o se le linee provengono da df1( 1) o df2( 2).

          date        city        ctry number other_col other_number    other_col2          #comment
 1.  2003-08-29      Berlin     Germany     10     apple              13        yellow      # matched on date, city, number
 2.  1999-06-12       Paris      France     20    banana              17         green      # matched on date, city similar, number - other_number == threshold.numbers
 31  2000-08-29      London          UK     30      pear            <NA>          <NA>      # not matched: number - other_number > threshold.numbers
 32  2000-08-29 near London         UK    <NA>      <NA>            3100          blue      #
 41  1999-02-24        Rome       Italy     40    banana            <NA>          <NA>      # not matched: number - other_number > threshold.numbers
 42  1999-02-24        Rome       Italy   <NA>      <NA>              45           red      #
 51  2001-04-17        Bern Switzerland     50     lemon            <NA>          <NA>      # not matched: cities different (dates okay, numbers okay)
 52  2001-04-17      Zurich Switzerland   <NA>      <NA>              51        purple      #
 6.  1999-06-30  Copenhagen     Denmark     60  cucumber              61        orange      # matched: date difference < threshold.date (cities okay, dates okay)
 71  1999-03-16      Warsaw      Poland     70     apple            <NA>          <NA>      # not matched: number - other_number > threshold.numbers (dates okay)
 72  1999-03-14      Warsaw      Poland   <NA>      <NA>             780          blue      # 
 81  1999-07-16      Moscow      Russia     80     peach            <NA>          <NA>      # not matched: number - other_number > threshold.numbers (dates okay)
 82  1999-07-17      Moscow      Russia   <NA>      <NA>              85           red      #
 91  2001-08-29       Tunis     Tunisia     90    cherry            <NA>          <NA>      # not matched: date difference < threshold.date (cities okay, dates okay)
 92  2000-01-29       Tunis     Tunisia   <NA>      <NA>              90         black      #
101  2002-07-30      Vienna     Austria    100    cherry            <NA>          <NA>      # not matched: date difference < threshold.date (cities okay, dates okay)
102  2002-07-01      Vienna     Austria   <NA>      <NA>             101         beige      #

Ho provato diverse implementazioni di unirle ma non riesco a implementare la soglia.

MODIFICA Mi scuso per la formulazione poco chiara - Vorrei conservare tutte le righe e ricevere un indicatore se la riga è abbinata, senza pari e da df1 o senza pari e da df2.

lo pseudo-codice è:

  if there is a case where abs("date_df2" - "date_df1") <= threshold.date:
    if "ctry_df2" == "ctry_df1":
      if "city_df2" ~ "city_df1":
        if abs("number_df2" - "number_df1") <= threshold.numbers:
          merge and go to next row in df2
  else:
    add row to df1```

2
Quest'ultimo frame di dati è stato stampato l'output che si desidera ottenere? cioè ci dovrebbero essere 17 righe alla fine? O solo i 3 segnati con un .?
Camille,

In realtà voglio preservare tutte le file ma con un indicatore se sono state abbinate. Scusate se non era chiaro; Ho modificato la domanda di conseguenza.
Ivo,

Questo significa che vuoi 10 file come l'originale?
Camille,

Ho aggiunto lo pseudo codice per renderlo più chiaro; questo aiuta?
Ivo,

Consiglio vivamente data.table se data.frame non è la tua unica opzione
Kevin Ho,

Risposte:


3

Ecco una soluzione che utilizza il mio pacchetto safejoin , avvolgendo in questo caso il pacchetto fuzzyjoin .

Possiamo usare l' byargomento per specificare una condizione complessa, usando la funzione X()per ottenere il suo valore df1e Y()per ottenere il valore da df2.

Se i tuoi tavoli reali sono grandi, questo potrebbe essere lento o impossibile come fa un prodotto cartesiano, ma qui funziona bene.

Ciò che vogliamo è un join completo (mantieni tutte le righe e unisci ciò che può essere unito), e vogliamo mantenere il primo valore quando si uniscono e prendere il prossimo saggio, ciò significa che vogliamo affrontare il conflitto di colonne identificate in modo identico dalla coalescenza, quindi usiamo l'argomento conflict = dplyr::coalesce

# remotes::install_github("moodymudskipper/safejoin")


# with provides inputs date is a factor, this will cause issues, so we need to
# convert either to date or character, character will do for now.
df1$date <- as.character(df1$date)
df2$date <- as.character(df2$date)

# we want our joining columns named the same to make them conflicted and use our
# conflict agument on conflicted paires
names(df2)[1:4] <- names(df1)[1:4]

library(safejoin)
safe_full_join(
  df1, df2,  
  by = ~ {
    # must convert every type because fuzzy join uses a matrix so coerces all inputs to character
    # see explanation at the bottom
    city1 <- X("city")
    city2 <- Y("city")
    date1 <- as.Date(X("date"), origin = "1970-01-01")
    date2 <- as.Date(Y("date"), origin = "1970-01-01")
    number1 <- as.numeric(X("number"))
    number2 <- as.numeric(Y("number"))
    # join if one city name contains the other
    (mapply(grepl, city1, city2) | mapply(grepl, city2, city1)) &
    # and dates are close enough (need to work in seconds because difftime is dangerous)
      abs(difftime(date1, date2, "sec")) <= threshold.date*3600*24 &
    # and numbers are close enough
      abs(number1 - number2) <= threshold.numbers
    },
  conflict = dplyr::coalesce)

produzione :

#>          date        city        ctry number      col other_col
#> 1  2003-08-29      Berlin     Germany     10    apple    yellow
#> 2  1999-06-12       Paris      France     20   banana     green
#> 3  1999-06-30  Copenhagen     Denmark     60 cucumber    orange
#> 4  2000-08-29      London          UK     30     pear      <NA>
#> 5  1999-02-24        Rome       Italy     40   banana      <NA>
#> 6  2001-04-17        Bern Switzerland     50    lemon      <NA>
#> 7  1999-03-16      Warsaw      Poland     70    apple      <NA>
#> 8  1999-07-16      Moscow      Russia     80    peach      <NA>
#> 9  2001-08-29       Tunis     Tunisia     90   cherry      <NA>
#> 10 2002-07-30      Vienna     Austria    100   cherry      <NA>
#> 11 2000-08-29 near London          UK   3100     <NA>      blue
#> 12 1999-02-24        Rome       Italy     45     <NA>       red
#> 13 2001-04-17      Zurich Switzerland     51     <NA>    purple
#> 14 1999-03-14      Warsaw      Poland    780     <NA>      blue
#> 15 1999-07-17      Moscow      Russia     85     <NA>       red
#> 16 2000-01-29       Tunis     Tunisia     90     <NA>     black
#> 17 2002-07-01      Vienna     Austria    101     <NA>     beige

Creato il 13-11-2019 dal pacchetto reprex (v0.3.0)

Sfortunatamente fuzzyjoin costringe tutte le colonne in una matrice quando fa un multi join, e safejoin avvolge fuzzyjoin quindi dobbiamo convertire le variabili nel tipo appropriato all'interno dell'argomento by, questo spiega le prime righe bynell'argomento.

Ulteriori informazioni su safejoin : https://github.com/moodymudskipper/safejoin


6

Ho prima trasformato i nomi delle città in vettori di personaggi, poiché (se ho capito bene) vuoi includere i nomi delle città che sono contenuti in df2.

df1$city<-as.character(df1$city)
df2$city<-as.character(df2$city)

Quindi uniscili per paese:

df = merge(df1, df2, by = ("ctry"))

> df
          ctry     date.x     city.x number      col     date.y      city.y other_number other_col
1      Austria 2002-07-30     Vienna    100   cherry 2002-07-01      Vienna          101     beige
2      Denmark 1999-06-30 Copenhagen     60 cucumber 1999-06-29  Copenhagen           61    orange
3       France 1999-06-12      Paris     20   banana 1999-06-12  East-Paris           17     green
4      Germany 2003-08-29     Berlin     10    apple 2003-08-29      Berlin           13    yellow
5        Italy 1999-02-24       Rome     40   banana 1999-02-24        Rome           45       red
6       Poland 1999-03-16     Warsaw     70    apple 1999-03-14      Warsaw          780      blue
7       Russia 1999-07-16     Moscow     80    peach 1999-07-17      Moscow           85       red
8  Switzerland 2001-04-17       Bern     50    lemon 2001-04-17      Zurich           51    purple
9      Tunisia 2001-08-29      Tunis     90   cherry 2000-01-29       Tunis           90     black
10          UK 2000-08-29     London     30     pear 2000-08-29 near London         3100      blue

La libreria stringrti permetterà di vedere se city.x è all'interno di city.y qui (vedi l'ultima colonna):

library(stringr)
df$city_keep<-str_detect(df$city.y,df$city.x) # this returns logical vector if city.x is contained in city.y (works one way)
> df
          ctry     date.x     city.x number      col     date.y      city.y other_number other_col city_keep
1      Austria 2002-07-30     Vienna    100   cherry 2002-07-01      Vienna          101     beige      TRUE
2      Denmark 1999-06-30 Copenhagen     60 cucumber 1999-06-29  Copenhagen           61    orange      TRUE
3       France 1999-06-12      Paris     20   banana 1999-06-12  East-Paris           17     green      TRUE
4      Germany 2003-08-29     Berlin     10    apple 2003-08-29      Berlin           13    yellow      TRUE
5        Italy 1999-02-24       Rome     40   banana 1999-02-24        Rome           45       red      TRUE
6       Poland 1999-03-16     Warsaw     70    apple 1999-03-14      Warsaw          780      blue      TRUE
7       Russia 1999-07-16     Moscow     80    peach 1999-07-17      Moscow           85       red      TRUE
8  Switzerland 2001-04-17       Bern     50    lemon 2001-04-17      Zurich           51    purple     FALSE
9      Tunisia 2001-08-29      Tunis     90   cherry 2000-01-29       Tunis           90     black      TRUE
10          UK 2000-08-29     London     30     pear 2000-08-29 near London         3100      blue      TRUE

Quindi puoi ottenere la differenza in giorni tra le date:

df$dayDiff<-abs(as.POSIXlt(df$date.x)$yday - as.POSIXlt(df$date.y)$yday)

e la differenza nei numeri:

df$numDiff<-abs(df$number - df$other_number)

Ecco come appare il frame di dati risultante:

> df
          ctry     date.x     city.x number      col     date.y      city.y other_number other_col city_keep dayDiff numDiff
1      Austria 2002-07-30     Vienna    100   cherry 2002-07-01      Vienna          101     beige      TRUE      29       1
2      Denmark 1999-06-30 Copenhagen     60 cucumber 1999-06-29  Copenhagen           61    orange      TRUE       1       1
3       France 1999-06-12      Paris     20   banana 1999-06-12  East-Paris           17     green      TRUE       0       3
4      Germany 2003-08-29     Berlin     10    apple 2003-08-29      Berlin           13    yellow      TRUE       0       3
5        Italy 1999-02-24       Rome     40   banana 1999-02-24        Rome           45       red      TRUE       0       5
6       Poland 1999-03-16     Warsaw     70    apple 1999-03-14      Warsaw          780      blue      TRUE       2     710
7       Russia 1999-07-16     Moscow     80    peach 1999-07-17      Moscow           85       red      TRUE       1       5
8  Switzerland 2001-04-17       Bern     50    lemon 2001-04-17      Zurich           51    purple     FALSE       0       1
9      Tunisia 2001-08-29      Tunis     90   cherry 2000-01-29       Tunis           90     black      TRUE     212       0
10          UK 2000-08-29     London     30     pear 2000-08-29 near London         3100      blue      TRUE       0    3070

Ma vogliamo eliminare cose in cui city.x non è stato trovato in city.y, dove la differenza del giorno è maggiore di 5 o la differenza numerica è maggiore di 3:

df<-df[df$dayDiff<=5 & df$numDiff<=3 & df$city_keep==TRUE,]

> df
     ctry     date.x     city.x number      col     date.y     city.y other_number other_col city_keep dayDiff numDiff
2 Denmark 1999-06-30 Copenhagen     60 cucumber 1999-06-29 Copenhagen           61    orange      TRUE       1       1
3  France 1999-06-12      Paris     20   banana 1999-06-12 East-Paris           17     green      TRUE       0       3
4 Germany 2003-08-29     Berlin     10    apple 2003-08-29     Berlin           13    yellow      TRUE       0       3

Ciò che rimane sono le tre righe che avevi sopra (che conteneva punti nella colonna 1).

Ora possiamo eliminare le tre colonne che abbiamo creato e la data e la città da df2:

> df<-subset(df, select=-c(city.y, date.y, city_keep, dayDiff, numDiff))
> df
     ctry     date.x     city.x number      col other_number other_col
2 Denmark 1999-06-30 Copenhagen     60 cucumber           61    orange
3  France 1999-06-12      Paris     20   banana           17     green
4 Germany 2003-08-29     Berlin     10    apple           13    yellow

5

Passaggio 1: unisci i dati in base a "città" e "ctry":

df = merge(df1, df2, by = c("city", "ctry"))

Passaggio 2: rimuovere le righe se la differenza tra le voci della data è> soglia.data (in giorni):

date_diff = abs(as.numeric(difftime(strptime(df$date.x, format = "%Y-%m-%d"),
                                    strptime(df$date.y, format = "%Y-%m-%d"), units="days")))
index_remove = date_diff > threshold.date
df = df[-index_remove,]

Passaggio 3: rimuovere le righe se la differenza tra i numeri è> threshhold.number:

number_diff = abs(df$number - df$other_number) 
index_remove = number_diff > threshold.numbers
df = df[-index_remove,]

I dati devono essere uniti prima di applicare le condizioni, nel caso in cui le righe non corrispondano.


3

Un'opzione che utilizza data.table(spiegazioni in linea):

library(data.table)
setDT(df1)
setDT(df2)

#dupe columns and create ranges for non-equi joins
df1[, c("n", "ln", "un", "d", "ld", "ud") := .(
    number, number - threshold.numbers, number + threshold.numbers,
    date, date - threshold.date, date + threshold.date)]
df2[, c("n", "ln", "un", "d", "ld", "ud") := .(
    other_number, other_number - threshold.numbers, other_number + threshold.numbers,
    date, date - threshold.date, date + threshold.date)]

#perform non-equi join using ctry, num, dates in both ways
res <- rbindlist(list(
    df1[df2, on=.(ctry, n>=ln, n<=un, d>=ld, d<=ud),
        .(date1=x.date, date2=i.date, city1=x.city, city2=i.city, ctry1=x.ctry, ctry2=i.ctry, number, col, other_number, other_col)],
    df2[df1, on=.(ctry, n>=ln, n<=un, d>=ld, d<=ud),
        .(date1=i.date, date2=x.date, city1=i.city, city2=x.city, ctry1=i.ctry, ctry2=x.ctry, number, col, other_number, other_col)]),
    use.names=TRUE, fill=TRUE)

#determine if cities are substrings of one and another
res[, city_match := {
    i <- mapply(grepl, city1, city2) | mapply(grepl, city2, city1)
    replace(i, is.na(i), TRUE)
}]

#just like SQL coalesce (there is a version in dev in rdatatable github)
coalesce <- function(...) Reduce(function(x, y) fifelse(!is.na(y), y, x), list(...))

#for rows that are matching or no matches to be found
ans1 <- unique(res[(city_match), .(date=coalesce(date1, date2),
    city=coalesce(city1, city2),
    ctry=coalesce(ctry1, ctry2),
    number, col, other_number, other_col)])

#for rows that are close in terms of dates and numbers but are diff cities
ans2 <- res[(!city_match), .(date=c(.BY$date1, .BY$date2),
        city=c(.BY$city1, .BY$city2),
        ctry=c(.BY$ctry1, .BY$ctry2),
        number=c(.BY$number, NA),
        col=c(.BY$col, NA),
        other_number=c(NA, .BY$other_number),
        other_col=c(NA, .BY$other_col)),
    names(res)][, seq_along(names(res)) := NULL]

#final desired output
setorder(rbindlist(list(ans1, ans2)), date, city, number, na.last=TRUE)[]

produzione:

          date        city        ctry number      col other_number other_col
 1: 1999-02-24        Rome       Italy     40   banana           NA      <NA>
 2: 1999-02-24        Rome       Italy     NA     <NA>           45       red
 3: 1999-03-14      Warsaw      Poland     NA     <NA>          780      blue
 4: 1999-03-16      Warsaw      Poland     70    apple           NA      <NA>
 5: 1999-06-12  East-Paris      France     20   banana           17     green
 6: 1999-06-29  Copenhagen     Denmark     60 cucumber           61    orange
 7: 1999-07-16      Moscow      Russia     80    peach           NA      <NA>
 8: 1999-07-17      Moscow      Russia     NA     <NA>           85       red
 9: 2000-01-29       Tunis     Tunisia     NA     <NA>           90     black
10: 2000-08-29      London          UK     30     pear           NA      <NA>
11: 2000-08-29 near London          UK     NA     <NA>         3100      blue
12: 2001-04-17        Bern Switzerland     50    lemon           NA      <NA>
13: 2001-04-17      Zurich Switzerland     NA     <NA>           51    purple
14: 2001-08-29       Tunis     Tunisia     90   cherry           NA      <NA>
15: 2002-07-01      Vienna     Austria     NA     <NA>          101     beige
16: 2002-07-30      Vienna     Austria    100   cherry           NA      <NA>
17: 2003-08-29      Berlin     Germany     10    apple           13    yellow

3

Puoi testare la citypartita con greple ctrysemplice con ==. Per coloro che corrispondono fino a qui puoi calcolare la differenza di data convertendola in dateutilizzo as.Datee confrontandola con a difftime. La numberdifferenza è fatta allo stesso modo.

i1 <- seq_len(nrow(df1)) #Store all rows 
i2 <- seq_len(nrow(df2))
res <- do.call(rbind, sapply(seq_len(nrow(df1)), function(i) { #Loop over all rows in df1
  t1 <- which(df1$ctry[i] == df2$ctry) #Match ctry
  t2 <- grepl(df1$city[i], df2$city[t1]) | sapply(df2$city[t1], grepl, df1$city[i]) #Match city
  t1 <- t1[t2 & abs(as.Date(df1$date[i]) - as.Date(df2$date[t1[t2]])) <=
    as.difftime(threshold.date, units = "days") & #Test for date difference
    abs(df1$number[i] - df2$other_number[t1[t2]]) <= threshold.numbers] #Test for number difference
  if(length(t1) > 0) { #Match found
    i1 <<- i1[i1!=i] #Remove row as it was found
    i2 <<- i2[i2!=t1]
    cbind(df1[i,], df2[t1,c("other_number","other_col")], match=".") 
  }
}))
rbind(res
    , cbind(df1[i1,], other_number=NA, other_col=NA, match="1")
    , cbind(df2[i2,1:3], number=NA, col=NA, other_number=df2[i2,4]
            , other_col=df2[i2,5], match="2"))
#          date        city        ctry number      col other_number other_col match
#1   2003-08-29      Berlin     Germany     10    apple           13    yellow     .
#2   1999-06-12       Paris      France     20   banana           17     green     .
#6   1999-06-30  Copenhagen     Denmark     60 cucumber           61    orange     .
#3   2000-08-29      London          UK     30     pear           NA      <NA>     1
#4   1999-02-24        Rome       Italy     40   banana           NA      <NA>     1
#5   2001-04-17        Bern Switzerland     50    lemon           NA      <NA>     1
#7   1999-03-16      Warsaw      Poland     70    apple           NA      <NA>     1
#8   1999-07-16      Moscow      Russia     80    peach           NA      <NA>     1
#9   2001-08-29       Tunis     Tunisia     90   cherry           NA      <NA>     1
#10  2002-07-30      Vienna     Austria    100   cherry           NA      <NA>     1
#31  2000-08-29 near London          UK     NA     <NA>         3100      blue     2
#41  1999-02-24        Rome       Italy     NA     <NA>           45       red     2
#51  2001-04-17      Zurich Switzerland     NA     <NA>           51    purple     2
#71  1999-03-14      Warsaw      Poland     NA     <NA>          780      blue     2
#81  1999-07-17      Moscow      Russia     NA     <NA>           85       red     2
#91  2000-01-29       Tunis     Tunisia     NA     <NA>           90     black     2
#101 2002-07-01      Vienna     Austria     NA     <NA>          101     beige     2

2

Ecco un approccio flessibile che ti consente di specificare qualsiasi raccolta di criteri di unione che scegli.

Prep lavoro

Mi sono assicurato che tutte le stringhe dentro df1e df2fossero stringhe, non fattori (come notato in molte altre risposte). Ho anche inserito le date as.Dateper renderle reali.

Specificare i criteri di unione

Crea un elenco di elenchi. Ogni elemento dell'elenco principale è un criterio; i membri di un criterio sono

  • final.col.name: il nome della colonna che vogliamo nella tabella finale
  • col.name.1: il nome della colonna in df1
  • col.name.2: il nome della colonna in df2
  • exact: booleano; dovremmo fare la corrispondenza esatta su questa colonna?
  • threshold: soglia (se non stiamo eseguendo la corrispondenza esatta)
  • match.function: una funzione che restituisce se le righe corrispondono o meno (per casi speciali come l'utilizzo greplper la corrispondenza delle stringhe; si noti che questa funzione deve essere vettorializzata)
merge.criteria = list(
  list(final.col.name = "date",
       col.name.1 = "date",
       col.name.2 = "date",
       exact = F,
       threshold = 5),
  list(final.col.name = "city",
       col.name.1 = "city",
       col.name.2 = "city",
       exact = F,
       match.function = function(x, y) {
         return(mapply(grepl, x, y) |
                  mapply(grepl, y, x))
       }),
  list(final.col.name = "ctry",
       col.name.1 = "ctry",
       col.name.2 = "ctry",
       exact = T),
  list(final.col.name = "number",
       col.name.1 = "number",
       col.name.2 = "other_number",
       exact = F,
       threshold = 3)
)

Funzione per la fusione

Questa funzione accetta tre argomenti: i due frame di dati che vogliamo unire e l'elenco dei criteri di corrispondenza. Procede come segue:

  1. Scorrere i criteri di corrispondenza e determinare quali coppie di righe soddisfano o meno tutti i criteri. (Ispirato dalla risposta di @ GKi, utilizza gli indici di riga invece di eseguire un join esterno completo, che potrebbe richiedere meno memoria per i set di dati di grandi dimensioni.)
  2. Crea un frame di dati scheletro con solo le righe desiderate (righe unite in caso di corrispondenze, righe non unite per record senza eguali).
  3. Scorrere le colonne dei frame di dati originali e utilizzarli per popolare le colonne desiderate nel nuovo frame di dati. (Esegui prima questa operazione per le colonne visualizzate nei criteri di corrispondenza, quindi per tutte le altre colonne rimaste.)
library(dplyr)
merge.data.frames = function(df1, df2, merge.criteria) {
  # Create a data frame with all possible pairs of rows from df1 and rows from
  # df2.
  row.decisions = expand.grid(df1.row = 1:nrow(df1), df2.row = 1:nrow(df2))
  # Iterate over the criteria in merge.criteria.  For each criterion, flag row
  # pairs that don't meet the criterion.
  row.decisions$merge = T
  for(criterion in merge.criteria) {
    # If we're looking for an exact match, test for equality.
    if(criterion$exact) {
      row.decisions$merge = row.decisions$merge &
        df1[row.decisions$df1.row,criterion$col.name.1] == df2[row.decisions$df2.row,criterion$col.name.2]
    }
    # If we're doing a threshhold test, test for difference.
    else if(!is.null(criterion$threshold)) {
      row.decisions$merge = row.decisions$merge &
        abs(df1[row.decisions$df1.row,criterion$col.name.1] - df2[row.decisions$df2.row,criterion$col.name.2]) <= criterion$threshold
    }
    # If the user provided a function, use that.
    else if(!is.null(criterion$match.function)) {
      row.decisions$merge = row.decisions$merge &
        criterion$match.function(df1[row.decisions$df1.row,criterion$col.name.1],
                                 df2[row.decisions$df2.row,criterion$col.name.2])
    }
  }
  # Create the new dataframe.  Just row numbers of the source dfs to start.
  new.df = bind_rows(
    # Merged rows.
    row.decisions %>% filter(merge) %>% select(-merge),
    # Rows from df1 only.
    row.decisions %>% group_by(df1.row) %>% summarize(matches = sum(merge)) %>% filter(matches == 0) %>% select(df1.row),
    # Rows from df2 only.
    row.decisions %>% group_by(df2.row) %>% summarize(matches = sum(merge)) %>% filter(matches == 0) %>% select(df2.row)
  )
  # Iterate over the merge criteria and add columns that were used for matching
  # (from df1 if available; otherwise from df2).
  for(criterion in merge.criteria) {
    new.df[criterion$final.col.name] = coalesce(df1[new.df$df1.row,criterion$col.name.1],
                                                df2[new.df$df2.row,criterion$col.name.2])
  }
  # Now add all the columns from either data frame that weren't used for
  # matching.
  for(other.col in setdiff(colnames(df1),
                           sapply(merge.criteria, function(x) x$col.name.1))) {
    new.df[other.col] = df1[new.df$df1.row,other.col]
  }
  for(other.col in setdiff(colnames(df2),
                           sapply(merge.criteria, function(x) x$col.name.2))) {
    new.df[other.col] = df2[new.df$df2.row,other.col]
  }
  # Return the result.
  return(new.df)
}

Applica la funzione e il gioco è fatto

df = merge.data.frames(df1, df2, merge.criteria)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.