【问题标题】:How do I return the first non-NULL value in a series of columns for every row? And the second non-NULL value?如何为每一行返回一系列列中的第一个非 NULL 值?第二个非NULL值?
【发布时间】:2017-06-24 07:26:42
【问题描述】:

我有以下组织数据:

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

【问题讨论】:

  • R 有 NA 值。使用它们比使用空字符串要好得多。

标签: r hierarchy hierarchical-data


【解决方案1】:

我们可以使用apply row-wise 并获取所有非空索引并选择第一个和第二个值分别获取两列。

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]]))

【讨论】:

  • 我的数据框要大得多,并且是较大代码集的一部分,因此如果以后添加或删除其他列,按订单号引用列可能会导致错误。我应该为此任务创建一个单独的数据框并将其合并回来,还是有办法编辑“apply(acme [,8:3]”以使用列名?我试过:acme [,c(“Supervisor ", "经理")]
  • 另外,我如何只返回主管?
  • @Ankie 我已经更新了答案。如果您仍有疑问,请告诉我。
【解决方案2】:

这是data.table“单行”:

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

它是如何工作的

  1. data.frame 被强制转换为 data.table
  2. 并按顺序从宽格式改成长格式
  3. 删除级别为"" 的所有行。
  4. 现在,数据按级别编号排序(隐式传达为Level.1Level.2 等)
  5. 为每个员工提取最后一个(主管)和倒数第二个值(经理),创建一个由三列组成的中间结果。
  6. 最后,将中间结果连接到acme 以追加新列
  7. 并打印

注意:melt() 将发出一条警告消息,指出并非所有级别的列都具有相同的数据类型。这是由于在acme data.frame 的定义中将整数值与字符 ("") 混合造成的。最好使用NA 而不是""。顺便说一句:在这种情况下,可以通过使用 na.rm = FALSEmelt() 来简化代码

注意:第 4 步中的简单 alaphybetical 排序最多可用于 9 个级别(Level.1Level.9)。如果级别更多,则必须提取级别编号并强制为整数。

【讨论】:

    【解决方案3】:

    dplyrtidyr 的解决方案依赖于数据的重塑。

    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)
    

    【讨论】:

      【解决方案4】:

      我们可以通过max.col 做到这一点。找到'Level'列的索引('i1'),将'acme'基于'i1'的子集转换为matrix!=""),应用max.col得到@987654325的列索引@TRUE 值,减 1 得到倒数第二个 TRUE 值('i3'),使用行/列索引提取元素并创建 '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
      

      注意:此解决方案非常简单高效,无需任何不必要的重塑

      【讨论】:

        猜你喜欢
        • 2017-08-14
        • 2019-12-07
        • 1970-01-01
        • 1970-01-01
        • 2021-06-18
        • 2017-09-02
        • 2014-04-03
        相关资源
        最近更新 更多