获取 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)
}
}
最终需要根据业务含义确认,究竟是取倒数第二份工作,还是直接取最早的一份工作。
备注:内容仅供参考。
