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 有很多列,只有 MON
到 SUN
列需要更新。
这里有两种方法使用 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
指示,恕我直言,这更容易检测。如果需要,NA
s 可以通过
变成另一个数值
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
列,在本例中是 Master
,i.MON
指的是第一个 MON
列第二个 data.table 即 Hours_wide
。 mget()
将列名作为字符串,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
中删除了 MON
到 SUN
的列,所以列名没有歧义。因此,可以使用列名 MON
到 SUN
而无需在它们前面加上 i.
,因为只有 MON
到 SUN
的列在 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)
我正在努力解决这个我无法解决的问题!我必须使用数据框(“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 有很多列,只有 MON
到 SUN
列需要更新。
这里有两种方法使用 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
指示,恕我直言,这更容易检测。如果需要,NA
s 可以通过
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
列,在本例中是 Master
,i.MON
指的是第一个 MON
列第二个 data.table 即 Hours_wide
。 mget()
将列名作为字符串,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
中删除了 MON
到 SUN
的列,所以列名没有歧义。因此,可以使用列名 MON
到 SUN
而无需在它们前面加上 i.
,因为只有 MON
到 SUN
的列在 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)