【问题标题】:Add Column Based on Matching Multiple Conditionals and Date Ranges from Multiple Data Sets根据匹配多个数据集中的多个条件和日期范围添加列
【发布时间】:2018-05-05 23:10:25
【问题描述】:

我一直在努力寻找解决这个问题的最佳方法。

为了概括这个问题并帮助可能发现自己需要执行类似任务的其他人,我正在尝试找到将列从第三个数据集添加到一个数据集的最佳方法,即基于中间数据集的匹配,并且属于第三数据集的日期范围。最终结果是将匹配值从第三个数据集中返回到第一个。

以下是示例数据帧的头部以增加清晰度:

> head(SalesData, 10)
   sale_id sale_amt int_rate  sale_date sale_status
1        1     7000    10.71 2008-05-01  Fully Paid
2        2    10800    13.57 2009-11-01  Fully Paid
3        3     7500    10.08 2008-04-01  Fully Paid
4        4     3000    14.26 2009-09-01  Fully Paid
5        5     5600    14.96 2010-02-01 Charged Off
6        6     2800    11.49 2010-08-01  Fully Paid
7        7    10000     8.59 2009-10-01  Fully Paid
8        8    18000    10.39 2008-03-01  Fully Paid
9        9     5000    15.13 2008-04-01  Fully Paid
10      10     9600    12.29 2008-03-01  Fully Paid

> head(EmployeeSales, 10)
   sale_id empl_name empl_num
1        1    Dakota        4
2        2    Dakota        4
3        3      Kami        9
4        4      Adel        1
5        5      Adel        1
6        6     Farah        6
7        7      Kami        9
8        8      Kami        9
9        9       Ida        7
10      10      Kami        9

> head(EmployeeMap, 10)
   empl_num empl_name skill_lvl team start_date   end_date
1         1      Adel       Beg  Red 2007-06-01 2008-05-31
2         1      Adel       Int  Red 2008-06-01 2010-10-31
3         1      Adel       Adv  Red 2010-11-01 2999-12-12
4         2    Bailey       Beg Blue 2010-08-01 2011-04-30
5         2    Bailey       Beg  Red 2011-05-01 2999-12-12
6         3     Casey       Beg Blue 2010-08-01 2010-12-31
7         3     Casey       Int Blue 2011-01-01 2999-12-12
8         4    Dakota       Beg  Red 2007-06-01 2009-08-30
9         4    Dakota       Int  Red 2009-09-01 2010-08-30
10        4    Dakota       Adv  Red 2010-09-01 2011-08-30

所需的输出会将 EmployeeMap 中的 empl_num、sales_team 和 Skill_level 添加到每个 sale_id 的 SalesData。

在尝试将步骤概念化时,这是我的想法,但也许有更好的方法: 从 SalesData 中获取 sale_id,将其与 Employee Sales 中的 sale_id 匹配并获取 empl_num。获取 empl_num 并将其与 Employee Map 中的 empl_num 匹配。现在我们需要从 SalesData 中获取 sale_date 并找出它属于“start_date, end_date”的哪个范围。然后我们会采用匹配的团队和技能水平,并将其添加到 SalesData。

见下表:

 > head(df2,10)
    sale_id sale_amt int_rate  sale_date sale_status empl_num  team skill_lvl
 1        1     7000    10.71 2008-05-01  Fully Paid        4   Red       Beg
 2        2    10800    13.57 2009-11-01  Fully Paid        4   Red       Int
 3        3     7500    10.08 2008-04-01  Fully Paid        9  Blue       Beg
 4        4     3000    14.26 2009-09-01  Fully Paid        1   Red       Int
 5        5     5600    14.96 2010-02-01 Charged Off        1   Red       Int
 6        6     2800    11.49 2010-08-01  Fully Paid        6   Red       Beg
 7        7    10000     8.59 2009-10-01  Fully Paid        9  Blue       Int
 8        8    18000    10.39 2008-03-01  Fully Paid        9  Blue       Beg
 9        9     5000    15.13 2008-04-01  Fully Paid        7  Blue       Beg
 10      10     9600    12.29 2008-03-01  Fully Paid        9  Blue       Int

对我来说复杂的是,在 EmployeeMap 中,start_date 和 end_date 告诉我们每个员工开始和结束属于特定技能水平和团队的日期。但是每个员工都改变了技能水平和/或团队,所以每个员工都有多行。

例如,在 empl_id 为 1 的 EmployeeMap 中,我们可以看到 3 行告诉我们他们的 start_date 和 end_date,而他们在 Red Team 的技能级别为 Beg、Int、Adv。但是有些,例如 empl_id 2 会在保持相同技能水平的同时更改团队。其他人改变技能水平和团队。

如果您对解决此问题的最佳方法有任何见解,我将不胜感激。

【问题讨论】:

    标签: r


    【解决方案1】:

    也许最简单的方法是使用两个类似 SQL 的连接(如果您不熟悉连接/关系代数,我建议您阅读 like this)。

    可以使用基本 R 中的 merge 函数执行许多连接,并且许多其他流行的包(dplyrdata.tablesqldf 等等)在连接操作中提供替代语法或扩展功能.

    两个连接中的第一个(在SalesDataEmployeeSales 之间)可以通过merge 轻松完成:

    merge(SalesData, EmployeeSales, by = "sale_id")
    
    #    sale_id sale_amt int_rate  sale_date sale_status empl_name empl_num
    # 1        1     7000    10.71 2008-05-01  Fully Paid    Dakota        4
    # 2        2    10800    13.57 2009-11-01  Fully Paid    Dakota        4
    # 3        3     7500    10.08 2008-04-01  Fully Paid      Kami        9
    # ...
    

    然而,第二个连接更复杂,因为它不是典型的equi-join。相反,连接逻辑需要在EmployeeMap 中查找行,其中start_date 小于sale_date 并且end date 大于它(除了empl_num 上的相等条件)。

    幸运的是,前面提到的data.table 包提供了应用所述逻辑的能力。

    library(data.table)
    
    # convert all three dataframes to data.table objects
    setDT(SalesData) ; setDT(EmployeeSales) ; setDT(EmployeeMap)
    
    EmployeeMap[SalesData[EmployeeSales[, c("sale_id","empl_num")],
                          on = "sale_id"], 
                on = .(empl_num, start_date <= sale_date, end_date >= sale_date)]
    
    #    empl_num empl_name skill_lvl team start_date   end_date sale_id sale_amt int_rate sale_status
    # 1:        4    Dakota       Beg  Red 2008-05-01 2008-05-01       1     7000    10.71  Fully Paid
    # 2:        4    Dakota       Int  Red 2009-11-01 2009-11-01       2    10800    13.57  Fully Paid
    # 3:        9        NA        NA   NA 2008-04-01 2008-04-01       3     7500    10.08  Fully Paid
    # ...
    

    请注意,所有三个日期列都应该是日期类型,而不是字符串,才能进行比较。另请注意,上面输出中的NA 值是问题中提供的EmployeeMap 快照的结果,它仅映射empl_num 1-4。

    我还建议阅读来自 this question 的答案,以了解有关如何在日期范围内加入的更多背景信息。

    【讨论】:

      【解决方案2】:

      考虑运行merge 两次,然后按日期运行subset。下面将调用嵌套在一个长的单行中,但可以在单独的行中分开。此外,由于您发布的数据是示例行,因此输出小于您想要的结果。

      # MERGE TWICE AND SUBSET BY DATE
      finaldf <- subset(merge(merge(SalesData, EmployeeSales, by="sale_id"), 
                              EmployeeMap, "empl_num", suffixes=c('', '_')),
                        sale_date >= start_date & sale_date <= end_date)
      
      # SELECT NEEDED COLUMNS
      finaldf <- finaldf[c("sale_id", "sale_amt", "int_rate", "sale_date", 
                           "sale_status", "empl_num", "team", "skill_lvl")]
      
      # RE-ORDER BY SALE_ID AND RESET ROW NAMES
      finaldf <- with(finaldf, finaldf[order(sale_id),])
      row.names(finaldf) <- NULL
      
      finaldf
      #   sale_id sale_amt int_rate  sale_date sale_status empl_num team skill_lvl
      # 1       1     7000    10.71 2008-05-01  Fully Paid        4  Red       Beg
      # 2       2    10800    13.57 2009-11-01  Fully Paid        4  Red       Int
      # 3       4     3000    14.26 2009-09-01  Fully Paid        1  Red       Int
      # 4       5     5600    14.96 2010-02-01 Charged Off        1  Red       Int
      

      【讨论】:

        【解决方案3】:

        在 SQL 术语中,这是一个 3 路连接。可以像这样在单个 SQL 选择中完成:

        library(sqldf)
        
        sqldf("
          select s.*, es.empl_num, em.team, em.skill_lvl
          from SalesData s
          left join EmployeeSales es 
            using (sale_id)
          left join EmployeeMap em
            on es.empl_num = em.empl_num and s.sale_date between em.start_date and em.end_date
        ")
        

        使用最后注释中的数据(基于问题显示的数据),我们得到以下结果。问题中显示的 EmployeeMap 数据中仅存在前 4 个员工编号,左连接确保我们获得团队的 NA 值和其他人的技能水平,而不是那些由于不匹配而被删除的 SalesData 行。

           sale_id sale_amt int_rate  sale_date sale_status empl_num team skill_lvl
        1        1     7000    10.71 2008-05-01  Fully Paid        4  Red       Beg
        2        2    10800    13.57 2009-11-01  Fully Paid        4  Red       Int
        3        3     7500    10.08 2008-04-01  Fully Paid        9 <NA>      <NA>
        4        4     3000    14.26 2009-09-01  Fully Paid        1  Red       Int
        5        5     5600    14.96 2010-02-01 Charged Off        1  Red       Int
        6        6     2800    11.49 2010-08-01  Fully Paid        6 <NA>      <NA>
        7        7    10000     8.59 2009-10-01  Fully Paid        9 <NA>      <NA>
        8        8    18000    10.39 2008-03-01  Fully Paid        9 <NA>      <NA>
        9        9     5000    15.13 2008-04-01  Fully Paid        7 <NA>      <NA>
        10      10     9600    12.29 2008-03-01  Fully Paid        9 <NA>      <NA>
        

        注意

        以可重现的形式输入数据:

        SalesData <- structure(list(sale_id = 1:10, sale_amt = c(7000L, 10800L, 7500L, 
        3000L, 5600L, 2800L, 10000L, 18000L, 5000L, 9600L), int_rate = c(10.71, 
        13.57, 10.08, 14.26, 14.96, 11.49, 8.59, 10.39, 15.13, 12.29), 
            sale_date = structure(c(3L, 6L, 2L, 4L, 7L, 8L, 5L, 1L, 2L, 
            1L), .Label = c("2008-03-01", "2008-04-01", "2008-05-01", 
            "2009-09-01", "2009-10-01", "2009-11-01", "2010-02-01", "2010-08-01"
            ), class = "factor"), sale_status = structure(c(2L, 2L, 2L, 
            2L, 1L, 2L, 2L, 2L, 2L, 2L), .Label = c("Charged Off", "Fully Paid"
            ), class = "factor")), class = "data.frame", row.names = c("1", 
        "2", "3", "4", "5", "6", "7", "8", "9", "10"))
        
        EmployeeSales <-
        structure(list(sale_id = 1:10, empl_name = structure(c(2L, 2L, 
        5L, 1L, 1L, 3L, 5L, 5L, 4L, 5L), .Label = c("Adel", "Dakota", 
        "Farah", "Ida", "Kami"), class = "factor"), empl_num = c(4L, 
        4L, 9L, 1L, 1L, 6L, 9L, 9L, 7L, 9L)), class = "data.frame", row.names = c("1", 
        "2", "3", "4", "5", "6", "7", "8", "9", "10"))
        
        EmployeeMap <- structure(list(empl_num = c(1L, 1L, 1L, 2L, 2L, 3L, 3L, 4L, 4L, 
        4L), empl_name = structure(c(1L, 1L, 1L, 2L, 2L, 3L, 3L, 4L, 
        4L, 4L), .Label = c("Adel", "Bailey", "Casey", "Dakota"), class = "factor"), 
            skill_lvl = structure(c(2L, 3L, 1L, 2L, 2L, 2L, 3L, 2L, 3L, 
            1L), .Label = c("Adv", "Beg", "Int"), class = "factor"), 
            team = structure(c(2L, 2L, 2L, 1L, 2L, 1L, 1L, 2L, 2L, 2L
            ), .Label = c("Blue", "Red"), class = "factor"), start_date = structure(c(1L, 
            2L, 6L, 4L, 8L, 4L, 7L, 1L, 3L, 5L), .Label = c("2007-06-01", 
            "2008-06-01", "2009-09-01", "2010-08-01", "2010-09-01", "2010-11-01", 
            "2011-01-01", "2011-05-01"), class = "factor"), end_date = structure(c(1L, 
            4L, 8L, 6L, 8L, 5L, 8L, 2L, 3L, 7L), .Label = c("2008-05-31", 
            "2009-08-30", "2010-08-30", "2010-10-31", "2010-12-31", "2011-04-30", 
            "2011-08-30", "2999-12-12"), class = "factor")), class = "data.frame", 
            row.names = c("1", "2", "3", "4", "5", "6", "7", "8", "9", "10"))
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-07-21
          • 2020-01-04
          • 1970-01-01
          • 1970-01-01
          • 2017-02-06
          • 1970-01-01
          • 1970-01-01
          • 2021-02-11
          相关资源
          最近更新 更多