通过不同的总和组合和关键列合并两个数据框

时间:2019-01-04 15:36:19

标签: r merge match left-join combinations

样本数据:

Bilagstoptekst <- c("A", "A", "A", "A", "A","B","B","C","C","C","C","C","C","C")
AKT <- c("80","80","80","25","25","25","25","80","80","80","80","80","80","80")
IA <- c("HUVE", "HUVE", "HUBO", "BILÅ", "BILÅ", "BILÅ","BILÅ", "HUBO","HUBO","HUBO","HUBO","HUBO","HUBO","HUBO")
Belob <- c(100,100,50,75,40,60,400,100,100,100,100,100,333,333)
FPT8 <- data.frame(Bilagstoptekst, AKT, IA, Belob)

> FPT8
 Bilagstoptekst AKT   IA   Belob
           A    80   HUVE   100
           A    80   HUVE   100
           A    80   HUBO    50
           A    25   BILÅ    75
           A    25   BILÅ    40
           B    25   BILÅ    60
           B    25   BILÅ   400
           C    80   HUBO   100
           C    80   HUBO   100
           C    80   HUBO   100
           C    80   HUBO   100
           C    80   HUBO   100
           C    80   HUBO   333
           C    80   HUBO   333

Bilagstoptekst <- c("A", "A", "A", "A", "B", "C", "C")
AKT <- c("80", "80", "25", "25", "25", "80", "80")
IA <- c("HUVE", "HUBO", "BILÅ", "BILÅ", "BILÅ", "HUBO", "HUBO")
RegKonto <- c(4,5,7,1,6,3,9)
Psteksnr <- c(1,6,8,2,5,7,9)
Belob_sum <- c(200,50,75,40,460,500,666)
G69 <- data.frame(Bilagstoptekst, AKT, IA, RegKonto, Psteksnr, Belob_sum)

> G69
Bilagstoptekst AKT   IA     RegKonto Psteksnr Belob_sum
          A    80   HUVE        4        1       200
          A    80   HUBO        5        6        50
          A    25   BILÅ        7        8        75
          A    25   BILÅ        1        2        40
          B    25   BILÅ        6        5       460
          C    80   HUBO        3        7       500
          C    80   HUBO        9        9       666  

现在,我的真实数据集非常大。

我要做的是将 RegKonto Psteksnr G69 合并到 FPT8

我有三个关键列,应该在两个数据框中相互匹配:

爱荷华州AKT的Bilagstoptekst

但是我不能仅仅使用left_join,因为还有另一条规则。 FPT8 $ Belob 应该与 G69 $ Belob_sum 相匹配。有时它确实匹配(在我的示例数据第3行中为fx)。有时,我可以通过将所有 FPT8 $ Belob 加在一起并将该数字(结合我的3个关键列)与 G69 $ Belob_sum (第1行中的fx)进行匹配来找到匹配项和2)。

但是有时将哪些行加在一起以找到正确的匹配项是随机的(实际上,它不是随机的,但确实感觉像这样!)。像最后一个带有bilagstoptekst == C的行。

我要问的是,是否有一种方法可以将不同的组合加起来并用于合并。

预期输出:

> FPT8
 Bilagstoptekst AKT   IA   Belob  RegKonto Psteksnr 
           A    80   HUVE   100       4        1 
           A    80   HUVE   100       4        1 
           A    80   HUBO    50       5        6  
           A    25   BILÅ    75       7        8
           A    25   BILÅ    40       1        2
           B    25   BILÅ    60       6        5
           B    25   BILÅ   400       6        5
           C    80   HUBO   100       3        7 
           C    80   HUBO   100       3        7 
           C    80   HUBO   100       3        7 
           C    80   HUBO   100       3        7 
           C    80   HUBO   100       3        7 
           C    80   HUBO   333       9        9
           C    80   HUBO   333       9        9

我已经尝试过的方法:

我已经展开了-对于每一行键-FPT8 $ Belob有什么不同的值。

dt <- as.data.table(FPT8)

dt[, idx := rowid(Bilagstoptekst, AKT, IA)] # creates the timevar

out <- dcast(dt, 
         Bilagstoptekst + AKT + IA~ paste0("Belob", idx),
         value.var = "Belob")

然后我对展开的FPT8 $ Belob的总和进行了不同的组合:

# Adding together two different FPT8$Belob - all combinations
output <- as.data.frame(combn(ncol(out[,-c(1:3)]), m=2, FUN =function(x) rowSums(out[,-c(1:3)][x])))
names(output) <- paste0("sum_", combn(names(out[,-c(1:3)]), 2, FUN = paste, collapse="_"))

在此之后,我来回合并,我真的不想进入这一部分,因为当我每个键有4个以上不同的FPT8 $ Belob(3列)时,这真是一团糟。因此,我绝对需要一种更流畅的方法来执行此操作。

希望有人可以帮助我。

编辑:行如何配对以及更多说明

所以我的FPT8数据是一笔付款(Belob表示金额)。 G69数据就是账单。我需要找到正确的匹配项,但是我的问题是,有时人们会选择将帐单分成较小的部分。因此,FPT8数据大于G69数据。

让我解释一下。

我有4个要匹配的关键列:Bilagstoptekst,AKT,IA和Belob。前三个应该始终在FPT8数据中找到完全匹配的内容。有时Belob确实与G69中的Belob_sum相匹配(逐行),有时我们需要FPT8 Belob行的总和与Bilagstoptekst,AKT和IA 中的相同,以与G69中的Belob_sum相匹配。让我尝试用下面的示例数据来显示它。

FPT8:

FPT8

基于我的3个关键列** Bilagstoptekst *, AKT IA ,前两行“相同”(即同一笔账单已支付了两次)。我添加了ID列作为第一列,而我的真实数据中没有。这仅是为了说明。因此,这两行我称为ID = 1。

第3行(ID = 2)与我的示例FPT8数据中的其他行不匹配,因为键列组合没有其他任何内容(即,该人一次付清了全部账单-这一个将很容易与G69账单信息匹配。

在底部,所有Bilagstoptekst == C具有三个关键列(C,80和HUBO)的相同组合。那是同一张账单。但是这些不是同一张账单。在这种情况下,我可以在G69数据中找到两个匹配项。我怎么知道哪一个是正确的?我看一下FPT8 $ Belob和G69 $ Belob_sum列。

G69:

G69

因此,如果我手动执行此操作,则尝试在FPT8 $ Belob中找到与G69 $ Belob_sum和其他3个关键列匹配的总和的不同组合。 Fx我可以看到,Belob中的最后两行总计为666,与G69中的最后一行相匹配。由于100 * 5 = 500,另一个Bilagstoptekst == C,AKT = 80和IA = HUBO匹配G69中的倒数第二行。

所需的输出:

Output

我已经添加了一些颜色,所以希望现在更容易理解。

1 个答案:

答案 0 :(得分:1)

嘿!

我看不到任何一站式解决方案,但是通过一些简单的规则,我们可以逐步进行匹配。

此外,最终输出不能匹配您的精美图像,因为您没有包括该数据(reg.nr列)。

首先,最简单的方法是:BelobBelob_sum直接匹配,因为只有一行:

library(dplyr)

# Rule 1: Easy matching -----

s1 <- inner_join(FPT8, G69, by=c('Bilagstoptekst','AKT','IA','Belob'='Belob_sum'))
not_matched1 <- anti_join(FPT8, s1,by=c('Bilagstoptekst','AKT','IA')) 

最后一行检查不匹配的内容。因此,我们应用规则2,使用分组总和:

# Rule 2: Calculate Belob_sum to match by ---------------
s2 <- not_matched1 %>% group_by(Bilagstoptekst, AKT, IA) %>% 
  mutate(Belob_sum=sum(Belob)) %>%
  inner_join(G69, by=c('Bilagstoptekst','AKT','IA','Belob_sum'))

matched <- bind_rows(s1, s2)

not_matched2 <- anti_join(FPT8, matched, by=c('Bilagstoptekst','AKT','IA')) 

同样,我们检查不匹配的内容并将两者合并。然后,规则3。现在这很棘手,并且仅在付款均分的假设下有效

# Rule 3: More gætværk ---------------
# We assume the payed amounts are divided *equally*
s3 <- not_matched2 %>% group_by(Bilagstoptekst, AKT, IA, Belob) %>% 
  mutate(Belob_sum=sum(Belob)) %>%
  inner_join(G69, by=c('Bilagstoptekst','AKT','IA','Belob_sum'))

matched <- bind_rows(matched, s3)

not_matched3 <- anti_join(FPT8, matched, by=c('Bilagstoptekst','AKT','IA')) 
# not_matched3 is now empty!

> matched
   Bilagstoptekst AKT   IA Belob RegKonto Psteksnr Belob_sum
1               A  80 HUBO    50        5        6        NA
2               A  25 BILÅ    75        7        8        NA
3               A  25 BILÅ    40        1        2        NA
4               A  80 HUVE   100        4        1       200
5               A  80 HUVE   100        4        1       200
6               B  25 BILÅ    60        6        5       460
7               B  25 BILÅ   400        6        5       460
8               C  80 HUBO   100        3        7       500
9               C  80 HUBO   100        3        7       500
10              C  80 HUBO   100        3        7       500
11              C  80 HUBO   100        3        7       500
12              C  80 HUBO   100        3        7       500
13              C  80 HUBO   333        9        9       666
14              C  80 HUBO   333        9        9       666

现在,如果您的“ C”组没有分成大组,则您会有点不高兴,您将不得不手动整理数据以识别相同的组分组,或应用其他算法尝试在给定的限制下匹配最佳分组。

**最后的提示:**

R和dplyr 可以使用丹麦字母,但这是小便。当将一列称为字符串时,很简单:

FPT8[,'Beløb']

但是,如果您在dplyr 中将它们用作变量名,请使用反斜线:

FPT8 %>% summarise(`Beløb_sum`=sum(`Beløb`))

更新

我已经找到了一种解决方案,可以基于迭代方法将各种不相等的分组求和。这是一个示例,您必须将其重构为解决方案的第四步。但是,如果您可以将搜索范围限制为匹配“ Bilagstoptekst”,“ AKT”和“ IA”的内容,那么我想您应该很好。

groups <- data.frame(name=letters[1:4], sumsize=c(100,130, 80,99), stringsAsFactors = FALSE)
subpayments <- data.frame(paid=c(50,40,10,50,43,37,20,25,20,15,42,57))
stopifnot(sum(groups$sumsize) == sum(subpayments$paid))
subpayments$id <- 1:nrow(subpayments)

groups <- groups[order(groups$sumsize, decreasing=TRUE),]
subpayments <- subpayments[order(subpayments$paid, decreasing=TRUE),]
subpayments$group <- NA

for (g in seq_along(groups$name)) {
  sumsize <- 0
  #subpayments$tried <- FALSE
  maxsize <- groups$sumsize[g]
  path <- c()
  attemptspath <- list()
  attempts <- vector('logical', nrow(subpayments))
  #attempts[1] <- TRUE
  #attemptspath <- list(1)
  i <- 0

  while (sumsize < maxsize) {
    #browser()
    last_i <- i
    i <- min(which(subpayments$paid <= (maxsize - sumsize) & !attempts & is.na(subpayments$group)))
    if (is.infinite(i)) {
      # current path did not succed, backpeddle and try another route
      #cat('is infinite.', i, 'path', path, '\n')
      #cat('attempts:', attempts, '\n')
      if (length(path) == 0) {
        # at the beginning again and exhausted our attempts
        break
      }
      if (is.infinite(last_i)) {
        attempts[attemptspath[[length(path)+1]]]  <- FALSE
        attemptspath[[length(path)+1]] <- logical(0)
        #last <- path[length(path)]
        #path <- path[-length(path)]
        #sumsize <- sumsize - subpayments$paid[last]

      }
      # backpeddle; remove last attempt and retry
      last <- path[length(path)]
      path <- path[-length(path)]
      sumsize <- sumsize - subpayments$paid[last]
      print(cbind(subpayments, attempts))
      next
    }
    #cat('i:', i, 'path before:', path, ' -- ')
    path <- c(path, i)
    sumsize <- sumsize + subpayments$paid[i]
    #cat('path after:', path, 'sumsize:', sumsize, '\n')
    attemptspath[[length(path)]] <- c(unlist(attemptspath[length(path)]) %||% integer(0), i)
    attempts[i] <- TRUE
    #print(attemptspath)
    #print(cbind(subpayments, attempts))
  }

  if (length(path) > 0)
    subpayments$group[path] <- groups$name[g]
}

print(subpayments)