在 R 中使用 dplyr 跨数据帧子集

Subsetting Across Data Frames Using dplyr in R

提前感谢您的关注。

我有一个事件(EV)的数据框:

Event_ID | Person_ID | Start_Period | End_Period | Event_Type
------------------------------------------------------------
A        | Person1   | 1            | 9          | Assessment
B        | Person1   | 2            | 9          | Activity
C        | Person1   | 3            | 6          | Assessment
D        | Person2   | 3            | 6          | Activity
E        | Person3   | 7            | 13         | Assessment

我有一个人-周期 (PP) 的数据框

Person_ID | Period
----------------------
Person1   | 1
Person1   | 2
Person1   | 3
Person2   | 1
Person2   | 2
Person2   | 3
Person3   | 1
Person3   | 2
Person3   | 3

我想了解每个时期有多少 活动或评估在该时期 中进行。例如,如果 EV 中 Person1 的事件开始时间为 5,结束时间为 10,则该事件应出现在 PP 中的 5、6、7、8、9、10 中。结果将如下所示:

Person_ID | Period | ActivitiesFreq | AssessmentsFreq
----------------------------------------------
Person1   | 1      | 0              | 1
Person1   | 2      | 1              | 1
Person1   | 3      | 1              | 2
Person2   | 1      | 0              | 0
Person2   | 2      | 0              | 0
Person2   | 3      | 1              | 0
Person3   | 1      | 0              | 0
Person3   | 2      | 0              | 0
Person3   | 3      | 0              | 0

目前我正在使用 for 循环 - slow.And 我拒绝加入,因为完整的数据集有成百上千的数据。我试过使用 dplyr 包中的 mutate:

mutate(PP,SUM(EV$Person_ID==Person_ID,EV$Start_Period<=Period,EV$End_Period>=Period)

但我收到以下错误:

Warning messages:
1: In mutate_impl(.data, dots) :
  is.na() applied to non-(list or vector) of type 'NULL'
2: In mutate_impl(.data, dots) :
  longer object length is not a multiple of shorter object length
3: In mutate_impl(.data, dots) :
  longer object length is not a multiple of shorter object length

我愿意使用其他包 - 我想我不太了解 mutate 的工作方式

这是一个可能的解决方案:

  1. 在 Person_ID 和 Period
  2. 左加入 PP 和 EV (dplyr::left_join)
  3. 按人物和时期分组 dplyr::group_by(Person_ID , 时期)
  4. 使用 dplyr::summarise()
  5. 计算值的数量

这是一个使用 data.table v1.9.5(当前开发版本)的解决方案。我将它用于新的 on= 功能,该功能允许连接而无需设置键:

require(data.table) # v1.9.5+
ans = setDT(df2)[df1, .(Period, Event_Type, 
                        isBetween = Period %between% c(Start_Period, End_Period)), 
                by = .EACHI, on = "Person_ID", nomatch = 0L]

dcast(ans, Person_ID + Period ~ Event_Type, fun.aggregate = sum)
# Using 'isBetween' as value column. Use 'value.var' to override
#    Person_ID Period Activity Assessment
# 1:   Person1      1        0          1
# 2:   Person1      2        1          1
# 3:   Person1      3        1          2
# 4:   Person2      1        0          0
# 5:   Person2      2        0          0
# 6:   Person2      3        1          0
# 7:   Person3      1        0          0
# 8:   Person3      2        0          0
# 9:   Person3      3        0          0

工作原理:

  • setDT()data.frame 转换为 data.table in-地方(参考)。

  • setDT(df2)[df1, on = "Person_ID"] 对列 Person_ID 执行 join 操作。对于 df1 中的每一行,计算 df2 中对应的匹配行,并提取与这些匹配行对应的所有列。

  • setDT(df2)[df1, on = "Person_ID", nomatch = 0L],正如您可能已经猜到的只有 returns 匹配行,并在 df1 中省略了 Person_ID 的那些行在 df2.

  • 中不匹配
  • by = .EACHI 部分是非常有用和强大的论据。它有助于计算我们在 j 中提供的表达式,[] 中的第二个参数,对于 df1.

    中的每一行

    例如,考虑 df1 的第 2 行。在 Person_ID 上加入,它与 df2 的第 1、2、3 行匹配。 by = .EACHI 将执行 .() 中提供的表达式,这将 return Period = 1,2,3Event_Type = "Activity"isBetween = FALSE,TRUE,TRUEEvent_Type被回收以适应最长向量的长度(=3)。

    Essentially, we are joining and computing at the same time. This is a feature (only?) in data.table, where joins are considered as extensions of subset operations. Since we can compute while subsetting and grouping, we can do exactly the same while joining as well. This is both fast and *memory efficient as the entire join doesn't have to be materialised.

    为了更好地理解它,请尝试计算 j 表达式最后一行的结果。

    再看看ans,结果应该很明显了。

  • 然后我们还有最后一步要做,那就是计算每个 Person_ID, PeriodActivityAssessment 的数量,并将它们分开列。这可以使用 dcast 函数一步完成。

    公式意味着对于每个 Person_ID, Period,我们希望 sum() inBetween 的值作为单独的列,对于 [=34= 的每个唯一值].

我还没有想出不加入数据集就可以做到这一点的方法。这是一个基于 dplyr 的解决方案,首先使用 left_join 加入数据集(我只从 EV 中获取任务所需的三列)。

加入数据集后,您只需按 Person_ID 对数据集进行分组,然后计算两类事件的累计和。我加入了一个 arrange 以防真实数据集在 Person_ID 中不按 Period 排序,并删除了 mutate.[=19 中的 Event_Type 列=]

library(dplyr)
PP %>% 
    left_join(., select(EV, -Event_ID, -End_Period), by = c("Person_ID", "Period" = "Start_Period")) %>%
    group_by(Person_ID) %>%
    arrange(Period) %>%
    mutate(ActivitiesFreq = cumsum(Event_Type == "Activity" & !is.na(Event_Type)),
            AssessmentFreq = cumsum(Event_Type == "Assessment" & !is.na(Event_Type)),
            Event_Type = NULL)

Source: local data frame [9 x 4]
Groups: Person_ID

  Person_ID Period ActivitiesFreq AssessmentFreq
1   Person1      1              0              1
2   Person1      2              1              1
3   Person1      3              1              2
4   Person2      1              0              0
5   Person2      2              0              0
6   Person2      3              1              0
7   Person3      1              0              0
8   Person3      2              0              0
9   Person3      3              0              0