如何用列表/数据框子集化多列并高效重设因子水平?
问题描述
我有一个元素长度不均的列表:
my.list = list(col1 = c("CC", "CT", "TT"), col2 = c("GG", "GT"), col3 = c("CC", "CT"), col4 = c("CC", "CG", "GG"), col5 = c("AC", "CC"), col6 = "GG")
该列表可转换为数据框:
mylist.df = plyr::ldply(my.list, rbind) names(mylist.df) <- c("cols","g1", "g2", "g3")
需求1:数据框子集化
需要用my.list或mylist.df对以下数据框进行子集化,保留每行中各列值均存在于my.list对应元素中的行:
df.to.subset = structure(list(IDs = c("ID1", "ID2", "ID3", "ID4", "ID5", "ID6"), gr = c("gr1", "gr1", "gr1", "gr1", "gr1", "gr1"), var = c(-3.451, -3.469, -3.837, -3.344, -3.904, -3.943), col1 = structure(c(1L, 2L, 3L, 1L, 2L, 2L), levels = c("CC", "CT", "TT"), class = "factor"), col2 = structure(c(1L, 1L, 2L, 3L, 3L, 3L), levels = c("GG", "GT", "TT"), class = "factor"), col3 = structure(c(1L, 2L, 1L, 1L, 1L, 1L), levels = c("CC", "CT"), class = "factor"), col4 = structure(c(2L, 2L, 2L, 2L, 2L, 2L), levels = c("CC", "CG", "GG"), class = "factor"), col5 = structure(c(1L, 2L, 2L, 2L, 2L, 2L), levels = c("AC", "CC"), class = "factor"), col6 = structure(c(1L, 1L, 2L, 1L, 1L, 1L), levels = c("GG","AA"), class = "factor")), row.names = c(NA, -6L), class = c("tbl_df", "tbl", "data.frame"))
数据框预览:
IDs gr var col1 col2 col3 col4 col5 col6 <chr> <chr> <dbl> <fct> <fct> <fct> <fct> <fct> <fct> 1 ID1 gr1 -3.45 CC GG CC CG AC GG 2 ID2 gr1 -3.47 CT GG CT CG CC GG 3 ID3 gr1 -3.84 TT GT CC CG CC AA 4 ID4 gr1 -3.34 CC TT CC CG CC GG 5 ID5 gr1 -3.90 CT TT CC CG CC GG 6 ID6 gr1 -3.94 CT TT CC CG CC GG
预期结果:
IDs gr var col1 col2 col3 col4 col5 col6 ID1 gr1 -3.45 CC GG CC CG AC GG ID2 gr1 -3.47 CT GG CT CG CC GG
需求2:重设因子水平
需要将df.to.subset中各列的因子水平重设为以下数据框指定的水平:
factor.levels.cols = structure(list(cols = c("col1", "col2", "col3", "col4", "col5", "col6"), g1 = c("CC", "GG", "CC", "CC", "AA", "AA"), g2 = c("CT", "GT", "CT", "CG", "AC", "AG"), g3 = c("TT", "TT", "TT", "GG", "CC", "GG")), row.names = c(NA, 6L), class = "data.frame")
数据框预览:
cols g1 g2 g3 1 col1 CC CT TT 2 col2 GG GT TT 3 col3 CC CT TT 4 col4 CC CG GG 5 col5 AA AC CC 6 col6 AA AG GG
疑问
是否必须用循环实现?有没有更高效的方法?我需要处理超过100万条数据。
解决方案
不需要使用行级循环,利用R的向量化操作和专业数据处理工具可以高效处理百万级数据,以下是具体实现:
一、高效子集化
核心逻辑:对col1-col6每一列,检查每行值是否在my.list对应元素的允许值中,保留所有列均符合条件的行。
方法1:dplyr+purrr(代码简洁,适配大数据)
library(dplyr) library(purrr) # 提取需要检查的目标列名 target_cols <- names(my.list) # 生成逻辑矩阵:每行每列是否符合条件 keep_matrix <- map2_dfc(target_cols, my.list, ~ df.to.subset[[.x]] %in% .y) # 保留所有列均为TRUE的行 df_subset <- df.to.subset[rowSums(keep_matrix) == length(target_cols), ]
方法2:data.table(速度最优,适合超大数据)
library(data.table) setDT(df.to.subset) # 动态生成筛选表达式 filter_expr <- reduce( imap(my.list, ~ paste0(.y, " %in% c('", paste(.x, collapse = "','"), "')")), ~ paste(.x, .y, sep = " & ") ) # 执行筛选 df_subset <- df.to.subset[eval(parse(text = filter_expr))]
两种方法均避免了行级循环,通过向量化操作提升效率,其中data.table在百万级数据场景下的内存占用和处理速度表现更优。
二、批量重设因子水平
同样采用向量化批量处理,无需行级循环:
方法1:dplyr+purrr
# 将因子水平数据框转换为列表:列名对应水平向量 level_list <- split(factor.levels.cols[, -1], factor.levels.cols$cols) level_list <- map(level_list, ~ na.omit(.x)) # 移除空值(适配原列表长度不足3的列) # 批量重设因子水平 df_updated <- df.to.subset %>% mutate(across(all_of(names(level_list)), ~ factor(.x, levels = level_list[[cur_column()]])))
方法2:data.table
setDT(df.to.subset) # 仅遍历列名(仅6次循环,非行级循环,效率极高) for (col in names(level_list)) { set(df.to.subset, j = col, value = factor(df.to.subset[[col]], levels = level_list[[col]])) }
关键说明
- 规避行级循环:遍历行的循环会大幅降低百万级数据的处理速度,上述方法均按列执行向量化操作,效率提升数个数量级。
- 数据类型兼容:无论目标列是因子还是字符型,
%in%操作都能直接适配,无需额外转换。 - 内存优化:处理超大数据时优先选择
data.table,它的内存占用更低,操作速度更快。
内容的提问来源于stack exchange,提问作者M. Beausoleil
相关产品推荐
相关产品推荐

