匹配数据帧排除最后的非 NA 值并忽略顺序
Match Dataframes Excluding Last Non-NA Value and disregarding order
我有两个数据框:
Partner<-c("Alpha","Beta","Zeta")
COL1<-c("A","C","M")
COL2<-c("B","D","K")
COL3<-c("C","F",NA)
COL4<-c("D",NA,NA)
df1<-data.frame(Partner,COL1,COL2,COL3,COL4)
lift<-c(9,10,11,12,12,23,12,24)
RULE1<-c("B","B","D","A","C","K","M","K")
RULE2<-c("A","A","C","B","A","M","T","M")
RULE3<-c("G","D","M","C" ,"M", "E",NA,NA)
RULE4<-c(NA,NA,"K","D" ,NA, NA,NA,NA)
df2<-data.frame(lift,RULE1,RULE2,RULE3,RULE4)
df1
Partner COL1 COL2 COL3 COL4
Alpha A B C D
Beta C D F NA
Zeta M K NA NA
df2
lift RULE1 RULE2 RULE3 RULE4
9 B A G NA
10 B A D NA
11 D C M K
12 A B C D
12 C A M NA
23 K M E NA
12 M T NA NA
24 K M NA NA
这是购物篮分析。 df1 是 customer/partner 购买了列出的每个项目:A、B、C...等
df2 是与过去购买的商品相关的推荐。
每个 df2 行中的最后一个值代表建议。因此,最后一个非 NA 值的每一行中的前面值是 "baskets".
比如df2第一行写着:如果B和A一起买,推荐G。
我想知道 df1 的每个合作伙伴是否都购买了每行中不包括最终值的所有值,因为这是建议。然后将该建议添加到新数据框每一行的末尾。
例如:
对于合作伙伴:Alpha,从第一行开始推荐值 G 好吗?答案是肯定的,因为他们购买了 df2(A 和 B)中该行的所有值。
对于合作伙伴:Beta,推荐值G 并不好,因为并非df2 第一行中的所有值都在Beta 行中找到。
最终输出:
Partner COL1 COL2 COL3 COL4 lift RULE1 RULE2 RULE3 RULE4 Does Last Non-NA Value Exist in Row?
Alpha A B C D 9 B A G NA No
Alpha A B C D 10 B A D NA Yes
Alpha A B C D 12 A B C D Yes
Alpha A B C D 12 C A M NA No
Zeta M K NA NA 23 K M E NA No
Zeta M K NA NA 12 M T NA NA No
Zeta M K NA NA 24 K M NA NA Yes
为清楚起见写出结果:
df3
第 1 行输出 "No",因为在 Alpha Partner 中找不到 G,并且 G 之前的所有值都出现在 Alpha Partner (B,A)
行 2 输出 "Yes" 因为 D 在 Alpha Partner 中找到并且 D 之前的所有值都出现在 Alpha Partner (B,A)
第 3 行输出 "Yes" 因为 D 在 Alpha Partner 中找到并且 D 之前的所有值都出现在 Alpha Partner (A,B,C)
第 4 行输出 "No" 因为在 Alpha Partner 中找不到 M,并且 M 之前的所有值都出现在 Alpha Partner (C,A)
第 5 行输出 "No" 因为在 Zeta Partner 中找不到 E,并且 E 之前的所有值都出现在 Zeta Partner (K,M)
第 6 行输出 "No" 因为在 Zeta Partner 中找不到 T 并且 T 之前的所有值都出现在 Zeta Partner (M)
第 7 行输出 "Yes" 因为 M 在 Zeta Partner 中找到并且 M 之前的所有值都出现在 Zeta Partner (K)
我认为这必须是某种类型的连接或匹配,但不知道该怎么做。
如果有人能帮我解决这个问题,这将非常有帮助。
谢谢。
这是一次尝试:
df1<-cbind(df1_id=1:nrow(df1),df1)
df2 <- cbind(df2_id=1:nrow(df2),df2)
d11 <- df1 %>% gather(Col, Value,starts_with("C")) #Long
d11 <- d11 %>% na.omit() %>%group_by(df1_id) %>% slice(-n()) #remove last non NA
d22 <- df2 %>% gather(Rule, Value,starts_with("R"))
res <- inner_join(d11,d22)
rm(d22)
rm(d11)
final<-cbind(df1[res$df1_id,],df2[res$df2_id,])
final$Exist <- apply(final, 1, FUN = function(x)
c("No", "Yes")[(anyDuplicated(x[!is.na(x) & x != "" ])!=0) +1])
但这没有用,因为它没有考虑所有的值,只有其中一个匹配...不是全部。
这非常棘手,因为 n 位客户的购买必须与一组 m 规则进行比较。除此之外,还有两点增加了复杂性:
df2
中的最后一个非 NA RULE
列在语义上与其他列不同。不幸的是,给定的数据结构并没有反映这一点。因此,df2
缺少明确的 recommended 列。
最后还要判断小伙伴是否已经购买了推荐商品
出于性能原因,下面的方法依赖于 melt()
、dcast()
和 data.table
包的连接操作。但是,为了避免创建 n * m 行的笛卡尔叉积,使用了循环。
EDIT dcast()
已移出 lapply()
函数。
为 n:m 加入准备数据
library(data.table)
# convert to data.table and add row numbers
# here, a copy is used insteasd of setDT() in order to rename the data.tables
purchases <- as.data.table(df1)[, rnp := seq_len(.N)]
rules <- as.data.table(df2)[, rnr := seq_len(.N)]
# prepare purchases for joins
lp <- melt(purchases, id.vars = c("rnp", "Partner"), na.rm = TRUE)
wp <- dcast(lp, rnp ~ value, drop = FALSE)
wp
# rnp A B C D F K M
#1: 1 A B C D NA NA NA
#2: 2 NA NA C D F NA NA
#3: 3 NA NA NA NA NA K M
# prepare rules
lr <- melt(rules, id.vars = c("rnr", "lift"), na.rm = TRUE)
# identify last column of each rule which becomes the recommendation
rn_of_last_col <- lr[, last(.I), by = rnr][, V1]
# reshape from long to wide without recommendation
wr <- dcast(lr[-rn_of_last_col], rnr ~ value)
# add column with recommendations (kind of cbind, no join)
wr[, recommended := lr[rn_of_last_col, value]]
wr
# rnr A B C D K M recommended
#1: 1 A B NA NA NA NA G
#2: 2 A B NA NA NA NA D
#3: 3 NA NA C D NA M K
#4: 4 A B C NA NA NA D
#5: 5 A NA C NA NA NA M
#6: 6 NA NA NA NA K M E
#7: 7 NA NA NA NA NA M T
#8: 8 NA NA NA NA K NA M
合并规则和购买
combi <- rbindlist(
# implied loop over rules to find matching purchases for each rule
lapply(seq_len(nrow(rules)), function(i) {
# get col names except last col which is the recommendation
cols <- lr[rnr == i, value[-.N]]
# join single rule with all partners on relevant cols for this rule
wp[wr[i, .SD, .SDcols = c(cols, "rnr", "recommended")], on = cols, nomatch = 0]
})
)
# check if recommendation was purchased already
combi[, already_purchased := Reduce(`|`, lapply(.SD, function(x) x == recommended)),
.SDcols = -c("rnp", "rnr", "recommended")]
# clean up already purchased
combi[is.na(already_purchased), already_purchased := FALSE
][, already_purchased := ifelse(already_purchased, "Yes", "No")]
combi
# rnp A B C D F K M rnr recommended already_purchased
#1: 1 A B C D NA NA NA 1 G No
#2: 1 A B C D NA NA NA 2 D Yes
#3: 1 A B C D NA NA NA 4 D Yes
#4: 1 A B C D NA NA NA 5 M No
#5: 3 NA NA NA NA NA K M 6 E No
#6: 3 NA NA NA NA NA K M 7 T No
#7: 3 NA NA NA NA NA K M 8 M Yes
在创建 combi
时,诀窍是仅连接包含在每个规则中的那些列。这就是为什么需要分别为每个规则进行连接的原因。
基本上,我们现在已经完成了。但是,它看起来不像所需的输出。
最终加入
tmp_rules <- rules[combi[, .(rnp, rnr, recommended, already_purchased)], on = "rnr"]
tmp_purch <- purchases[combi[, .(rnp, rnr)], on = "rnp"]
result <- tmp_purch[tmp_rules, on = c("rnp", "rnr")]
result[, (c("rnp", "rnr")) := NULL]
result
# Partner COL1 COL2 COL3 COL4 lift RULE1 RULE2 RULE3 RULE4 recommend already_purchased
#1: Alpha A B C D 9 B A G NA G No
#2: Alpha A B C D 10 B A D NA D Yes
#3: Alpha A B C D 12 A B C D D Yes
#4: Alpha A B C D 12 C A M NA M No
#5: Zeta M K NA NA 23 K M E NA E No
#6: Zeta M K NA NA 12 M T NA NA T No
#7: Zeta M K NA NA 24 K M NA NA M Yes
我有两个数据框:
Partner<-c("Alpha","Beta","Zeta")
COL1<-c("A","C","M")
COL2<-c("B","D","K")
COL3<-c("C","F",NA)
COL4<-c("D",NA,NA)
df1<-data.frame(Partner,COL1,COL2,COL3,COL4)
lift<-c(9,10,11,12,12,23,12,24)
RULE1<-c("B","B","D","A","C","K","M","K")
RULE2<-c("A","A","C","B","A","M","T","M")
RULE3<-c("G","D","M","C" ,"M", "E",NA,NA)
RULE4<-c(NA,NA,"K","D" ,NA, NA,NA,NA)
df2<-data.frame(lift,RULE1,RULE2,RULE3,RULE4)
df1
Partner COL1 COL2 COL3 COL4
Alpha A B C D
Beta C D F NA
Zeta M K NA NA
df2
lift RULE1 RULE2 RULE3 RULE4
9 B A G NA
10 B A D NA
11 D C M K
12 A B C D
12 C A M NA
23 K M E NA
12 M T NA NA
24 K M NA NA
这是购物篮分析。 df1 是 customer/partner 购买了列出的每个项目:A、B、C...等
df2 是与过去购买的商品相关的推荐。
每个 df2 行中的最后一个值代表建议。因此,最后一个非 NA 值的每一行中的前面值是 "baskets".
比如df2第一行写着:如果B和A一起买,推荐G。
我想知道 df1 的每个合作伙伴是否都购买了每行中不包括最终值的所有值,因为这是建议。然后将该建议添加到新数据框每一行的末尾。
例如: 对于合作伙伴:Alpha,从第一行开始推荐值 G 好吗?答案是肯定的,因为他们购买了 df2(A 和 B)中该行的所有值。
对于合作伙伴:Beta,推荐值G 并不好,因为并非df2 第一行中的所有值都在Beta 行中找到。
最终输出:
Partner COL1 COL2 COL3 COL4 lift RULE1 RULE2 RULE3 RULE4 Does Last Non-NA Value Exist in Row?
Alpha A B C D 9 B A G NA No
Alpha A B C D 10 B A D NA Yes
Alpha A B C D 12 A B C D Yes
Alpha A B C D 12 C A M NA No
Zeta M K NA NA 23 K M E NA No
Zeta M K NA NA 12 M T NA NA No
Zeta M K NA NA 24 K M NA NA Yes
为清楚起见写出结果:
df3
第 1 行输出 "No",因为在 Alpha Partner 中找不到 G,并且 G 之前的所有值都出现在 Alpha Partner (B,A)
行 2 输出 "Yes" 因为 D 在 Alpha Partner 中找到并且 D 之前的所有值都出现在 Alpha Partner (B,A)
第 3 行输出 "Yes" 因为 D 在 Alpha Partner 中找到并且 D 之前的所有值都出现在 Alpha Partner (A,B,C)
第 4 行输出 "No" 因为在 Alpha Partner 中找不到 M,并且 M 之前的所有值都出现在 Alpha Partner (C,A)
第 5 行输出 "No" 因为在 Zeta Partner 中找不到 E,并且 E 之前的所有值都出现在 Zeta Partner (K,M)
第 6 行输出 "No" 因为在 Zeta Partner 中找不到 T 并且 T 之前的所有值都出现在 Zeta Partner (M)
第 7 行输出 "Yes" 因为 M 在 Zeta Partner 中找到并且 M 之前的所有值都出现在 Zeta Partner (K)
我认为这必须是某种类型的连接或匹配,但不知道该怎么做。
如果有人能帮我解决这个问题,这将非常有帮助。
谢谢。
这是一次尝试:
df1<-cbind(df1_id=1:nrow(df1),df1)
df2 <- cbind(df2_id=1:nrow(df2),df2)
d11 <- df1 %>% gather(Col, Value,starts_with("C")) #Long
d11 <- d11 %>% na.omit() %>%group_by(df1_id) %>% slice(-n()) #remove last non NA
d22 <- df2 %>% gather(Rule, Value,starts_with("R"))
res <- inner_join(d11,d22)
rm(d22)
rm(d11)
final<-cbind(df1[res$df1_id,],df2[res$df2_id,])
final$Exist <- apply(final, 1, FUN = function(x)
c("No", "Yes")[(anyDuplicated(x[!is.na(x) & x != "" ])!=0) +1])
但这没有用,因为它没有考虑所有的值,只有其中一个匹配...不是全部。
这非常棘手,因为 n 位客户的购买必须与一组 m 规则进行比较。除此之外,还有两点增加了复杂性:
df2
中的最后一个非 NARULE
列在语义上与其他列不同。不幸的是,给定的数据结构并没有反映这一点。因此,df2
缺少明确的 recommended 列。最后还要判断小伙伴是否已经购买了推荐商品
出于性能原因,下面的方法依赖于 melt()
、dcast()
和 data.table
包的连接操作。但是,为了避免创建 n * m 行的笛卡尔叉积,使用了循环。
EDIT dcast()
已移出 lapply()
函数。
为 n:m 加入准备数据
library(data.table)
# convert to data.table and add row numbers
# here, a copy is used insteasd of setDT() in order to rename the data.tables
purchases <- as.data.table(df1)[, rnp := seq_len(.N)]
rules <- as.data.table(df2)[, rnr := seq_len(.N)]
# prepare purchases for joins
lp <- melt(purchases, id.vars = c("rnp", "Partner"), na.rm = TRUE)
wp <- dcast(lp, rnp ~ value, drop = FALSE)
wp
# rnp A B C D F K M
#1: 1 A B C D NA NA NA
#2: 2 NA NA C D F NA NA
#3: 3 NA NA NA NA NA K M
# prepare rules
lr <- melt(rules, id.vars = c("rnr", "lift"), na.rm = TRUE)
# identify last column of each rule which becomes the recommendation
rn_of_last_col <- lr[, last(.I), by = rnr][, V1]
# reshape from long to wide without recommendation
wr <- dcast(lr[-rn_of_last_col], rnr ~ value)
# add column with recommendations (kind of cbind, no join)
wr[, recommended := lr[rn_of_last_col, value]]
wr
# rnr A B C D K M recommended
#1: 1 A B NA NA NA NA G
#2: 2 A B NA NA NA NA D
#3: 3 NA NA C D NA M K
#4: 4 A B C NA NA NA D
#5: 5 A NA C NA NA NA M
#6: 6 NA NA NA NA K M E
#7: 7 NA NA NA NA NA M T
#8: 8 NA NA NA NA K NA M
合并规则和购买
combi <- rbindlist(
# implied loop over rules to find matching purchases for each rule
lapply(seq_len(nrow(rules)), function(i) {
# get col names except last col which is the recommendation
cols <- lr[rnr == i, value[-.N]]
# join single rule with all partners on relevant cols for this rule
wp[wr[i, .SD, .SDcols = c(cols, "rnr", "recommended")], on = cols, nomatch = 0]
})
)
# check if recommendation was purchased already
combi[, already_purchased := Reduce(`|`, lapply(.SD, function(x) x == recommended)),
.SDcols = -c("rnp", "rnr", "recommended")]
# clean up already purchased
combi[is.na(already_purchased), already_purchased := FALSE
][, already_purchased := ifelse(already_purchased, "Yes", "No")]
combi
# rnp A B C D F K M rnr recommended already_purchased
#1: 1 A B C D NA NA NA 1 G No
#2: 1 A B C D NA NA NA 2 D Yes
#3: 1 A B C D NA NA NA 4 D Yes
#4: 1 A B C D NA NA NA 5 M No
#5: 3 NA NA NA NA NA K M 6 E No
#6: 3 NA NA NA NA NA K M 7 T No
#7: 3 NA NA NA NA NA K M 8 M Yes
在创建 combi
时,诀窍是仅连接包含在每个规则中的那些列。这就是为什么需要分别为每个规则进行连接的原因。
基本上,我们现在已经完成了。但是,它看起来不像所需的输出。
最终加入
tmp_rules <- rules[combi[, .(rnp, rnr, recommended, already_purchased)], on = "rnr"]
tmp_purch <- purchases[combi[, .(rnp, rnr)], on = "rnp"]
result <- tmp_purch[tmp_rules, on = c("rnp", "rnr")]
result[, (c("rnp", "rnr")) := NULL]
result
# Partner COL1 COL2 COL3 COL4 lift RULE1 RULE2 RULE3 RULE4 recommend already_purchased
#1: Alpha A B C D 9 B A G NA G No
#2: Alpha A B C D 10 B A D NA D Yes
#3: Alpha A B C D 12 A B C D D Yes
#4: Alpha A B C D 12 C A M NA M No
#5: Zeta M K NA NA 23 K M E NA E No
#6: Zeta M K NA NA 12 M T NA NA T No
#7: Zeta M K NA NA 24 K M NA NA M Yes