कई कॉलम और थ्रेसहोल्ड के आधार पर डेटाफ्रेम मर्ज करें


11

मैं दो है data.frameकई आम कॉलम के साथ है (यहाँ: date, city, ctry, और ( other_) number)।

मैं अब उन्हें उपरोक्त स्तंभों में मिलाना चाहूंगा लेकिन कुछ स्तर के अंतर को बर्दाश्त करूंगा:

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

यदि dateप्रविष्टियों के बीच का अंतर > threshold.date(दिनों में) है या > threshold.numbers , मैं नहीं चाहता कि लाइनों का विलय हो। इसी तरह, यदि प्रविष्टि कॉलम में cityदूसरे dfकी प्रविष्टि का एक विकल्प cityहै, तो मैं चाहता हूं कि लाइनों को मिला दिया जाए। [अगर किसी के पास वास्तविक शहर के नामों की समानता के लिए परीक्षण करने का एक बेहतर विचार है, तो मुझे इसके बारे में सुनकर खुशी होगी।] (और पहली dfकी प्रविष्टियों को रखें date, cityऔर countryदोनों ( other_) numberकॉलम और अन्य सभी कॉलमों में df

निम्नलिखित उदाहरण पर विचार करें:

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

अब, मैं विलय करना चाहूंगा data.framesऔर dfयदि उपरोक्त शर्तों को पूरा किया जाता है तो एक रेखा का विलय कर दिया जाएगा।

(पहला कॉलम केवल आपकी सुविधा के लिए है: पहले अंक के पीछे, जो मूल मामले को इंगित करता है, यह दर्शाता है कि क्या लाइनें जहां मर्ज की गई हैं .) ( या) लाइनें कहां से df1( 1) या 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      #

मैंने उन्हें विलय करने के विभिन्न कार्यान्वयनों की कोशिश की, लेकिन उन्हें लागू करने की सीमा नहीं मिल सकती है।

अस्पष्ट सूत्रीकरण के लिए EDIT माफी - मैं सभी पंक्तियों को बनाए रखना चाहता हूं और एक संकेतक प्राप्त करना चाहता हूं कि क्या पंक्ति मेल खाती है, बेजोड़ और df1 से या बेजोड़ और df2 से।

छद्म कोड है:

  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
क्या यह अंतिम डेटा फ़्रेम है जिसे आप उस आउटपुट को प्रिंट करना चाहते हैं जिसे आप प्राप्त करना चाहते हैं? यानी अंत में 17 पंक्तियाँ होनी चाहिए? या सिर्फ 3 एक के साथ चिह्नित .?
कैमिली

मैं वास्तव में चाहता हूं कि सभी पंक्तियों को संरक्षित किया जाए लेकिन एक संकेतक के साथ अगर वे मिलान किए गए थे। क्षमा करें यदि यह अस्पष्ट था; मैंने तदनुसार प्रश्न संपादित किया।
इवो

तो इसका मतलब है कि आप मूल की तरह 10 पंक्तियाँ चाहते हैं?
कैमिली

मैंने इसे स्पष्ट करने के लिए छद्म कोड जोड़ा; क्या यह मदद करता है?
इवो

मैं अत्यधिक data.table सुझाव है कि अगर data.frame अपने ही एकमात्र विकल्प नहीं है
केविन हो

जवाबों:


3

यहाँ एक समाधान है जो मेरे पैकेज सेफजेन का उपयोग करता है , इस मामले में लपेटकर पैकेज फजीयॉइन

हम byएक जटिल स्थिति निर्दिष्ट करने के लिए तर्क का उपयोग कर सकते हैं, जिससे X()वह मान प्राप्त करने के लिए फ़ंक्शन का उपयोग कर रहा है df1, और Y()मूल्य प्राप्त करने के लिए df2

यदि आपकी असली मेजें बड़ी हैं तो यह धीमा या असंभव हो सकता है क्योंकि यह कार्टेशियन उत्पाद करता है, लेकिन यहां यह अच्छी तरह से काम करता है।

हम जो चाहते हैं वह एक पूर्ण जुड़ाव है (सभी पंक्तियों को रखें, और जो शामिल हो सकते हैं, उसमें शामिल हों), और हम शामिल होने पर पहला मूल्य रखना चाहते हैं, और अगले एक अन्य बुद्धिमान को लेते हैं, इसका मतलब है कि हम संघर्ष से निपटना चाहते हैं स्तंभों को पहचानने के नाम से, इसलिए हम तर्क का उपयोग करते हैं 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)

आउटपुट:

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

2019-11-13 को रेप्रेक्स पैकेज (v0.3.0) द्वारा बनाया गया

दुर्भाग्य से fuzzyjoin जब एक बहु में शामिल होने के कर रही है, और एक मैट्रिक्स में सभी स्तंभों coerces safejoin wraps fuzzyjoin तो हम तर्क द्वारा उचित प्रकार के चर परिवर्तित करना होगा अंदर, इस में पहली लाइनों बताते byतर्क।

Safejoin के बारे में अधिक जानकारी : https://github.com/moodymudskipper/safejoin


6

मैंने पहली बार शहर के नामों को चरित्र वैक्टर में बदल दिया, क्योंकि (अगर मैं सही तरीके से समझा गया) तो आप शहर के नाम शामिल करना चाहते हैं जो df2 के भीतर समाहित हैं।

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

फिर उन्हें देश द्वारा मर्ज करें:

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

पुस्तकालय stringrआपको यह देखने की अनुमति देगा कि क्या city.x शहर के भीतर है। यहाँ पर (अंतिम कॉलम देखें):

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

फिर आप तिथियों के बीच के दिनों में अंतर पा सकते हैं:

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

और संख्याओं में अंतर:

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

यहाँ परिणामी डेटाफ़्रेम कैसा दिखता है:

> 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

लेकिन हम उन चीजों को छोड़ना चाहते हैं, जहां city.x शहर के भीतर नहीं पाया गया था। हालांकि, जहां दिन का अंतर 5 से अधिक है या संख्या का अंतर 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

जो बचा है वह तीन पंक्तियाँ हैं जो आपके ऊपर थीं (जिसमें कॉलम 1 में डॉट्स थे)।

अब हम अपने द्वारा बनाए गए तीन कॉलम और दिनांक और शहर को 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

चरण 1: "शहर" और "ctry" पर आधारित डेटा मर्ज करें:

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

चरण 2: पंक्तियों को हटा दें यदि दिनांक प्रविष्टियों के बीच का अंतर> थ्रेशोल्ड है। (दिनों में):

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,]

चरण 3: पंक्तियों को हटा दें यदि संख्याओं के बीच का अंतर है> थ्रेशहोल्ड। निंबर:

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

यदि पंक्तियाँ मेल नहीं खा रही हैं, तो शर्तों को लागू करने से पहले डेटा को मर्ज किया जाना चाहिए।


3

एक विकल्प का उपयोग कर data.table(स्पष्टीकरण इनलाइन):

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

उत्पादन:

          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

आप के cityसाथ greplऔर ctryसरल के साथ मैच का परीक्षण कर सकते हैं ==। उन लोगों के लिए जो यहां तक ​​मेल खाते हैं, आप इसका dateउपयोग करके as.Dateऔर इसकी तुलना करके दिनांक अंतर की गणना कर सकते हैं difftimenumberअंतर एक ही तरह से किया जाता है।

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

यहां एक लचीला दृष्टिकोण है जो आपको आपके द्वारा चुने गए मर्ज मानदंड के किसी भी संग्रह को निर्दिष्ट करने देता है।

तैयारी का काम

मैंने यह सुनिश्चित किया कि सभी तार अंदर थे df1और df2तार थे, कारक नहीं (जैसा कि अन्य उत्तरों में से कई में उल्लेख किया गया है)। मैंने as.Dateउन्हें वास्तविक तिथियां बनाने के लिए तिथियों को भी लपेटा ।

मर्ज मानदंड निर्दिष्ट करें

सूचियों की सूची बनाएं। मुख्य सूची का प्रत्येक तत्व एक मानदंड है; एक कसौटी के सदस्य हैं

  • final.col.name: अंतिम तालिका में स्तंभ का नाम जो हम चाहते हैं
  • col.name.1: कॉलम का नाम df1
  • col.name.2: कॉलम का नाम df2
  • exact: बूलियन; क्या हमें इस स्तंभ पर सटीक मिलान करना चाहिए?
  • threshold: दहलीज (यदि हम सटीक मिलान नहीं कर रहे हैं)
  • match.function: एक फ़ंक्शन जो पंक्तियों से मेल खाता है या नहीं (विशेष मामलों के लिए जैसे greplस्ट्रिंग मिलान के लिए उपयोग करना; ध्यान दें कि यह फ़ंक्शन वेक्टर होना चाहिए)
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)
)

विलय के लिए कार्य

यह फ़ंक्शन तीन तर्क लेता है: दो डेटा फ़्रेम जिन्हें हम मर्ज करना चाहते हैं, और मैच मानदंडों की सूची। यह निम्नानुसार आगे बढ़ता है:

  1. मैच मापदंड के माध्यम से Iterate करें और निर्धारित करें कि कौन सी पंक्ति जोड़े सभी मानदंडों को पूरा करते हैं या नहीं करते हैं। (@ GKi के उत्तर से प्रेरित होकर, यह एक पूर्ण बाहरी जुड़ाव करने के बजाय पंक्ति अनुक्रमणिका का उपयोग करता है, जो बड़े डेटासेट के लिए कम स्मृति-गहन हो सकता है।)
  2. हम चाहते हैं कि पंक्तियों के साथ एक कंकाल डेटा फ्रेम बनाएँ (मिलान के मामले में मर्ज किए गए पंक्तियों, बेजोड़ रिकॉर्ड के लिए पंक्तियों के बिना)।
  3. मूल डेटा फ़्रेमों के स्तंभों के माध्यम से परिवर्तन करें और नए डेटा फ़्रेम में वांछित कॉलमों को पॉप्युलेट करने के लिए उनका उपयोग करें। (यह पहले उन कॉलमों के लिए करें जो मैच के मानदंड में दिखाई देते हैं, और फिर किसी अन्य कॉलम के लिए जो बचे हैं।)
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)
}

फ़ंक्शन लागू करें, और हम कर रहे हैं

df = merge.data.frames(df1, df2, merge.criteria)
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.