计算两个值在 R 中的一个组内同时出现的次数

Count number of times two values co-occur within a group in R

我搜索了这个问题的答案并找到了类似的答案 (Count number of rows within each group, , ),但其中 none 解决了我的特定问题。

我有一个包含变量 yearIDcode 的数据框。每个人都有一个 ID 并且在(可能)多个 years.

的过程中可以有多个 code
df = data.frame(ID   = c(1,1,1,1, 2,2,2, 3, 4,4,4,4,4,4,4,4, 5,5,5),
                year = c(2018, 2018, 2020, 2020,
                         2020, 2020, 2020,
                         2011,
                         2019, 2019, 2019, 2019, 2020, 2020, 2020, 2020,
                         2018, 2019, 2020),
                code = c("A", "B", "C", "D",
                         "A", "B", "Q",
                         "G",
                         "A", "B", "Q", "G", "C", "D", "T", "S",
                         "S", "Z", "F")

)

df
   ID year code
1   1 2018    A
2   1 2018    B
3   1 2020    C
4   1 2020    D
5   2 2020    A
6   2 2020    B
7   2 2020    Q
8   3 2011    G
9   4 2019    A
10  4 2019    B
11  4 2019    Q
12  4 2019    G
13  4 2020    C
14  4 2020    D
15  4 2020    T
16  4 2020    S
17  5 2018    S
18  5 2019    Z
19  5 2020    F

我想要的是另一个数据帧,给出 code 的两个不同值在 IDyear 的分组中同时出现的次数(在这个例子中,A和 B 共现 3 次,A 和 C 共现 0 次),然后我将用于网络分析。

到目前为止我有这样的语法:

1:制作宽版数据

library(tidyverse)
wide = df %>% 
        group_by(year, ID) %>% 
        mutate(row = row_number()) %>% 
        ungroup() %>% 
        pivot_wider(
            id_cols = c(ID, year),
            names_from = row, 
            names_prefix = "code_", 
            values_from = code
        )

2:制作节点列表

nodes = distinct(df, code) %>% rowid_to_column("id")

3:制作边列表

#edge list needs to be three vars: source, dest, and weight
# source and dest are simply code names that (potentially) co-occur in the same year for an ID
# weight is the number of times the codes co-occurred in the same year for each ID.

#all combinations of two codes
edges = combn(x = nodes$code, m = 2 ) %>% 
    t() %>% 
    as.data.frame()

colnames(edges) = c("source", "dest")
edges$weight = NA_integer_


#oh, no! a for() loop! a coder's last ditch effort to make something work
for(i in 1:nrow(edges)){
    
    source = edges$source[i]
    dest = edges$dest[i]
    

    #get the cases with the first code of interest
    temp = df %>% 
        filter( code == source ) %>% 
        select(ID, year)
    
    #get the other codes that occurred for that ID in that year
    temp = left_join(temp, 
                     wide, 
                     by = c("ID", "year"))
    
    
    #convert to a logical showing if the other codes are the one I want
    temp = temp %>% mutate_at(vars(starts_with("code_")),
                            function(x){ x == dest }
    ) 
    
    #sum the number of times `source` and `dest` co-occurred
    temp$dest = temp %>% select(starts_with("code_")) %>% rowSums(., na.rm=TRUE)
    edges$weight[i] = sum(temp$dest, na.rm = TRUE)
    
}

编辑添加结果:

结果:

edges
   source dest weight
1       A    B      3
2       A    C      0
3       A    D      0
4       A    Q      2
5       A    G      1
6       A    T      0
7       A    S      0
8       A    Z      0
9       A    F      0
10      B    C      0
11      B    D      0
12      B    Q      2
13      B    G      1
14      B    T      0
15      B    S      0
16      B    Z      0
17      B    F      0
18      C    D      2
19      C    Q      0
20      C    G      0
21      C    T      1
22      C    S      1
23      C    Z      0
24      C    F      0
25      D    Q      0
26      D    G      0
27      D    T      1
28      D    S      1
29      D    Z      0
30      D    F      0
31      Q    G      1
32      Q    T      0
33      Q    S      0
34      Q    Z      0
35      Q    F      0
36      G    T      0
37      G    S      0
38      G    Z      0
39      G    F      0
40      T    S      1
41      T    Z      0
42      T    F      0
43      S    Z      0
44      S    F      0
45      Z    F      0

这给了我想要的(一个数据帧显示 A 和 B 共同出现 3 次,A 和 C 共同出现 0 次,A 和 D 共同出现 0 次,A 和 G 共同出现 1 次, A 和 Q 同时出现 2 次,等等...)。所以这是有效的,但即使是这个小例子也需要一两秒钟。我的真实数据集是 ~3,000,000 个观察值。我让它 运行 了一会儿,但停止它只是发现它完成了 ~1%。

有better/faster方法吗?

这应该有效。由于 sort.

,每对你只能获得一个条目
library(data.table)
setDT(df)
all_pairs <- function(x) {
  if (length(x) > 1) {
    sapply(combn(sort(x), 2, simplify = FALSE), paste, collapse = '')
  } else {
    c()
  }
}
df[,.(pairs = all_pairs(code)), .(ID, year)][,.N, .(pairs)]

#>     pairs N
#>  1:    AB 3
#>  2:    CD 2
#>  3:    AQ 2
#>  4:    BQ 2
#>  5:    AG 1
#>  6:    BG 1
#>  7:    GQ 1
#>  8:    CS 1
#>  9:    CT 1
#> 10:    DS 1
#> 11:    DT 1
#> 12:    ST 1

这里有一个替代方案,它只执行连接,因此对于大数据来说可能非常快。

library(data.table)
setDT(df)
df[df, on = c('ID','year'), allow.cartesian = TRUE][
  code<i.code, .N, .(pair = paste0(code, i.code))]

#>     pair N
#>  1:   AB 3
#>  2:   CD 2
#>  3:   AQ 2
#>  4:   BQ 2
#>  5:   GQ 1
#>  6:   AG 1
#>  7:   BG 1
#>  8:   CT 1
#>  9:   DT 1
#> 10:   ST 1
#> 11:   CS 1
#> 12:   DS 1