R 语言中如何进行跨行观测值比较?

有个小伙伴问到了这样一个问题:

对于数据集中的观测值 A,如果数据集中能找到一个和 A 的 id 相同,class 相同的观测值 B,且 A 的 year 减 B 的 year 的差值大于 0 小于 5,则将 A 标记为 1;如果找不到这样的观测值 B,则将 A 标记为 0。

她还提供了一个示例数据:

library(tidyverse)
tibble(
id = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2),
year = c(2013, 2013, 2013, 2016, 2016, 2016, 2016, 2017, 2017, 2017,
2017, 2017, 2017, 2017, 2017, 2018, 2005, 2006, 2006, 2006, 2006),
num = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21),
class = c("G06Q", "G06Q", "G06Q", "G06Q", "G06Q",
"G06Q", "G06Q", "G06Q", "G06Q", "G06Q", "G06Q", "G06Q", "G06Q",
"G06Q", "G06Q", "G06Q", "E04H1/00", "B65F", "E03C1/12", "B65F",
"B65F")
) -> df
df

#> # A tibble: 21 × 4
#> id year num class
#> <dbl> <dbl> <dbl> <chr>
#> 1 1 2013 1 G06Q
#> 2 1 2013 2 G06Q
#> 3 1 2013 3 G06Q
#> 4 1 2016 4 G06Q
#> 5 1 2016 5 G06Q
#> 6 1 2016 6 G06Q
#> 7 1 2016 7 G06Q
#> 8 1 2017 8 G06Q
#> 9 1 2017 9 G06Q
#> 10 1 2017 10 G06Q
#> # ℹ 11 more rows

我们再生成一个 ID 变量以更好的区分各个观测值:

df %>%
mutate(ID = row.names(.)) -> df

为了进行跨行比较,我们需要生成一个交叉数据集:

df %>%
rename_all(~paste0(.x, "2")) %>%
crossing(df)
#> # A tibble: 441 × 10
#> id2 year2 num2 class2 ID2 id year num class ID
#> <dbl> <dbl> <dbl> <chr> <chr> <dbl> <dbl> <dbl> <chr> <chr>
#> 1 1 2013 1 G06Q 1 1 2013 1 G06Q 1
#> 2 1 2013 1 G06Q 1 1 2013 2 G06Q 2
#> 3 1 2013 1 G06Q 1 1 2013 3 G06Q 3
#> 4 1 2013 1 G06Q 1 1 2016 4 G06Q 4
#> 5 1 2013 1 G06Q 1 1 2016 5 G06Q 5
#> 6 1 2013 1 G06Q 1 1 2016 6 G06Q 6
#> 7 1 2013 1 G06Q 1 1 2016 7 G06Q 7
#> 8 1 2013 1 G06Q 1 1 2017 8 G06Q 8
#> 9 1 2013 1 G06Q 1 1 2017 9 G06Q 9
#> 10 1 2013 1 G06Q 1 1 2017 10 G06Q 10
#> # ℹ 431 more rows

由于筛选条件里面有对年份差值的比较,所以我们再生成一个 yeardiff 变量:

df %>%
rename_all(~paste0(.x, "2")) %>%
crossing(df) %>%
mutate(yeardiff = year - year2)
#> # A tibble: 441 × 11
#> id2 year2 num2 class2 ID2 id year num class ID yeardiff
#> <dbl> <dbl> <dbl> <chr> <chr> <dbl> <dbl> <dbl> <chr> <chr> <dbl>
#> 1 1 2013 1 G06Q 1 1 2013 1 G06Q 1 0
#> 2 1 2013 1 G06Q 1 1 2013 2 G06Q 2 0
#> 3 1 2013 1 G06Q 1 1 2013 3 G06Q 3 0
#> 4 1 2013 1 G06Q 1 1 2016 4 G06Q 4 3
#> 5 1 2013 1 G06Q 1 1 2016 5 G06Q 5 3
#> 6 1 2013 1 G06Q 1 1 2016 6 G06Q 6 3
#> 7 1 2013 1 G06Q 1 1 2016 7 G06Q 7 3
#> 8 1 2013 1 G06Q 1 1 2017 8 G06Q 8 4
#> 9 1 2013 1 G06Q 1 1 2017 9 G06Q 9 4
#> 10 1 2013 1 G06Q 1 1 2017 10 G06Q 10 4
#> # ℹ 431 more rows

这样我们就可以筛选出符合要求的观测值了:

df %>%
rename_all(~paste0(.x, "2")) %>%
crossing(df) %>%
mutate(yeardiff = year - year2) %>%
filter(id == id2 & class == class2 & (yeardiff > 0 & yeardiff < 5) & ID != ID2)
#> # A tibble: 80 × 11
#> id2 year2 num2 class2 ID2 id year num class ID yeardiff
#> <dbl> <dbl> <dbl> <chr> <chr> <dbl> <dbl> <dbl> <chr> <chr> <dbl>
#> 1 1 2013 1 G06Q 1 1 2016 4 G06Q 4 3
#> 2 1 2013 1 G06Q 1 1 2016 5 G06Q 5 3
#> 3 1 2013 1 G06Q 1 1 2016 6 G06Q 6 3
#> 4 1 2013 1 G06Q 1 1 2016 7 G06Q 7 3
#> 5 1 2013 1 G06Q 1 1 2017 8 G06Q 8 4
#> 6 1 2013 1 G06Q 1 1 2017 9 G06Q 9 4
#> 7 1 2013 1 G06Q 1 1 2017 10 G06Q 10 4
#> 8 1 2013 1 G06Q 1 1 2017 11 G06Q 11 4
#> 9 1 2013 1 G06Q 1 1 2017 12 G06Q 12 4
#> 10 1 2013 1 G06Q 1 1 2017 13 G06Q 13 4
#> # ℹ 70 more rows

然后我们再把结果和最初的 df 连接起来就可以了:

df %>%
rename_all(~paste0(.x, "2")) %>%
crossing(df) %>%
mutate(yeardiff = year - year2) %>%
filter(id == id2 & class == class2 & (yeardiff > 0 & yeardiff < 5) & ID != ID2) %>%
distinct(ID, id, year, num, class) %>%
mutate(kind = 1) %>%
right_join(df) %>%
mutate(kind = if_else(is.na(kind), 0, kind)) %>%
mutate(ID = as.numeric(ID)) %>%
arrange(ID) %>%
print(n = 21)
#> # A tibble: 21 × 6
#> ID id year num class kind
#> <dbl> <dbl> <dbl> <dbl> <chr> <dbl>
#> 1 1 1 2013 1 G06Q 0
#> 2 2 1 2013 2 G06Q 0
#> 3 3 1 2013 3 G06Q 0
#> 4 4 1 2016 4 G06Q 1
#> 5 5 1 2016 5 G06Q 1
#> 6 6 1 2016 6 G06Q 1
#> 7 7 1 2016 7 G06Q 1
#> 8 8 1 2017 8 G06Q 1
#> 9 9 1 2017 9 G06Q 1
#> 10 10 1 2017 10 G06Q 1
#> 11 11 1 2017 11 G06Q 1
#> 12 12 1 2017 12 G06Q 1
#> 13 13 1 2017 13 G06Q 1
#> 14 14 1 2017 14 G06Q 1
#> 15 15 1 2017 15 G06Q 1
#> 16 16 1 2018 16 G06Q 1
#> 17 17 2 2005 17 E04H1/00 0
#> 18 18 2 2006 18 B65F 0
#> 19 19 2 2006 19 E03C1/12 0
#> 20 20 2 2006 20 B65F 0
#> 21 21 2 2006 21 B65F 0

这样我们就解决了这个问题。

另外我还把这个问题问了 chatGPT,它给出了这样的答案:

可以使用 dplyr 和 tidyr 包中的函数实现:

# 非正确代码
library(dplyr)
library(tidyr)
df %>%
group_by(id, class) %>%
arrange(id, year) %>%
mutate(year_diff = year - lag(year)) %>%
fill(year_diff, .direction = "up") %>%
mutate(flag = ifelse(year_diff > 0 & year_diff < 5, 1, 0)) %>%
select(-year_diff)

看起来它对这个问题理解有误,给出了一个错误的答案。不过这也提示我们可以分组处理:

df %>%
dplyr::group_split(id, class) %>%
map(.f = function(x){
x %>%
rename_all(~paste0(.x, 2)) %>%
crossing(x) %>%
mutate(yeardiff = year - year2) %>%
filter(yeardiff > 0 & yeardiff < 5 & ID != ID2)
}) %>%
bind_rows() %>%
distinct(ID, id, year, num, class) %>%
mutate(kind = 1) %>%
right_join(df) %>%
mutate(kind = if_else(is.na(kind), 0, kind)) %>%
mutate(ID = as.numeric(ID)) %>%
arrange(ID) %>%
print(n = 21)

这样也可以解决这个问题。

点击这里跳转到 RStata 短书平台获取附件:R 语言中如何进行跨行观测值比较?

评论