第三方DataFrame长字符串解析与按指定列序重组技术问询
解析带标识的餐食字符串并重组DataFrame列序
问题背景
从第三方获取的mealDF中,meals列是包含PR(蛋白质)、FR(水果)、VG(蔬菜)标识的长字符串,每份餐含1个PR、1个FR、2个VG。需要解析后按指定列头[PR, FR, VG1, VG2]重组为目标DataFrame,数据量达数百万行。
现有进展
已通过str_split_fixed拆分出食物项,但无法对应到目标列:
library(stringr) # 原始数据 id <- c('meal1', 'meal2', 'meal3') meals <- c(' FR Banana VG Carrot VG Celery PR Chicken ', ' VG Broccoli PR Tofu VG Celery FR Apple ', ' PR Pork VG Brussels Sprouts FR Orange VG Carrot ') mealDF <- data.frame(id, meals, stringsAsFactors = FALSE) # 拆分食物项 sepMeals <- as.data.frame(str_split_fixed(mealDF$meals, c(' FR | VG | PR '), 5))[,2:5]
解决方案
核心思路:提取每行的标识序列,将食物项与标识配对后,按目标列名重新映射,同时区分两个VG为VG1、VG2。
步骤1:提取每行的标识序列
用str_extract_all提取每行中的PR/FR/VG,保留出现顺序:
# 提取每行的标识(PR/FR/VG) tags <- str_extract_all(mealDF$meals, 'PR|FR|VG') # 转换为数据框,每行对应一个标识序列 tags_df <- as.data.frame(do.call(rbind, tags), stringsAsFactors = FALSE)
步骤2:配对标识与食物项,生成键值对
将标识和拆分后的食物项按行绑定,然后转成长格式,再处理VG的序号:
library(dplyr) library(tidyr) # 合并标识和食物项,转为长格式 combined <- bind_cols(tags_df, sepMeals) %>% rename_with(~paste0('tag', 1:4), 1:4) %>% rename_with(~paste0('food', 1:4), 5:8) %>% pivot_longer(cols = everything(), names_to = c('.value', 'num'), names_pattern = '(tag|food)(\\d)') %>% # 给VG添加序号(因为有两个) group_by(id = mealDF$id, tag) %>% mutate(tag = ifelse(tag == 'VG', paste0(tag, row_number()), tag)) %>% ungroup()
步骤3:按目标列名重组为宽格式
# 转宽格式并匹配目标列序 result <- combined %>% pivot_wider(names_from = tag, values_from = food) %>% select(id, PR, FR, VG1, VG2) %>% # 去除字符串前后空格 mutate(across(c(PR, FR, VG1, VG2), str_trim)) # 查看结果 print(result)
运行后输出符合预期:
id PR FR VG1 VG2 1 meal1 Chicken Banana Carrot Celery 2 meal2 Tofu Apple Broccoli Celery 3 meal3 Pork Orange Brussels Sprouts Carrot
性能优化(针对百万行数据)
- 优先使用
stringr的向量化函数,避免逐行循环 - 若
dplyr/tidyr速度不足,可尝试用data.table替代:
library(data.table) setDT(mealDF) # 提取标识和食物项 mealDF[, c(paste0('tag', 1:4), paste0('food', 1:4)) := { tags <- str_extract_all(meals, 'PR|FR|VG')[[1]] foods <- str_split_fixed(meals, ' FR | VG | PR ', 5)[,2:5] c(as.list(tags), as.list(foods)) }, by = id] # 处理VG序号并转宽格式 long_df <- melt(mealDF, measure.vars = patterns('tag', 'food'), value.name = c('tag', 'food')) long_df[, tag := ifelse(tag == 'VG', paste0(tag, .GRP), tag), by = .(id, tag)] result_dt <- dcast(long_df, id ~ tag, value.var = 'food')[, .(id, PR, FR, VG1, VG2)] result_dt[, lapply(.SD, str_trim), .SDcols = c('PR', 'FR', 'VG1', 'VG2')]
内容的提问来源于stack exchange,提问作者CoolGuyHasChillDay
相关产品推荐
相关产品推荐

