PAYATHON 2026

获取 data frame 每行倒数第二个非 NA 值

数据小许

结论

先筛出所有工作列。逐行去掉 NA 后,取剩余值中的倒数第二个。如果有效工作值不足两个,则返回 NA_character_。

job_cols <- grep("^Job", names(data), value = TRUE)

second_last_job <- function(x) {
  x <- x[!is.na(x)]
  
  if (length(x) < 2L) {
    NA_character_
  } else {
    x[length(x) - 1L]
  }
}

data$second_last_job <- apply(
  data[job_cols],
  1,
  second_last_job
)

为什么要先筛选工作列

原代码直接对整个 data 使用 apply():

apply(data, 1, first_job)

这样一来,Index、Years1、Skills 等非工作字段也会传入函数。数据框同时包含字符列和数值列时,apply() 通常会先把整个数据框转换成字符矩阵,导致不应参与计算的字段混入结果。

因此,需要先选择名称以 Job 开头的列:

job_cols <- grep("^Job", names(data), value = TRUE)

对于给出的数据,这段代码会选出:

Job1_latest, Job2, Job3, ..., Job12

FromJob、Years1 等其他列不会被选中。

如何取倒数第二个非 NA 值

假设某一行的工作记录如下:

c("最新工作", "上一份工作", "更早的工作", NA)

去掉 NA 后还剩三个值,倒数第二个元素的位置是:

length(x) - 1L

完整函数如下:

second_last_job <- function(x) {
  x <- x[!is.na(x)]

  if (length(x) < 2L) {
    return(NA_character_)
  }

  x[length(x) - 1L]
}

也可以使用 tail():

second_last_job <- function(x) {
  x <- x[!is.na(x)]

  if (length(x) < 2L) {
    NA_character_
  } else {
    tail(x, 2)[1]
  }
}

tail(x, 2) 会返回最后两个有效值,其中第一个就是倒数第二个值。

使用 dplyr 的写法

如果想通过列名规则明确限定工作列,可以使用 rowwise() 和 c_across():

library(dplyr)

data <- data %>%
  rowwise() %>%
  mutate(
    second_last_job = {
      jobs <- c_across(matches("^Job"))
      jobs <- jobs[!is.na(jobs)]

      if (length(jobs) < 2L) {
        NA_character_
      } else {
        jobs[length(jobs) - 1L]
      }
    }
  ) %>%
  ungroup()

注意列的排列顺序

这些方法会按照工作列在数据框中的实际顺序计算,因此要确保列的顺序符合预期:

Job1_latest, Job2, Job3, ..., Job12

如果列的位置不可靠,建议显式生成列名,以免字符串排序时 Job10 排在 Job2 前面:

job_cols <- c("Job1_latest", paste0("Job", 2:12))

data$second_last_job <- apply(
  data[job_cols],
  1,
  second_last_job
)

还需要区分两个概念:

  • 如果目标是“倒数第二个非 NA 工作”,以上代码可以直接使用。
  • 如果目标是“最早的一份工作”,通常应该取最后一个非 NA 工作,而不是倒数第二个。

取最早工作可以写成:

earliest_job <- function(x) {
  x <- x[!is.na(x)]

  if (length(x) == 0L) {
    NA_character_
  } else {
    tail(x, 1)
  }
}

最终需要根据业务含义确认,究竟是取倒数第二份工作,还是直接取最早的一份工作。

备注:内容仅供参考。