带索引的 for 循环的替代方案 - R

Alternatives to a for loop with indexing - R

我正在将非结构化数据转换为长格式,需要创建一个 ID(分组)变量。我想根据另一个变量中包含的值集分配一个 ID 变量。更具体地说,考虑以下数据集。

set.seed(1234); x.1 <- rep(letters[1:5], 10)
x.2 <- sample(c(0:10), 50, replace=TRUE)
x.3 <- rep(NA, 50); df <- data.frame(x.1, x.2, x.3) 
df <- df[-c(2, 19),]

可以从 x.1 变量中识别出一个独特的案例——它以 a 开始并以 e 结束。情况总是如此。 x.3 将保存 ID(分组)变量。

> head(df, 9)
x.1 x.2 x.3
a   1    NA
c   6    NA
d   6    NA
e   9    NA
a   7    NA
b   0    NA
c   2    NA
d   7    NA
e   5    NA

给定案例的 ae 之间的记录数可能有很大差异(在实际数据文件中)。因此,我无法通过简单地将变量除以固定数量的记录来分配唯一 ID。我想出了如何使用 for 循环进行正确的分配:

START <- which(df$x.1== "a")
END <- which(df$x.1 == "e")
for(i in 1:length(START)){df$x.3[START[i]:END[i]] <- i}

head(df, 9)
x.1 x.2 x.3
a   1    1
c   6    1
d   6    1
e   9    1
a   7    2
b   0    2
c   2    2
d   7    2
e   5    2

这种方法的明显问题是对于超过一百万条记录的数据集来说太慢了。 lapply 似乎是另一种选择,但我似乎无法弄清楚如何指定案例何时结束以及新案例何时开始遍历数据文件。并且,如果存在答案,请随时向我指出现有答案——我没有找到答案!

提前致谢。

如果组与组之间没有间隙,即在每个 "e" 后跟下一个组的 "a" 之后,您可以轻松使用 cumsum

df$x.3 <- cumsum(df$x.1 == "a")
df
#   x.1 x.2 x.3
#1    a   1   1
#3    c   6   1
#4    d   6   1
#5    e   9   1
#6    a   7   2
#7    b   0   2
#8    c   2   2
#9    d   7   2
#10   e   5   2
#11   a   7   3
#12   b   5   3
#13   c   3   3
#...

如果您的数据非常大,您可以使用 data.table 通过引用更新数据:

library(data.table)
setDT(df)[, x.3 := cumsum(x.1 == "a")]

正如@nicola 在评论中正确指出的那样,这假设 a 仅出现在组的开头,不在组的中间。根据示例数据,这似乎是一个有效的假设。


工作原理:

让我们取 "x.1" 列的一个子集:

x <- df$x.1[1:15]
x
# [1] a c d e a b c d e a b c d e a
#Levels: a b c d e

您现在可以检查 x 是否等于 "a",这将创建一个逻辑向量:

x == "a"
# [1]  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE

现在,cumsum 做了什么:它累积地 累加所有 TRUE 值(本质上是 1):

cumsum(x == "a")
# [1] 1 1 1 1 2 2 2 2 2 3 3 3 3 3 4

因此,您可以像使用数字向量一样使用逻辑向量,并像使用 1 和 0 的向量一样使用它们进行数学计算。