我试图找到一种方法来对行进行分组,并在不使用循环的情况下为每个组分配一个索引。难点在于分组变量num没有唯一标识符; num 是一个数字向量(定义为字符)。我想对至少与向量中的任何数字匹配的所有行进行分组。向量具有不同的长度,最多包含 20 个数字。 我举个例子:
我有一个这样的数据框:
df <- data.frame(id = c(1:5), num = c('111;222', '333;111;444', '000;88888;1', '9999;111', '1'))
我用分隔符 ; 分割 num 使得每一行都变成一个向量:
library(dplyr)
df <- df %>%
mutate(num = str_split(num, ';'))
我想索引向量中至少有一个数字与相同索引匹配的所有行。结果应如下所示:
id num group_index
1 c('111','222') 1
2 c('333','111','444') 1
3 c('000','88888','1') 2
4 c('9999','444') 1
5 '1' 2
该示例说明了另一个困难:第 1 组由“111”和“444”标识,即使第 1 行是第 1 组的一部分并且 num 中不包含“444”。
如果 num 只是一个字符串,我会执行以下操作
df <- group_by(num) %>%
mutate(group_index = group_indices(.,num))
现在,我想我或许应该从识别群体开始。第一种不起作用的方法是:
df <- df %>%
group_by_if(num, any(num, str_c(num, collapse = '|')) == T)
我知道我可以从写一个循环开始。但是,R 对循环不是很有效,所以我更喜欢没有循环的解决方案——如果有的话?任何提示都会有所帮助!
答案 0 :(得分:1)
好的,这个答案可能会被缩短(可能很多),但我认为使用 igraph
可以保持每一个很好的和可见的组数的目视检查。< /p>
library( data.table )
library( igraph )
#make df a data.table
setDT(df)
#split num-column to v1, v2, ... ,vn
df[, paste0("v", 1:length( tstrsplit(df$num, ";"))) := tstrsplit( num, ";")]
# id num v1 v2 v3
# 1: 1 111;222 111 222 <NA>
# 2: 2 333;111;444 333 111 444
# 3: 3 000;88888;1 000 88888 1
# 4: 4 9999;111 9999 111 <NA>
# 5: 5 1 1 <NA> <NA>
#now melt to long format
df.melt <- melt(df, id.vars = "id", measure.vars = patterns("^v[0-9]"), value.name = "from" )
#create links
df.melt[, to := shift( from, type = "lead"), by = .(id)][]
#drop inomplete rows
df.melt <- df.melt[ complete.cases(df.melt), ]
# id variable from to
# 1: 1 v1 111 222
# 2: 2 v1 333 111
# 3: 3 v1 000 88888
# 4: 4 v1 9999 111
# 5: 2 v2 111 444
# 6: 3 v2 88888 1
g = graph_from_data_frame( df.melt[ , .(from, to)])
# plot(g)
看起来我们有两个独立的小组要合作。让我们找出哪个节点(编号)属于哪个组,并在原始 df 上使用此信息
dt.lookup <- as.data.table( components(g)$membership, keep.rownames = TRUE )
# V1 V2
# 1: 111 1
# 2: 333 1
# 3: 000 2
# 4: 9999 1
# 5: 88888 2
# 6: 222 1
# 7: 444 1
# 8: 1 2
#go back to the molten data of the original df
df.melt <- melt(df, id.vars = "id", measure.vars = patterns("^v[0-9]"))
df.melt <- df.melt[ complete.cases(df.melt), ]
#perform update join to get the groupnumber
df.melt[ dt.lookup, group := i.V2, on = .(value = V1) ]
# id variable value group
# 1: 1 v1 111 1
# 2: 2 v1 333 1
# 3: 3 v1 000 2
# 4: 4 v1 9999 1
# 5: 5 v1 1 2
# 6: 1 v2 222 1
# 7: 2 v2 111 1
# 8: 3 v2 88888 2
# 9: 4 v2 111 1
# 10: 2 v3 444 1
# 11: 3 v3 1 2
#summarise to go back to oroiginal df form
df.melt[, .(num = paste0( value, collapse = ";"),
group = paste0( unique(group), collapse = ",")),
by = .(id) ][]
最终输出
# id num group
# 1: 1 111;222 1
# 2: 2 333;111;444 1
# 3: 3 000;88888;1 2
# 4: 4 9999;111 1
# 5: 5 1 2