如何 return 每一行的一系列列中的第一个非 NULL 值?而第二个非空值?
How do I return the first non-NULL value in a series of columns for every row? And the second non-NULL value?
我有以下组织数据:
EmployeeID <- c(10:15)
Job.Title <- c("Program Manager", "Development Manager", "Developer" , "Developer", "Developer", "Summer Intern")
Level.1 <- c(1,1,1,1,1,1)
Level.2 <- c(2,2,2,2,2,2)
Level.3 <- c("",10,10,10,10,10)
Level.4 <- c("","",11,11,11,11)
Level.5 <- c("","","","","",12)
Level.6 <- c("","","","","","")
Pay.Type <- c("Salary", "Salary", "Salary", "Salary", "Salary", "Hourly")
acme = data.frame(EmployeeID, Job.Title, Level.1, Level.2, Level.3, Level.4, Level.5, Level.6, Pay.Type)
acme
EmployeeID Job.Title Level.1 Level.2 Level.3 Level.4 Level.5 Level.6 Pay.Type
1 10 Program Manager 1 2 Salary
2 11 Development Manager 1 2 10 Salary
3 12 Developer 1 2 10 11 Salary
4 13 Developer 1 2 10 11 Salary
5 14 Developer 1 2 10 11 Salary
6 15 Summer Intern 1 2 10 11 12 Hourly
对于每一行,我需要确定 Level.1 到 Level.6 的第一个非 NULL 值,从右边开始是 Level.6,然后是 Level.5,然后是 Level.4,依此类推.我还需要在同一模式中识别第二个非 Null 值。每行的标识值需要放入新列中,因此最终表格如下所示:
EmployeeID Job.Title Level.1 Level.2 Level.3 Level.4 Level.5 Level.6 Pay.Type Supervisor Manager
1 10 Program Manager 1 2 Salary 2 1
2 11 Development Manager 1 2 10 Salary 10 2
3 12 Developer 1 2 10 11 Salary 11 10
4 13 Developer 1 2 10 11 Salary 11 10
5 14 Developer 1 2 10 11 Salary 11 10
6 15 Summer Intern 1 2 10 11 12 Hourly 12 11
我们可以用 max.col
做到这一点。找到'Level'列的索引('i1'),将'acme'基于'i1'的子集转换为matrix
(!=""
),应用max.col
并得到last
TRUE值的列索引,减1得到倒数第二个TRUE值('i3'),使用row/column索引提取元素并创建'Supervisor' 和 'Manager' 列
i1 <- grep("Level\.\d+", names(acme))
i2 <- max.col(acme[i1]!="", "last")
i3 <- i2-1
acme$Supervisor <- acme[i1][cbind(1:nrow(acme), i2)]
acme$Manager <- acme[i1][cbind(1:nrow(acme), i3)]
acme
# EmployeeID Job.Title Level.1 Level.2 Level.3 Level.4 Level.5 Level.6 Pay.Type Supervisor Manager
#1 10 Program Manager 1 2 Salary 2 1
#2 11 Development Manager 1 2 10 Salary 10 2
#3 12 Developer 1 2 10 11 Salary 11 10
#4 13 Developer 1 2 10 11 Salary 11 10
#5 14 Developer 1 2 10 11 Salary 11 10
#6 15 Summer Intern 1 2 10 11 12 Hourly 12 11
注意:此解决方案非常简单高效,无需任何不必要的重塑
我们可以使用apply
row-wise 并得到所有索引not-null 和select 第一和第二个值分别得到两列。
acme[, c("Supervisor", "Manager")] <- t(apply(acme[, 8:3], 1,
function(x) c(x[which(x != "")[1]], x[which(x != "")[2]])))
acme
# EmployeeID Job.Title Level.1 Level.2 Level.3 Level.4 Level.5 Level.6 Pay.Type Supervisor Manager
#1 10 Program Manager 1 2 Salary 2 1
#2 11 Development Manager 1 2 10 Salary 10 2
#3 12 Developer 1 2 10 11 Salary 11 10
#4 13 Developer 1 2 10 11 Salary 11 10
#5 14 Developer 1 2 10 11 Salary 11 10
#6 15 Summer Intern 1 2 10 11 12 Hourly 12 11
编辑
如果有很多列,我们需要找到开始和结束列的索引。我们可以使用 grep
同样的
mincol <- min(grep("Level", colnames(acme)))
maxcol <- max(grep("Level", colnames(acme)))
acme[, c("Supervisor", "Manager")] <- t(apply(acme[, maxcol:mincol], 1,
function(x) c(x[which(x != "")[1]], x[which(x != "")[2]])))
应该可以。
如果我们只需要 Supervisor
我们可以忽略第二部分。
acme[, "Supervisor"] <- t(apply(acme[, maxcol:mincol], 1,
function(x) x[which(x != "")[1]]))
这是一个data.table
"one-liner":
library(data.table)
setDT(acme)[melt(acme, measure.vars = patterns("Level.\d"))[value != ""][
order(variable), .(Supervisor = value[.N], Manager = value[.N - 1]), by = EmployeeID],
on = "EmployeeID"][]
EmployeeID Job.Title Level.1 Level.2 Level.3 Level.4 Level.5 Level.6 Pay.Type Supervisor
#1: 10 Program Manager 1 2 Salary 2
#2: 11 Development Manager 1 2 10 Salary 10
#3: 12 Developer 1 2 10 11 Salary 11
#4: 13 Developer 1 2 10 11 Salary 11
#5: 14 Developer 1 2 10 11 Salary 11
#6: 15 Summer Intern 1 2 10 11 12 Hourly 12
Manager
#1: 1
#2: 2
#3: 10
#4: 10
#5: 10
#6: 11
工作原理
data.frame
被强制转换为 data.table
- 并且按顺序从宽格式改成了长格式
- 删除级别为
""
的所有行。
- 现在,数据按级别编号排序(隐式表示为
Level.1
、Level.2
等)
- 为每个员工提取最后一个值(主管)和倒数第二个值(经理),创建一个由三列组成的中间结果。
- 最后,将中间结果连接到
acme
以追加新列
- 并打印
注意:melt()
会发出警告信息,提示并非所有级别的列都具有相同的数据类型。这是由于在 acme
data.frame 的定义中将整数值与字符 (""
) 混合造成的。最好使用 NA
而不是 ""
。顺便说一句:在那种情况下,可以通过使用 na.rm = FALSE
和 melt()
来简化代码
注意: 第 4 步中的简单字母顺序最多适用于 9 个级别(Level.1
到 Level.9
)。如果有更多级别,则必须提取级别编号并将其强制为整数。
dplyr
和 tidyr
依赖于数据重塑的解决方案。
library(tidyverse)
acme %>%
gather('level', 'value', starts_with('Level.')) %>%
group_by(EmployeeID) %>%
filter(value != '') %>%
summarise(Supervisor = last(value),
Manager = nth(value, -2)) %>%
left_join(acme)
我有以下组织数据:
EmployeeID <- c(10:15)
Job.Title <- c("Program Manager", "Development Manager", "Developer" , "Developer", "Developer", "Summer Intern")
Level.1 <- c(1,1,1,1,1,1)
Level.2 <- c(2,2,2,2,2,2)
Level.3 <- c("",10,10,10,10,10)
Level.4 <- c("","",11,11,11,11)
Level.5 <- c("","","","","",12)
Level.6 <- c("","","","","","")
Pay.Type <- c("Salary", "Salary", "Salary", "Salary", "Salary", "Hourly")
acme = data.frame(EmployeeID, Job.Title, Level.1, Level.2, Level.3, Level.4, Level.5, Level.6, Pay.Type)
acme
EmployeeID Job.Title Level.1 Level.2 Level.3 Level.4 Level.5 Level.6 Pay.Type
1 10 Program Manager 1 2 Salary
2 11 Development Manager 1 2 10 Salary
3 12 Developer 1 2 10 11 Salary
4 13 Developer 1 2 10 11 Salary
5 14 Developer 1 2 10 11 Salary
6 15 Summer Intern 1 2 10 11 12 Hourly
对于每一行,我需要确定 Level.1 到 Level.6 的第一个非 NULL 值,从右边开始是 Level.6,然后是 Level.5,然后是 Level.4,依此类推.我还需要在同一模式中识别第二个非 Null 值。每行的标识值需要放入新列中,因此最终表格如下所示:
EmployeeID Job.Title Level.1 Level.2 Level.3 Level.4 Level.5 Level.6 Pay.Type Supervisor Manager
1 10 Program Manager 1 2 Salary 2 1
2 11 Development Manager 1 2 10 Salary 10 2
3 12 Developer 1 2 10 11 Salary 11 10
4 13 Developer 1 2 10 11 Salary 11 10
5 14 Developer 1 2 10 11 Salary 11 10
6 15 Summer Intern 1 2 10 11 12 Hourly 12 11
我们可以用 max.col
做到这一点。找到'Level'列的索引('i1'),将'acme'基于'i1'的子集转换为matrix
(!=""
),应用max.col
并得到last
TRUE值的列索引,减1得到倒数第二个TRUE值('i3'),使用row/column索引提取元素并创建'Supervisor' 和 'Manager' 列
i1 <- grep("Level\.\d+", names(acme))
i2 <- max.col(acme[i1]!="", "last")
i3 <- i2-1
acme$Supervisor <- acme[i1][cbind(1:nrow(acme), i2)]
acme$Manager <- acme[i1][cbind(1:nrow(acme), i3)]
acme
# EmployeeID Job.Title Level.1 Level.2 Level.3 Level.4 Level.5 Level.6 Pay.Type Supervisor Manager
#1 10 Program Manager 1 2 Salary 2 1
#2 11 Development Manager 1 2 10 Salary 10 2
#3 12 Developer 1 2 10 11 Salary 11 10
#4 13 Developer 1 2 10 11 Salary 11 10
#5 14 Developer 1 2 10 11 Salary 11 10
#6 15 Summer Intern 1 2 10 11 12 Hourly 12 11
注意:此解决方案非常简单高效,无需任何不必要的重塑
我们可以使用apply
row-wise 并得到所有索引not-null 和select 第一和第二个值分别得到两列。
acme[, c("Supervisor", "Manager")] <- t(apply(acme[, 8:3], 1,
function(x) c(x[which(x != "")[1]], x[which(x != "")[2]])))
acme
# EmployeeID Job.Title Level.1 Level.2 Level.3 Level.4 Level.5 Level.6 Pay.Type Supervisor Manager
#1 10 Program Manager 1 2 Salary 2 1
#2 11 Development Manager 1 2 10 Salary 10 2
#3 12 Developer 1 2 10 11 Salary 11 10
#4 13 Developer 1 2 10 11 Salary 11 10
#5 14 Developer 1 2 10 11 Salary 11 10
#6 15 Summer Intern 1 2 10 11 12 Hourly 12 11
编辑
如果有很多列,我们需要找到开始和结束列的索引。我们可以使用 grep
同样的
mincol <- min(grep("Level", colnames(acme)))
maxcol <- max(grep("Level", colnames(acme)))
acme[, c("Supervisor", "Manager")] <- t(apply(acme[, maxcol:mincol], 1,
function(x) c(x[which(x != "")[1]], x[which(x != "")[2]])))
应该可以。
如果我们只需要 Supervisor
我们可以忽略第二部分。
acme[, "Supervisor"] <- t(apply(acme[, maxcol:mincol], 1,
function(x) x[which(x != "")[1]]))
这是一个data.table
"one-liner":
library(data.table)
setDT(acme)[melt(acme, measure.vars = patterns("Level.\d"))[value != ""][
order(variable), .(Supervisor = value[.N], Manager = value[.N - 1]), by = EmployeeID],
on = "EmployeeID"][]
EmployeeID Job.Title Level.1 Level.2 Level.3 Level.4 Level.5 Level.6 Pay.Type Supervisor
#1: 10 Program Manager 1 2 Salary 2
#2: 11 Development Manager 1 2 10 Salary 10
#3: 12 Developer 1 2 10 11 Salary 11
#4: 13 Developer 1 2 10 11 Salary 11
#5: 14 Developer 1 2 10 11 Salary 11
#6: 15 Summer Intern 1 2 10 11 12 Hourly 12
Manager
#1: 1
#2: 2
#3: 10
#4: 10
#5: 10
#6: 11
工作原理
data.frame
被强制转换为data.table
- 并且按顺序从宽格式改成了长格式
- 删除级别为
""
的所有行。 - 现在,数据按级别编号排序(隐式表示为
Level.1
、Level.2
等) - 为每个员工提取最后一个值(主管)和倒数第二个值(经理),创建一个由三列组成的中间结果。
- 最后,将中间结果连接到
acme
以追加新列 - 并打印
注意:melt()
会发出警告信息,提示并非所有级别的列都具有相同的数据类型。这是由于在 acme
data.frame 的定义中将整数值与字符 (""
) 混合造成的。最好使用 NA
而不是 ""
。顺便说一句:在那种情况下,可以通过使用 na.rm = FALSE
和 melt()
注意: 第 4 步中的简单字母顺序最多适用于 9 个级别(Level.1
到 Level.9
)。如果有更多级别,则必须提取级别编号并将其强制为整数。
dplyr
和 tidyr
依赖于数据重塑的解决方案。
library(tidyverse)
acme %>%
gather('level', 'value', starts_with('Level.')) %>%
group_by(EmployeeID) %>%
filter(value != '') %>%
summarise(Supervisor = last(value),
Manager = nth(value, -2)) %>%
left_join(acme)