如何获取数据框每行首尾非NA值的列名及日期间隔
解决方法:提取每个用户的首尾非NA日期及间隔
首先注意你的数据框里有重复的列名2012-02-01,这会导致R自动重命名列(比如变成2012-02-01.1),建议先修正列名,以下示例将第三个日期列改为2012-03-01:
# 构建示例数据框 df <- data.frame( `User ID` = c("Cell 1", "Cell 3"), `2012-01-01` = c(NA, 1), `2012-02-01` = c(2, NA), `2012-03-01` = c(NA, 5), check.names = FALSE # 保留原始日期列名 )
基础R实现
先提取日期列名称,再逐行计算首尾非NA对应的日期:
# 获取日期列的名称集合 date_cols <- colnames(df)[-1] # 提取首个非NA值对应的日期 df$first_date <- apply(df[-1], 1, function(x) { non_na_pos <- which(!is.na(x)) # 无NA值时返回NA,否则取对应位置的列名 if (length(non_na_pos) == 0) NA else date_cols[min(non_na_pos)] }) # 提取末个非NA值对应的日期 df$last_date <- apply(df[-1], 1, function(x) { non_na_pos <- which(!is.na(x)) if (length(non_na_pos) == 0) NA else date_cols[max(non_na_pos)] }) # 计算日期间隔(转成Date类型后计算) df$date_diff <- as.Date(df$last_date) - as.Date(df$first_date)
运行后结果:
User ID 2012-01-01 2012-02-01 2012-03-01 first_date last_date date_diff 1 Cell 1 NA 2 NA 2012-02-01 2012-02-01 0 days 2 Cell 3 1 NA 5 2012-01-01 2012-03-01 59 days
你的代码问题分析
- 第一段代码:
x[which.min(which(is.na(x) == FALSE))]取的是非NA值本身,不是列名;且which.min多余——which(!is.na(x))直接返回非NA值的位置,取min()就是第一个非NA的位置。 - 第二段代码:
x是行向量,没有列名属性,colnames(x)会返回NULL,应该从原数据框的date_cols里提取对应位置的名称。
tidyverse实现(简洁直观)
用宽转长的方式处理,适合大规模数据:
library(tidyverse) result <- df %>% # 宽表转长表,过滤NA值 pivot_longer(-`User ID`, names_to = "date", values_to = "value", values_drop_na = TRUE) %>% mutate(date = as.Date(date)) %>% # 按用户分组计算首尾日期和间隔 group_by(`User ID`) %>% summarise( first_date = as.character(min(date)), last_date = as.character(max(date)), date_diff = max(date) - min(date) ) %>% # 合并回原数据框,保留无NA值的用户 right_join(df, by = "User ID")
内容的提问来源于stack exchange,提问作者Nat Guess
相关产品推荐
相关产品推荐

