使用 mutate 标记其他行中同时出现多个匹配值
支付小周
结论
先按 date 和 merchant 分组,再检查每个分组内是否有金额互为相反数的交易:
library(dplyr)
transactions %>%
group_by(date, merchant) %>%
mutate(refund = (-amount) %in% amount) %>%
ungroup()
分组后,日期、商户和相反金额必须同时匹配,因此不会把其他日期或商户的交易误判为退款。
原写法的问题
原来的几个判断相互独立:
date %in% date
merchant %in% merchant
(-amount) %in% amount
前两个条件始终为 TRUE,因为每个日期和商户都存在于自身所在的列中。即使换成与其他数据比较,多个独立的 %in% 也只能确认各个值分别存在,无法保证它们来自同一行。
需要判断的是:
在同一天、同一商户的交易中,当前金额的相反数是否存在。
group_by(date, merchant) 将检查范围限定在了对应的日期和商户内。
完整示例
library(dplyr)
library(tibble)
transactions <- tibble(
date = as.Date(c(
"2020-01-01",
"2020-01-02",
"2020-01-02",
"2020-01-03",
"2020-01-04"
)),
merchant = c("A", "B", "B", "C", "A"),
amount = c(10, 5, -5, 9, 10)
)
result <- transactions %>%
group_by(date, merchant) %>%
mutate(refund = (-amount) %in% amount) %>%
ungroup()
result
输出结果:
# A tibble: 5 × 4
date merchant amount refund
<date> <chr> <dbl> <lgl>
1 2020-01-01 A 10 FALSE
2 2020-01-02 B 5 TRUE
3 2020-01-02 B -5 TRUE
4 2020-01-03 C 9 FALSE
5 2020-01-04 A 10 FALSE
商户 B 在 2020-01-02 的分组中有 5 和 -5 两笔交易,金额互为相反数,所以两行都会标记为 TRUE。
允许退款晚于付款
如果正数代表付款,负数代表退款,并且退款可以发生在付款当天或之后,可以先为每行添加唯一标识,再执行自连接:
transactions_with_id <- transactions %>%
mutate(transaction_id = row_number())
matched_ids <- transactions_with_id %>%
filter(amount > 0) %>%
inner_join(
transactions_with_id %>%
filter(amount < 0),
by = join_by(
merchant,
amount == -amount,
date <= date
),
suffix = c("_payment", "_refund")
) %>%
transmute(
ids = Map(
c,
transaction_id_payment,
transaction_id_refund
)
) %>%
tidyr::unnest_longer(ids) %>%
distinct(ids)
result <- transactions_with_id %>%
mutate(refund = transaction_id %in% matched_ids$ids) %>%
select(-transaction_id)
这段代码基于以下条件:
- 正数表示付款;
- 负数表示退回款项;
- 付款和退款的绝对金额完全相等;
- 退款日期不早于付款日期;
merchant相同。
join_by() 的非等值连接语法需要相应版本的 dplyr。如果只需处理当天发生的退款,前面的分组写法更简单。
注意事项
假设同一分组中有两笔 5 和一笔 -5,简单使用 %in% 会将三笔交易全部标记为 TRUE。它只判断相反金额是否存在,不会进行一对一配对。
如果每笔退款只能对应一笔付款,就需要根据交易时间、交易流水号或出现顺序制定明确的配对规则。
金额为 0 时,-amount 仍为 0,因此单独一笔零金额交易也会与自身匹配。可以增加条件将其排除:
transactions %>%
group_by(date, merchant) %>%
mutate(refund = amount != 0 & (-amount) %in% amount) %>%
ungroup()
备注:内容仅供参考。
