R中的高效方法是将新列添加到具有大数据集的数据框中

Ste*_*eMc 1 r dataset dataframe dplyr data.table

我真的需要加速一些R代码.我有一个特定运动的大型数据集.数据框中的每一行代表游戏中的某种类型的动作.对于每个游戏(game_id),我们有两个团队(team_id)参与游戏.time_ref在数据框中是每个游戏按时间顺序的动作.type_id是游戏中的动作类型.player_off被设置为TRUE或被FALSE链接到action_id=3.action_id=3代表玩家获得一张牌并被player_off设置为TRUE/ FALSE如果玩家在获得该牌时被罚下.示例data.frame:

> df

game_id team_id action_id   player_off  time_ref
100     10         1             NA       1000
100     10         1             NA       1001
100     10         1             NA       1002
100     11         1             NA       1003
100     11         2             NA       1004
100     11         1             NA       1005
100     10         3             1        1006
100     11         1             NA       1007
100     10         1             NA       1008
100     10         1             NA       1009
101     12         3             0        1000
101     12         1             NA       1001
101     12         1             NA       1002
101     13         2             NA       1003
101     13         3             1        1004
101     12         1             NA       1005
101     13         1             NA       1006
101     13         1             NA       1007
101     12         1             NA       1008
101     12         1             NA       1009
Run Code Online (Sandbox Code Playgroud)

我需要的是数据框中的另一个栏目,它给出了我TRUEFALSE两个球队在每个动作(排)发生时球场上是否有相同/不等数量的球员.

所以,game_id=100有一个action_id=3player_off=1用于team_id=10time_ref=1006.所以我们知道球队在场上的数量与场上球员的数量相同,但在比赛剩余时间内则不相等(time_ref>1006).同样的事情game_id=101也发生了.

这是一个数据框的示例,其中包含我希望为数据集添加的额外列.

>df
game_id team_id action_id   player_off  time_ref    is_even
100      10        1            NA        1000         1
100      10        1            NA        1001         1
100      10        1            NA        1002         1 
100      11        1            NA        1003         1
100      11        2            NA        1004         1
100      11        1            NA        1005         1
100      10        3            1         1006         1
100      11        1            NA        1007         0
100      10        1            NA        1008         0
100      10        1            NA        1009         0
101      12        3            0         1000         1
101      12        1            NA        1001         1
101      12        1            NA        1002         1
101      13        2            NA        1003         1
101      13        3            1         1004         1
101      12        1            NA        1005         0
101      13        1            NA        1006         0
101      13        1            NA        1007         0
101      12        1            NA        1008         0
101      12        1            NA        1009         0
Run Code Online (Sandbox Code Playgroud)

所以你可以看到在game_id=100一个玩家被发送的时候time_ref=1006所有以前的行被标记为is_even=1,随后标记为不均匀或0.类似的game_id=101time_ref=1004.

实现这个额外列的最有效方法是什么?优选不使用for循环.

Mar*_*gan 5

对于一些矢量

x = c(0, NA, NA, NA, 1, NA, NA, NA)
Run Code Online (Sandbox Code Playgroud)

编写一个函数来标准化数据(0或1个玩家丢失),计算丢失的玩家的累积数量,并将其与零进行比较,

fun0 = function(x)  {
    x[is.na(x)] = 0
    cumsum(x) == 0
}
Run Code Online (Sandbox Code Playgroud)

对于多个组,请使用ave()分组变量

x = c(x, rev(x))
grp = rep(1:2, each = length(x) / 2)
ave(x, grp, FUN = fun0)
Run Code Online (Sandbox Code Playgroud)

对于问题中的数据,请尝试

df$is_even = ave(df$player_off, df$game_id, FUN = fun)
Run Code Online (Sandbox Code Playgroud)

在语义上,似乎fun0()比这个解决方案中隐含的更复杂,特别是如果每​​个团队失去一个玩家,他们甚至会再次,就像@SunLisa所说的那样.如果是,请清理数据

df$player_off[is.na(df$player_off)] = 0
Run Code Online (Sandbox Code Playgroud)

并改变fun0(),例如,

fun1 <- function(x, team) {
    is_team_1 <- team == head(team, 1) # is 'team' the first team?
    x1 <- x & is_team_1                # lost player & team 1
    x2 <- x & !is_team_1               # lost player & team 2
    cumsum(x1) == cumsum(x2)           # same total number of players?
}
Run Code Online (Sandbox Code Playgroud)

(将逻辑返回值强制转换为整数似乎不是一个好主意).这可以通过组来应用

df$is_even = ave(seq_len(nrow(df)), df$game_id, FUN = function(i) {
    fun1(df$player_off[i], df$team_id[i])
})
Run Code Online (Sandbox Code Playgroud)

要么

split(df$is_even, df$game_id) <-
    Map(fun1,
        split(df$player_off, df$game_id),
        split(df$team_id, df$game_id)
    )
Run Code Online (Sandbox Code Playgroud)

执行ave()是有用的,重要的是

split(x, g) <- lapply(split(x, g), FUN)
Run Code Online (Sandbox Code Playgroud)

右侧x按组拆分g,然后应用于FUN()每个组.左侧split<-()是一个棘手的操作,使用组索引来更新原始向量x.

评论

最初的问题是'no for loops',但实际上是lapply()(in ave())并且Map()正是如此; ave()由于它采用的分割 - 应用 - 组合策略,而不是OP可能实现的,可能会迭代游戏,对数据帧进行子集化,然后更新每个游戏的数据帧,因此效率相对较高.子集将具有整个数据集的重复子集,并且特别是更新将至少复制每个赋值的整个结果列; 这种复制会大大减慢执行速度.OP也有可能在努力fun0(); 这将有助于澄清问题,特别是标题,以确定这是问题.

有更快的方法,特别是使用data.table包,但原理是相同的 - 确定一个按照你想要的方式对向量进行操作的函数,并按组应用它.

另一种完全矢量化的解决方案遵循此建议来按组计算累积总和.因为fun0(),标准化x为在特定时间点离开游戏的玩家数量,没有NA

x[is.na(x)] = 0
Run Code Online (Sandbox Code Playgroud)

相当于fun(),计算离开游戏的玩家的累积总和,而不考虑群体

cs = cumsum(x)
Run Code Online (Sandbox Code Playgroud)

对累积和适用的组更正此问题

in_game = cs - (grp - 1)
Run Code Online (Sandbox Code Playgroud)

当0名玩家离开游戏时,将其设置为"TRUE"

is_even = (in_game == 0)
Run Code Online (Sandbox Code Playgroud)

这取决于grp从1到组数的索引; 对于这里的数据可能grp = match(df$game_id, unique(df$game_id)).存在类似的解决方案fun1().