如何用tidyverse为数据框同类别连续行块生成分组编号
问题描述
现有如下数据集:
set.seed(10) df <- data.frame(row=c(1:15), cat = c(rep("a", 3), rep("b", 3), rep("c", 3), rep("b", 3), rep("a", 3)), val = runif(15)) df
输出结果:
row cat val 1 1 a 0.53559704 2 2 a 0.09308813 3 3 a 0.16980304 4 4 b 0.89983245 5 5 b 0.42263761 6 6 b 0.74774647 7 7 c 0.82265258 8 8 c 0.95465365 9 9 c 0.68544451 10 10 b 0.50050323 11 11 b 0.27548386 12 12 b 0.22890394 13 13 a 0.01443391 14 14 a 0.72896456 15 15 a 0.24988047
可以看到cat列存在连续的同类别行块,例如类别a出现在1-3行和13-15行。需要新增cat2列,按每个类别出现的行块顺序编号,格式如a1、a2,b1、b2,预期输出:
row cat val cat2 1 1 a 0.53559704 a1 2 2 a 0.09308813 a1 3 3 a 0.16980304 a1 4 4 b 0.89983245 b1 5 5 b 0.42263761 b1 6 6 b 0.74774647 b1 7 7 c 0.82265258 c1 8 8 c 0.95465365 c1 9 9 c 0.68544451 c1 10 10 b 0.50050323 b2 11 11 b 0.27548386 b2 12 12 b 0.22890394 b2 13 13 a 0.01443391 a2 14 14 a 0.72896456 a2 15 15 a 0.24988047 a2
仅使用dplyr::group_by(cat)无法实现该需求。尝试了以下方法:
df %>% mutate(run = with(rle(cat), rep(seq_along(lengths), lengths))) %>% group_by(cat, run) %>% mutate(cat2 = paste0(cat, run)) %>% ungroup() %>% select(-run)
但得到的是全局递增的编号,不符合预期:
row cat val cat2 <int> <chr> <dbl> <chr> 1 1 a 0.536 a1 2 2 a 0.0931 a1 3 3 a 0.170 a1 4 4 b 0.900 b2 5 5 b 0.423 b2 6 6 b 0.748 b2 7 7 c 0.823 c3 8 8 c 0.955 c3 9 9 c 0.685 c3 10 10 b 0.501 b4 11 11 b 0.275 b4 12 12 b 0.229 b4 13 13 a 0.0144 a5 14 14 a 0.729 a5 15 15 a 0.250 a5
请问如何使用tidyverse实现需求的分组编号?
解决方案
核心思路是先识别每个连续的行块,再针对每个cat类别,对其出现的行块进行独立编号。具体实现代码如下:
library(tidyverse) set.seed(10) df <- data.frame(row=c(1:15), cat = c(rep("a", 3), rep("b", 3), rep("c", 3), rep("b", 3), rep("a", 3)), val = runif(15)) df %>% # 生成每个连续行块的全局唯一标识 mutate(block_id = with(rle(cat), rep(seq_along(lengths), lengths))) %>% # 按cat分组,对每个类别下的行块进行顺序编号 group_by(cat) %>% mutate(block_num = dense_rank(block_id)) %>% # 拼接得到目标格式的cat2列 mutate(cat2 = paste0(cat, block_num)) %>% ungroup() %>% # 移除中间辅助列 select(-block_id, -block_num)
运行后得到的结果完全符合预期:
row cat val cat2 1 1 a 0.53559704 a1 2 2 a 0.09308813 a1 3 3 a 0.16980304 a1 4 4 b 0.89983245 b1 5 5 b 0.42263761 b1 6 6 b 0.74774647 b1 7 7 c 0.82265258 c1 8 8 c 0.95465365 c1 9 9 c 0.68544451 c1 10 10 b 0.50050323 b2 11 11 b 0.27548386 b2 12 12 b 0.22890394 b2 13 13 a 0.01443391 a2 14 14 a 0.72896456 a2 15 15 a 0.24988047 a2
代码解释
block_id:通过rle()函数识别连续的同类别行块,为每个行块分配全局唯一的编号;block_num:按cat分组后,用dense_rank()对block_id重新排序,确保每个类别下的行块从1开始递增编号;cat2:将cat类别与对应的行块编号拼接,得到需求的格式。
内容的提问来源于stack exchange,提问作者Mysliik
相关产品推荐
相关产品推荐

