Объединить два кадра данных, используя разные комбинации сумм и ключевые столбцы. - PullRequest
0 голосов
/ 04 января 2019

Пример данных:

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 .

У меня есть три ключевых столбца, которые должны совпадать друг с другом в двух фреймах данных:

Билагстоптекст, АКТ, ИА .

Но я не могу просто пойти налево, используя их, так как есть другое правило. FPT8 $ Белоб должен соответствовать G69 $ Белоб_сум . И иногда это совпадает (fx в моем примере строки данных 3). Иногда я могу найти совпадение, сложив все FPT8 $ Belob вместе и сопоставив это число (в сочетании с моими 3 ключевыми столбцами) с G69 $ Belob_sum (fx в строке 1 и 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 $ Белоба.

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 $, которые я разложил:

# 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 $ Белоба на ключ (3 столбца). Поэтому мне определенно нужен более плавный способ сделать это.

Надеюсь, кто-нибудь сможет мне помочь.

РЕДАКТИРОВАТЬ: Как строки объединяются и немного больше объяснений

Так что мои данные FPT8 - это куча платежей (Белоб означает сумму денег). Данные G69 - это счета. Мне нужно найти правильное соответствие, но моя проблема в том, что иногда люди предпочитают разделить свой счет на более мелкие платежи. Поэтому данные FPT8 больше, чем данные G69.

Позвольте мне объяснить ..

У меня есть 4 ключевых столбца: Билагстоптекст, АКТ, ИА и Белоб. Сначала 3 должны всегда найти точное совпадение в данных FPT8. Иногда Belob совпадает с Belob_sum в G69 (строка за строкой), иногда нам нужна комбинация сумм строк FPT8 Belob в одной и той же в Bilagstoptekst, AKT и IA , чтобы соответствовать Belob_sum в G69. Позвольте мне попытаться показать это с моими примерами данных ниже.

FPT8:

FPT8

Исходя из моих трех ключевых столбцов ** Bilagstoptekst *, AKT и IA , первые две строки "одинаковы" (то есть один и тот же счет был оплачен более двух раз). Я добавил столбец идентификаторов в качестве первого столбца, которого нет в моих реальных данных. Это только для объяснения. Поэтому эти две строки я называю 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 Я вижу, что две последние строки складываются до 666 в Belob, что соответствует последней строке в G69. Другой Bilagstoptekst == C, AKT = 80 и IA = HUBO соответствует второй последней строке в G69, так как 100 * 5 = 500.

Желаемый вывод:

Output

Я добавил несколько цветов, поэтому надеюсь, что теперь это легче понять.

1 Ответ

0 голосов
/ 07 января 2019

Хей!

Я не вижу ни одного одношагового решения, но с некоторыми простыми правилами мы можем пошагово сопоставить их.

Кроме того, окончательный вывод не может соответствовать вашим красивым изображениям, так как вы не включили эти данные (столбец reg.nr.).

Во-первых, самое простое: где Belob соответствует Belob_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`))

Обновление:

Я нашел решение, которое может суммировать различные, неравномерно разделенные группы на основе итеративного подхода. Это пример того, что вам придется выполнить рефакторинг в 4-й шаг для вашего решения. Но если вы можете ограничить поиск теми, у кого есть совпадения с "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)
...