如何在R语言中实现当move_left==1时将多列非NA值左移?
问题描述
需要将R语言中的tibble数据框df转换为目标数据框df_want,转换规则如下:
- 当
move_left列的值为1时,将该行v1至v5列中的非NA值向左移动,填补左侧的NA位置,右侧补NA; - 当
move_left列的值为0时,保持原行数据不变。
原始数据与目标数据定义如下:
library(tibble) df <- tribble( ~id, ~v1, ~v2, ~v3, ~v4, ~v5, ~move_left, 1L, "a", NA, "b", NA, "c", 0L, 2L, "a", NA, "b", NA, "c", 1L, 3L, NA, "a", NA, "b", NA, 0L, 4L, NA, NA, "a", NA, NA, 1L, 5L, NA, NA, "a", NA, "b", 1L, 6L, "a", "b", NA, "c", "d", 1L ) df_want <- tribble( ~id, ~v1, ~v2, ~v3, ~v4, ~v5, ~move_left, 1L, "a", NA, "b", NA, "c", 0L, 2L, "a", "b", "c", NA, NA, 1L, 3L, NA, "a", NA, "b", NA, 0L, 4L, "a", NA, NA, NA, NA, 1L, 5L, "a", "b", NA, NA, NA, 1L, 6L, "a", "b", "c", "d", NA, 1L )
解决方案
可以结合dplyr和purrr包实现需求,核心逻辑是逐行处理:如果当前行move_left为1,就提取该行v1-v5的非NA值靠左排列,剩余位置补NA;move_left为0则原样保留。
代码实现
library(dplyr) library(purrr) library(stringr) df_result <- df %>% rowwise() %>% mutate( across(v1:v5, ~{ if (move_left == 1) { # 提取当前行v1-v5的非NA值 non_na_vals <- c(v1, v2, v3, v4, v5)[!is.na(c(v1, v2, v3, v4, v5))] # 生成长度为5的向量:非NA值在前,后面补NA filled_vals <- c(non_na_vals, rep(NA, 5 - length(non_na_vals))) # 根据当前列的序号取对应位置的值 filled_vals[str_extract(cur_column(), "\\d") %>% as.integer()] } else { .x } }) ) %>% ungroup() # 验证结果是否匹配目标数据框 all.equal(df_result, df_want) #> [1] TRUE
代码解释
rowwise():让后续操作按行执行,保证每一行的处理独立;across(v1:v5, ...):对v1到v5列逐一处理;- 分支逻辑:
- 当
move_left=1时:先筛选出当前行v1-v5的非NA值,再用NA补全为5个元素的向量,最后根据当前列的编号(比如v1对应第1位)提取对应值; - 当
move_left=0时:直接保留原列的值;
- 当
ungroup():取消行分组,恢复数据框的常规结构。
另一种简洁实现(用tidyr)
如果习惯用宽长格式转换的思路,也可以用tidyr的pivot_longer和pivot_wider实现:
library(tidyr) library(dplyr) df_result2 <- df %>% # 将v1-v5转成长格式 pivot_longer(cols = v1:v5, names_to = "var", values_to = "val", values_drop_na = FALSE) %>% group_by(id) %>% mutate( # 当move_left=1时,重新分配列名:非NA值按顺序对应v1-v5,剩余位置留空 new_var = if (move_left == 1) { non_na_pos <- which(!is.na(val)) new_pos <- c(non_na_pos, setdiff(1:5, non_na_pos)) paste0("v", rank(new_pos, ties.method = "first")) } else { var } ) %>% # 转回宽格式 pivot_wider(id_cols = c(id, move_left), names_from = new_var, values_from = val) %>% # 保证列顺序和原数据一致 select(id, v1:v5, move_left) all.equal(df_result2, df_want) #> [1] TRUE
内容的提问来源于stack exchange,提问作者bgxq
相关产品推荐
相关产品推荐

