hợp nhất các tệp dữ liệu dựa trên nhiều cột và ngưỡng


11

Tôi có hai data.frames với nhiều cột thông thường (ở đây: date, city, ctry, và ( other_) number).

Bây giờ tôi muốn hợp nhất chúng trên các cột trên nhưng chịu được một số mức độ khác biệt:

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

Nếu sự khác biệt giữa các datemục là > threshold.date(tính theo ngày) hoặc > threshold.numbers , tôi không muốn các dòng được hợp nhất. Tương tự, nếu mục nhập trong citylà một chuỗi con của dfmục nhập khác trong citycột, tôi muốn các dòng được hợp nhất. [Nếu bất cứ ai có ý tưởng tốt hơn để kiểm tra độ tương tự của tên thành phố thực tế, tôi rất vui khi biết về nó.] (Và giữ các mục đầu tiên dfcủa date, citycountrycả hai ( other_) numbercột và tất cả các cột khác trong df.

Hãy xem xét ví dụ sau:

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

Bây giờ, tôi muốn hợp nhất data.framesvà nhận được một dfdòng được hợp nhất nếu các điều kiện trên được đáp ứng.

(Cột đầu tiên chỉ để thuận tiện cho bạn: phía sau chữ số đầu tiên, biểu thị trường hợp ban đầu, nó cho biết liệu các dòng được hợp nhất ( .) hoặc liệu các dòng là từ df1( 1) hoặc 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      #

Tôi đã thử các cách triển khai khác nhau để hợp nhất chúng nhưng không thể thực hiện được ngưỡng.

Lời xin lỗi EDIT cho công thức không rõ ràng - Tôi muốn giữ lại tất cả các hàng và nhận được một chỉ báo cho dù hàng đó được khớp, không khớp và từ df1 hoặc không khớp và từ df2.

mã giả là:

  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
Đây có phải là khung dữ liệu cuối cùng mà bạn đã in đầu ra mà bạn muốn nhận không? tức là nên có 17 hàng cuối cùng? Hay chỉ là 3 đánh dấu với một .?
camille

Tôi thực sự muốn tất cả các hàng được bảo tồn nhưng với một chỉ báo nếu chúng được khớp. Xin lỗi nếu điều này không rõ ràng; Tôi chỉnh sửa câu hỏi cho phù hợp.
Ivo

Vậy có nghĩa là bạn muốn 10 hàng như ban đầu?
camille

Tôi đã thêm mã giả để làm cho nó rõ ràng hơn; không giúp đỡ à?
Ivo

Tôi rất muốn đề xuất data.table nếu data.frame không phải là lựa chọn duy nhất của bạn
Kevin Ho

Câu trả lời:


3

Đây là một giải pháp sử dụng gói safjoin của tôi , trong trường hợp này là gói fuzzyjoin .

Chúng ta có thể sử dụng byđối số để xác định một điều kiện phức tạp, sử dụng hàm X()để lấy giá trị từ anh ta df1Y()để lấy giá trị từ đó df2.

Nếu các bảng thực sự của bạn lớn thì điều này có thể chậm hoặc không thể vì nó là một sản phẩm cartesian, nhưng ở đây nó hoạt động tốt.

Điều chúng tôi muốn là tham gia đầy đủ (giữ tất cả các hàng và tham gia những gì có thể tham gia) và chúng tôi muốn giữ giá trị đầu tiên khi họ tham gia và tiếp theo một cách khôn ngoan khác, điều này có nghĩa là chúng tôi muốn giải quyết xung đột các cột được đặt tên giống hệt nhau bằng cách kết hợp lại, vì vậy chúng tôi sử dụng đối số 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)

đầu ra:

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

Được tạo vào ngày 2019-11-13 bởi gói reprex (v0.3.0)

Thật không may, fuzzyjoin ép buộc tất cả các cột trong một ma trận khi thực hiện đa liên kết và safejoin kết thúc fuzzyjoin để chúng ta phải chuyển đổi các biến thành loại thích hợp bên trong đối số, điều này giải thích các dòng đầu tiên trong byđối số.

Tìm hiểu thêm về safjoin : https://github.com/moodymudskipper/safejoin


6

Trước tiên tôi biến tên thành phố thành vectơ ký tự, vì (nếu tôi hiểu chính xác) bạn muốn bao gồm tên thành phố được chứa trong df2.

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

Sau đó hợp nhất chúng theo quốc gia:

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

Thư viện stringrsẽ cho phép bạn xem nếu city.x nằm trong city.y tại đây (xem cột cuối cùng):

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

Sau đó, bạn có thể nhận được sự khác biệt trong ngày giữa các ngày:

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

và sự khác biệt về số lượng:

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

Đây là những gì các khung dữ liệu kết quả trông như thế nào:

> 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

Nhưng chúng tôi muốn bỏ đi những thứ không tìm thấy city.x trong city.y, trong đó chênh lệch ngày lớn hơn 5 hoặc chênh lệch số lớn hơn 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

Những gì còn lại là ba hàng mà bạn đã có ở trên (có chứa các dấu chấm trong cột 1).

Bây giờ chúng tôi có thể thả ba cột chúng tôi đã tạo và ngày và thành phố từ 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

Bước 1: Hợp nhất dữ liệu dựa trên "thành phố" và "ctry":

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

Bước 2: Xóa các hàng nếu chênh lệch giữa các mục nhập ngày là> ngưỡng.date (tính theo ngày):

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

Bước 3: Xóa các hàng nếu chênh lệch giữa các số là> threshkeep.number:

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

Dữ liệu phải được hợp nhất trước khi áp dụng các điều kiện, trong trường hợp các hàng không khớp.


3

Một tùy chọn sử dụng data.table(giải thích nội tuyến):

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

đầu ra:

          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

Bạn có thể kiểm tra citytrận đấu với greplctryđơn giản với ==. Đối với những người phù hợp cho đến đây, bạn có thể tính toán chênh lệch ngày bằng cách chuyển đổi sang datesử dụng as.Datevà so sánh nó với a difftime. Sự numberkhác biệt được thực hiện theo cùng một cách.

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

Đây là một cách tiếp cận linh hoạt cho phép bạn chỉ định bất kỳ bộ tiêu chí hợp nhất nào bạn chọn.

Chuẩn bị công việc

Tôi đảm bảo rằng tất cả các chuỗi trong df1df2là các chuỗi, không phải là các yếu tố (như đã lưu ý trong một số câu trả lời khác). Tôi cũng gói ngày tháng as.Dateđể làm cho họ ngày thực sự.

Chỉ định tiêu chí hợp nhất

Tạo một danh sách các danh sách. Mỗi yếu tố của danh sách chính là một tiêu chí; các thành viên của một tiêu chí là

  • final.col.name: tên của cột chúng ta muốn trong bảng cuối cùng
  • col.name.1: tên của cột trong df1
  • col.name.2: tên của cột trong df2
  • exact: boolean; chúng ta nên làm khớp chính xác trên cột này?
  • threshold: ngưỡng (nếu chúng tôi không thực hiện khớp chính xác)
  • match.function: một hàm trả về việc các hàng có khớp hay không (đối với các trường hợp đặc biệt như sử dụng greplđể khớp chuỗi; lưu ý rằng hàm này phải được vector hóa)
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)
)

Chức năng hợp nhất

Hàm này có ba đối số: hai khung dữ liệu mà chúng ta muốn hợp nhất và danh sách các tiêu chí khớp. Nó tiến hành như sau:

  1. Lặp lại thông qua các tiêu chí khớp và xác định cặp hàng nào làm hoặc không đáp ứng tất cả các tiêu chí. (Lấy cảm hứng từ câu trả lời của @ GKi's, nó sử dụng các chỉ mục hàng thay vì thực hiện tham gia bên ngoài đầy đủ, có thể ít tốn bộ nhớ hơn cho các bộ dữ liệu lớn.)
  2. Tạo khung dữ liệu khung chỉ với các hàng chúng ta muốn (các hàng được hợp nhất trong trường hợp khớp, các hàng chưa được trộn cho các bản ghi chưa từng có).
  3. Lặp lại qua các cột của khung dữ liệu gốc và sử dụng chúng để điền vào các cột mong muốn trong khung dữ liệu mới. (Làm điều này trước tiên cho các cột xuất hiện trong tiêu chí khớp và sau đó cho bất kỳ cột nào còn lại.)
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)
}

Áp dụng chức năng, và chúng ta đã hoàn thành

df = merge.data.frames(df1, df2, merge.criteria)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.