【问题标题】:Return years of first and last non-NA values for each column R返回每列 R 的第一个和最后一个非 NA 值的年份
【发布时间】:2026-01-04 17:35:01
【问题描述】:

我有一个如下所示的数据框:

# A tibble: 9 x 5
# Groups:   group [3]
      group   year    value1  value2  value3
      <int>   <dbl>   <int>   <int>   <int>
1     1       2000    NA      3       4
2     1       2001    8       3       4
3     1       2002    4       3       NA
4     2       2000    NA      NA      1
5     2       2001    9       NA      1
6     2       2002    1       NA      NA
7     3       2000    NA      5       NA
8     3       2001    9       5       NA
9     3       2002    NA      5       NA

我需要一个脚本来返回每列的第一个和最后一个非 na 值的年份,而与组无关。理想情况下,输出应该是这样的。注意实际的数据集要大得多。

          start   end
value 1   2001    2002
value 2   2000    2002
value 3   2000    2001

【问题讨论】:

  • end 列中的value 1 不应该是2001 吗?你写的不考虑组,也就是说你不关心组列,对吧?
  • @MacOS 似乎是 max 年份的值
  • @akrun 谢谢!我逐行浏览它。该死的。
  • 我们应该考虑编辑问题。
  • @MacOS 我之前也确实是按照OP的标题,然后我发现结果有些不一致并更改了它

标签: r dplyr


【解决方案1】:

我们可以重塑成'long'格式,然后按'name'和summarise进行分组,得到minmax'year'

library(dplyr)
library(tidyr)
library(tibble)
df1 %>%
   select(-group) %>%
   pivot_longer(cols  = starts_with('value'), values_drop_na = TRUE) %>% 
   group_by(name) %>%
   summarise(start = min(year), end = max(year))  %>%
   column_to_rownames('name')
#        start  end
#value1  2001 2002
#value2  2000 2002
#value3  2000 2001

或者使用melt 来自data.table

library(data.table)
melt(setDT(df1), id.var = c('year', 'group'), na.rm = TRUE)[,
     .(start = min(year), end = max(year)), .(variable)]

或者我们也可以使用summarise_at

df1 %>%
    summarise_at(vars(starts_with('value')), ~ 
       list(range(year[!is.na(.)]))) %>% 
    unnest(everything()) %>% 
    pivot_longer(everything())

数据

df1 <- structure(list(group = c(1L, 1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L), 
    year = c(2000L, 2001L, 2002L, 2000L, 2001L, 2002L, 2000L, 
    2001L, 2002L), value1 = c(NA, 8L, 4L, NA, 9L, 1L, NA, 9L, 
    NA), value2 = c(3L, 3L, 3L, NA, NA, NA, 5L, 5L, 5L), value3 = c(4L, 
    4L, NA, 1L, 1L, NA, NA, NA, NA)), class = "data.frame", 
    row.names = c("1", 
"2", "3", "4", "5", "6", "7", "8", "9"))

【讨论】:

  • 更改为 min/max 而不是 first/last 对我有用。我应该指定年份列不是日期格式。我真的很感激!
【解决方案2】:

对于提到的列value1value3,我们首先在year 中找到了一个基本解决方案,而不是NA

data.frame(t(sapply(paste0("value", 1:3), function(i){
val_i <- df1[ , i]

data.frame(start = 
df1$year[min(which(!is.na(val_i)))], end= 
df1$year[max(which(!is.na(val_i)))])
})))

【讨论】:

    【解决方案3】:

    另一个data.table 选项。不确定是否推荐使用 names(.SD),但它应该可以很好地扩展

    library(data.table)
    
    setDT(df1)[, .(val = names(.SD),
                   start = lapply(.SD, function(x) min(year[!is.na(x)])),
                   end = lapply(.SD, function(x) max(year[!is.na(x)]))), .SDcols = startsWith(names(df1), "value")]
    
          val start  end
    1: value1  2001 2002
    2: value2  2000 2002
    3: value3  2000 2001
    

    【讨论】:

      最近更新 更多