按分组筛选含连续1的行:生成两类目标数据集
问题描述
我有一个dataframe,需要筛选出仅包含连续值“1”的子集。具体数据结构如下:
library(tidyverse) library(zoo) df <- data.frame(matrix(ncol = 3, nrow = 17)) colnames(df) <- c("row_id","id", "k_yes") df$row_id <- c(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17) df$id <- c("1_1","1_1","1_1","1_1","1_1","1_1","1_2","1_2","1_2","1_2","1_2","1_2","1_3","1_3","1_3","1_3","1_3") df$k_yes <- c(1,1,1,0,1,1,0,0,0,1,1,0,1,0,1,0,1) df # 输出: # row_id id k_yes # 1 1 1_1 1 # 2 2 1_1 1 # 3 3 1_1 1 # 4 4 1_1 0 # 5 5 1_1 1 # 6 6 1_1 1 # 7 7 1_2 0 # 8 8 1_2 0 # 9 9 1_2 0 # 10 10 1_2 1 # 11 11 1_2 1 # 12 12 1_2 0 # 13 13 1_3 1 # 14 14 1_3 0 # 15 15 1_3 1 # 16 16 1_3 0 # 17 17 1_3 1
需求说明
需要生成两个数据集:
1) 仅保留至少两个连续“1”的序列
按id分组,仅保留至少两个连续的“1”;若“1”之间有“0”,则需舍弃后续的“1”序列。同时新增new_group列标记每个唯一序列,目标输出如下:
row_id id k_yes new_group 1 1 1_1 1 1_1_A 2 2 1_1 1 1_1_A 3 3 1_1 1 1_1_A 4 5 1_1 1 1_1_B 5 6 1_1 1 1_1_B 6 10 1_2 1 1_2_A 7 11 1_2 1 1_2_A
2) 允许“1”之间存在单个“0”(且“0”后必须有“1”)
按id分组,允许“1”之间存在单个“0”,但“0”后必须有“1”,不接受如0-1-1、1-1-0等序列,同样新增new_group列,目标输出如下:
row_id id k_yes new_group 1 1 1_1 1 1_1_A 2 2 1_1 1 1_1_A 3 3 1_1 1 1_1_A 4 4 1_1 0 1_1_A 5 5 1_1 1 1_1_A 6 6 1_1 1 1_1_A 7 10 1_2 1 1_2_A 8 11 1_2 1 1_2_A 9 13 1_3 1 1_3_A 10 14 1_3 0 1_3_A 11 15 1_3 1 1_3_A 12 16 1_3 0 1_3_A 13 17 1_3 1 1_3_A
尝试代码及错误
尝试参考方案使用以下代码:
df |> group_by(id) |> mutate(b = c(first(k_yes) , zoo::rollsum(k_yes, 1))) |> summarise(groups_to_keep = id[which(b >= 2)]) -> gk
出现错误:
Error in `mutate()`: ℹ In argument: `b = c(first(k_yes), zoo::rollsum(k_yes, 1))`. ℹ In group 1: `id = "1_1"`. Caused by error: ! `b` must be size 5 or 1, not 6.
推测是rollsum()函数使用问题,寻求解决帮助。
更新:新增数据集版本,会在第一个数据集中生成两个new_group值。
解决方案
针对需求1的解决代码
核心思路是按id分组识别连续的1序列,筛选长度≥2的序列后生成标记列:
df1 <- df %>% group_by(id) %>% # 标记连续的1序列:k_yes切换时新建分组 mutate(seq_group = cumsum(c(TRUE, diff(k_yes) != 0))) %>% filter(k_yes == 1) %>% # 计算每个序列的长度 add_count(seq_group, name = "seq_length") %>% # 保留长度≥2的序列 filter(seq_length >= 2) %>% # 生成new_group标记 mutate(new_group = paste0(id, "_", LETTERS[match(seq_group, unique(seq_group))])) %>% select(-seq_group, -seq_length) %>% ungroup() print(df1)
针对需求2的解决代码
核心思路是先标记符合规则的行,再筛选掉开头/结尾为0的无效序列:
df2 <- df %>% group_by(id) %>% mutate( # 标记有效行:1直接有效,0需前后都是1 valid = case_when( k_yes == 1 ~ TRUE, k_yes == 0 ~ lag(k_yes) == 1 & lead(k_yes) == 1, TRUE ~ FALSE ), # 标记连续的有效序列组 seq_group = cumsum(c(TRUE, diff(valid) != 0)) ) %>% filter(valid) %>% group_by(id, seq_group) %>% # 排除开头或结尾为0的序列 filter(first(k_yes) != 0 & last(k_yes) != 0) %>% # 生成new_group标记 mutate(new_group = paste0(id, "_", LETTERS[match(seq_group, unique(seq_group))])) %>% select(-valid, -seq_group) %>% ungroup() print(df2)
原错误解释
原代码中zoo::rollsum(k_yes, 1)无实际意义(窗口大小1的滚动和等于原数据),且c(first(k_yes), rollsum(...))会导致列长度不匹配:以id=1_1分组为例,分组共6行,rollsum返回6个值,加上first(k_yes)后变成7个,与分组行数冲突从而报错。正确做法是用cumsum(diff(k_yes)!=0)标记连续序列,而非错误使用rollsum。
内容的提问来源于stack exchange,提问作者mto23
相关产品推荐
相关产品推荐

