Merge and Left Join 在 R 中不断重复行项目

Merge and Left Join keeps duplicating line items in R

我正在努力解决这个我无法解决的问题!我必须使用数据框(“Master”和“Hours”)。'Master' df 有很多列,但具体如下:

硕士

StoreNumber ... MON TUE WED THU FRI SAT SUN 
     1           0   0   0   0   0   0   0
     2           0   0   0   0   0   0   0
     3           0   0   0   0   0   0   0
     ...

注意:Master df 在 StoreNumber 之间有很多列作为星期几,并且保存了大量数据(大约 3000 家商店)

小时

BranchNumber   Day   TimeDiff
     1         MON   7.50
     1         TUE   6.00
     1         WED   8.50
     1         THU   2.00
     1         FRI   1.00
     1         SAT   2.50
     3         MON   7.50
     3         TUE   6.00
     3         WED   8.50
     3         THU   2.00
     3         FRI   1.00
     3         SAT   2.50
     3         SUN   5.00
    ... 

所以我的想法是尝试将 'Hours' BrandNumber 与 'Master' StoreNumber 相匹配。一旦匹配,它就会将 'Hours' table 中的 Day 列与 'Masters' Table 中的星期几相匹配...它将为每个行,然后用 'TimeDiff' 列中的相应值填充星期几...如果商店和分支机构编号不匹配(如 StoreNumber 2),则它应该跳过该行并移至下一行。另一种情况,如 BranchNumber '1' 没有 SUNDAY 的数据,因此在 'Master' table 中 SUNDAY 单元格应保留为 0...这应该适用于一周中的任何一天。

输出应该是 'Master' Table 但包含来自 'Hours' Table 的一周中所有日期的数据。在此示例中,它应如下所示:

    StoreNumber ... MON    TUE   WED   THU   FRI   SAT   SUN 
         1          7.50  6.00  8.50  2.00  1.00  2.50    0
         2           0      0     0     0     0     0     0
         3          7.50  6.00  8.50  2.00  1.00  2.50  5.00
     ...

我试过的代码是半工作的,但我不确定它是否是正确的方法。我遇到的最大问题是它复制了第一行所期望的行。例如,输出看起来更像这样。

StoreNumber
    1
    2
    2
    3
    3
    4
    4
    5
    5 
    5

所有都是重复的,有些是三倍的,每 87 列都是相同的...但是重复行的星期几都是 0。

 merged <- Master %>% select(-c("MON","TUE","WED","THU","FRI","SAT","SUN")) %>%
 left_join(
 Hours %>% pivot_wider(names_from = Day, values_from = TimeDiff),
 by = c('StoreNumber' = 'BranchNumber'))

 merged <- merged %>% replace(is.na(.),0)

抱歉问了这么长的问题,这个问题困扰了我一段时间所以 help/advice 将不胜感激

根据@GregorThomas 的评论,这里有一个更长更宽的方法:

master <- data.frame(
  StoreNumber = 1:3,
  MON = 0,
  TUE = 0,
  WED = 0,
  THU = 0,
  FRI = 0,
  SAT = 0,
  SUN = 0
)

hours <- read.table(text = "BranchNumber   Day   TimeDiff
     1         MON   7.50
     1         TUE   6.00
     1         WED   8.50
     1         THU   2.00
     1         FRI   1.00
     1         SAT   2.50
     3         MON   7.50
     3         TUE   6.00
     3         WED   8.50
     3         THU   2.00
     3         FRI   1.00
     3         SAT   2.50
     3         SUN   5.00", header = TRUE)

library(dplyr)
library(tidyr)

master %>% 
  pivot_longer(
    cols = MON:SUN,
    names_to = "Day",
    values_to = "Time"
  ) %>% 
  left_join(hours, by = c("StoreNumber" = "BranchNumber", "Day")) %>% 
  mutate(TimeDiff = replace_na(TimeDiff, 0),
         Time = TimeDiff) %>% 
  select(-TimeDiff) %>% 
  pivot_wider(
    id_cols = StoreNumber,
    names_from = Day,
    values_from = Time
  )
# A tibble: 3 x 8
  StoreNumber   MON   TUE   WED   THU   FRI   SAT   SUN
        <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1           1   7.5     6   8.5     2     1   2.5     0
2           2   0       0   0       0     0   0       0
3           3   7.5     6   8.5     2     1   2.5     5

编辑

这是一个版本,其中 master 有额外的列并存储输出:

master <- data.frame(
  StoreNumber = 1:3,
  other_colum = c("A", "B", "C"),
  MON = 0,
  TUE = 0,
  WED = 0,
  THU = 0,
  FRI = 0,
  SAT = 0,
  SUN = 0
)

hours <- read.table(text = "BranchNumber   Day   TimeDiff
     1         MON   7.50
     1         TUE   6.00
     1         WED   8.50
     1         THU   2.00
     1         FRI   1.00
     1         SAT   2.50
     3         MON   7.50
     3         TUE   6.00
     3         WED   8.50
     3         THU   2.00
     3         FRI   1.00
     3         SAT   2.50
     3         SUN   5.00", header = TRUE)

library(dplyr)
library(tidyr)

master <- master %>% 
  pivot_longer(
    cols = MON:SUN,
    names_to = "Day",
    values_to = "Time"
  ) %>% 
  left_join(hours, by = c("StoreNumber" = "BranchNumber", "Day")) %>% 
  mutate(TimeDiff = replace_na(TimeDiff, 0),
         Time = TimeDiff) %>% 
  select(-TimeDiff) %>% 
  pivot_wider(
    names_from = Day,
    values_from = Time
  )

master
# A tibble: 3 x 9
  StoreNumber other_colum   MON   TUE   WED   THU   FRI   SAT   SUN
        <int> <chr>       <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1           1 A             7.5     6   8.5     2     1   2.5     0
2           2 B             0       0   0       0     0   0       0
3           3 C             7.5     6   8.5     2     1   2.5     5

如果我没理解错,Master table 有很多列,只有 MONSUN 列需要更新。

这里有两种方法使用 data.table 的能力在连接 中 更新。仅通过引用修改相关列,即不复制整个数据对象。它避免来回重塑(或旋转)Master table。

变体 1

library(data.table)
days <- names(Master)[which(names(Master) == "MON") + (0:6)]
setDT(Master)[, (days) := lapply(.SD, as.double), .SDcols = days]
for (d in days) {
  Master[Hours, on =.(StoreNumber = BranchNumber), (d) := TimeDiff[d == Day], by = .EACHI]
}
Master[]
   StoreNumber OtherCol MON TUE WED THU FRI SAT SUN
1:           1        a 7.5   6 8.5   2   1 2.5   0
2:           2        b 0.0   0 0.0   0   0 0.0   0
3:           3        c 7.5   6 8.5   2   1 2.5   5

说明

  • days 包含列的名称。
    days <- names(Master)[which(names(Master) == "MON") + (0:6)] 等同于
    days <- c("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN")
  • data.table 更新部分列时需要一致的数据类型。 Master 中的日期列初始化为整数零,但 Hours 中的 TimeDiff 是数字。因此,Master 中的日期列在更新前被强制加倍。
  • for 循环遍历每一天的列并为此列执行 update join。对于每场比赛 (by = .EACHI),都会选择相关日期的 Timediff

为了验证Master没有被复制我们可以调用

data.table::address(Master)

运行前后:Master的地址没有变化。

变体 2

这种方法有点精简。它还使用 update join 但它与变体 1 不同,因为它将 Hours 从长格式重塑(或旋转)为宽格式,并从 Master 而不是强制一堆整数零键入数字:

    library(data.table)
    days <- c("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN")
    Hours_wide <- dcast(setDT(Hours)[, Day := ordered(Day, levels = days)], BranchNumber ~ Day)
    setDT(Master)[, (days) := NULL][
      Hours_wide, on = .(StoreNumber = BranchNumber), (days) := mget(paste0("i.", days))]
    Master[]
   StoreNumber OtherCol MON TUE WED THU FRI SAT SUN
1:           1        a 7.5   6 8.5   2   1 2.5  NA
2:           2        b  NA  NA  NA  NA  NA  NA  NA
3:           3        c 7.5   6 8.5   2   1 2.5   5

请注意,缺少的元素现在被初始化为/由 NA 指示,恕我直言,这更容易检测。如果需要,NAs 可以通过

变成另一个数值
Master[, (days) := lapply(.SD, nafill, fill = 0), .SDcols = days][] 
   StoreNumber OtherCol MON TUE WED THU FRI SAT SUN
1:           1        a 7.5   6 8.5   2   1 2.5   0
2:           2        b 0.0   0 0.0   0   0 0.0   0
3:           3        c 7.5   6 8.5   2   1 2.5   5

此方法使用 mget(paste0("i.", days))Hours 中选择天数列。如果在连接中的两个 data.tables 中都有同名的列,我们可以通过在列名前加上 x.i. 来区分这些列。因此,x.MON 指的是第一个 data.table 中的 MON 列,在本例中是 Masteri.MON 指的是第一个 MON 列第二个 data.table 即 Hours_widemget() 将列名作为字符串,returns 将各个列的值列表。

变体 2 - 编辑 1

以上代码可以简化为

setDT(Master)[, (days) := NULL][
  Hours_wide, on = .(StoreNumber = BranchNumber), (days) := mget(days)][]
   StoreNumber OtherCol MON TUE WED THU FRI SAT SUN
1:           1        a 7.5   6 8.5   2   1 2.5  NA
2:           2        b  NA  NA  NA  NA  NA  NA  NA
3:           3        c 7.5   6 8.5   2   1 2.5   5

因为 setDT(Master)[, (days) := NULL] 已经从 Master 中删除了 MONSUN 的列,所以列名没有歧义。因此,可以使用列名 MONSUN 而无需在它们前面加上 i.,因为只有 MONSUN 的列在 Hours_wide.

变体 2 - 编辑 2

自 2021 年 5 月 10 日起,开发版本 1.14.1 已添加用于 在 data.table 上编程的新界面(参见 item 10 in NEWS以及新的小插图 在 data.table 上编程)。建议使用新的 env 参数代替 get()/ mget()

library(data.table) # development version 1.14.1 used
days <- c("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN")
Hours_wide <- dcast(setDT(Hours)[, Day := ordered(Day, levels = days)], BranchNumber ~ Day)
setDT(Master)[, (days) := NULL][
  Hours_wide, on = .(StoreNumber = BranchNumber), (days) := s,
  env = list(s = as.list(days))][]
   StoreNumber OtherCol MON TUE WED THU FRI SAT SUN
1:           1        a 7.5   6 8.5   2   1 2.5  NA
2:           2        b  NA  NA  NA  NA  NA  NA  NA
3:           3        c 7.5   6 8.5   2   1 2.5   5

变体 3:env 参数和 fcoalesce()

OP 的预期结果显示 0 而不是 NA。对于上面的 Variants 2,这是通过 单独的更新步骤 使用 nafill().

实现的

可以通过在 update join:

中使用 fcoalesce() 函数来避免这个单独的更新步骤
library(data.table) # development version 1.14.1 used
days <- c("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN")
Hours_wide <- dcast(setDT(Hours)[, Day := ordered(Day, levels = days)], BranchNumber ~ Day)
setDT(Master)[, (days) := lapply(.SD, as.double), .SDcols = days][
  Hours_wide, on = .(StoreNumber = BranchNumber), (days) := lapply(s, fcoalesce, 0), 
  env = list(s = as.list(paste0("i.", days)))][]
   StoreNumber OtherCol MON TUE WED THU FRI SAT SUN
1:           1        a 7.5   6 8.5   2   1 2.5   0
2:           2        b 0.0   0 0.0   0   0 0.0   0
3:           3        c 7.5   6 8.5   2   1 2.5   5

数据

library(data.table)

Master <- fread("
StoreNumber OtherCol MON TUE WED THU FRI SAT SUN 
     1         a      0   0   0   0   0   0   0
     2         b      0   0   0   0   0   0   0
     3         c      0   0   0   0   0   0   0
", data.table = FALSE)
 
Hours <- fread("
BranchNumber   Day   TimeDiff
     1         MON   7.50
     1         TUE   6.00
     1         WED   8.50
     1         THU   2.00
     1         FRI   1.00
     1         SAT   2.50
     3         MON   7.50
     3         TUE   6.00
     3         WED   8.50
     3         THU   2.00
     3         FRI   1.00
     3         SAT   2.50
     3         SUN   5.00
", data.table = FALSE)