如何在R语言中基于已有列创建发育阶段时长新列?
问题:统计动物发育阶段时长与总发育天数
现有记录动物随时间发育的R数据框,包含物种(Species)、处理方式(Treatment)及多列日期观测数据,示例代码如下:
df <- data.frame (Species = c("sp1", "sp1", "sp2"), Treatment = c("hot", "cold", "hot"), Apr1 = c("egg", "egg", "egg"), Apr2 = c("1", "egg", "1"), Apr3 = c("2", "egg", "1"), Apr4 = c("adult", "1", "2"))
需要生成新列统计每个发育阶段的天数,以及总发育时长(到成虫阶段的天数),期望输出结构如下:
Species Treatment Apr1 Apr2 Apr3 Apr4 Egg Instar1 Instar2 DaysToAdult 1 sp1 hot egg 1 2 adult 1 1 1 4 2 sp1 cold egg egg egg 1 3 1 0 NA 3 sp2 hot egg 1 1 2 1 2 1 NA
询问是否有简便函数实现该需求,若必要也可逐个创建新列(注:实际有大约15个发育阶段)。
解决方案
方法1:批量处理(适配多发育阶段)
用tidyverse工具包可以实现批量处理,不用逐个手动创建列,适合15个阶段的场景:
- 先加载依赖包
library(tidyverse)
- 定义发育阶段的顺序(确保统计逻辑按阶段递进)
stage_order <- c("egg", "1", "2", "adult")
- 完整处理代码
result <- df %>% # 将日期列转为长格式,方便按阶段分组统计 pivot_longer(cols = starts_with("Apr"), names_to = "Day", values_to = "Stage") %>% # 从日期名中提取天数数字(比如Apr1提取为1) mutate(Day = as.numeric(str_extract(Day, "\\d+"))) %>% # 按物种和处理方式分组 group_by(Species, Treatment) %>% # 按天数排序,保证时间顺序正确 arrange(Day, .by_group = TRUE) %>% # 计算每个阶段的持续天数 mutate( Stage = factor(Stage, levels = stage_order), prev_day = lag(Day, default = 0), # 当阶段变化或到最后一天时,计算当前阶段的时长 stage_duration = ifelse(Stage != lead(Stage, default = Stage), Day - prev_day, 0) ) %>% # 按阶段汇总总天数 group_by(Species, Treatment, Stage) %>% summarise(Duration = sum(stage_duration), .groups = "drop") %>% # 转回宽格式,对应每个阶段的列 pivot_wider(names_from = Stage, values_from = Duration, names_prefix = "") %>% # 未经历的阶段天数设为0 mutate(across(all_of(stage_order), ~replace_na(., 0))) %>% # 计算到成虫的天数:找到出现adult的最大天数,未出现则为NA left_join( df %>% pivot_longer(cols = starts_with("Apr"), names_to = "Day", values_to = "Stage") %>% mutate(Day = as.numeric(str_extract(Day, "\\d+"))) %>% group_by(Species, Treatment) %>% filter(Stage == "adult") %>% summarise(DaysToAdult = max(Day), .groups = "drop"), by = c("Species", "Treatment") ) %>% # 和原数据框合并,保留原始日期列 left_join(df, by = c("Species", "Treatment")) %>% # 调整列顺序,匹配期望输出 select(Species, Treatment, starts_with("Apr"), all_of(stage_order), DaysToAdult) %>% # 重命名阶段列,让名称更直观 rename( Egg = egg, Instar1 = `1`, Instar2 = `2` ) %>% # 未发育到成虫的个体,DaysToAdult设为NA mutate(DaysToAdult = ifelse(adult == 0, NA, DaysToAdult)) %>% # 移除不需要的adult列 select(-adult)
运行后得到的result就是符合要求的输出:
> result # A tibble: 3 × 11 Species Treatment Apr1 Apr2 Apr3 Apr4 Egg Instar1 Instar2 DaysToAdult <chr> <chr> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl> 1 sp1 cold egg egg egg 1 3 1 0 NA 2 sp1 hot egg 1 2 adult 1 1 1 4 3 sp2 hot egg 1 1 2 1 2 1 NA
方法2:逐个创建列(适合阶段数少的场景)
如果不想用批量处理,也可以针对每个阶段单独写逻辑,示例如下:
# 统计Egg阶段天数:数出连续为"egg"的日期列数量 df$Egg <- apply(df[, starts_with("Apr")], 1, function(x) sum(x == "egg")) # 统计Instar1阶段天数:在Egg阶段结束后,数出连续为"1"的数量 df$Instar1 <- apply(df[, starts_with("Apr")], 1, function(x) { egg_last_pos <- max(which(x == "egg")) sum(x[(egg_last_pos + 1):length(x)] == "1") }) # 统计Instar2阶段天数:在Instar1阶段结束后,数出连续为"2"的数量 df$Instar2 <- apply(df[, starts_with("Apr")], 1, function(x) { instar1_last_pos <- max(c(which(x == "egg"), which(x == "1"))) sum(x[(instar1_last_pos + 1):length(x)] == "2") }) # 计算DaysToAdult:找到第一个出现"adult"的日期对应的天数,无则为NA df$DaysToAdult <- apply(df[, starts_with("Apr")], 1, function(x) { adult_pos <- which(x == "adult") if (length(adult_pos) > 0) as.numeric(str_extract(names(x)[adult_pos], "\\d+")) else NA })
这种方法需要为每个阶段单独编写逻辑,阶段越多越繁琐,15个阶段的话更推荐用方法1的批量处理。
内容的提问来源于stack exchange,提问作者Abby L
相关产品推荐
相关产品推荐

