在R中没有for循环的行之间移动值

Posted

技术标签:

【中文标题】在R中没有for循环的行之间移动值【英文标题】:Moving values between rows without a for loop in R 【发布时间】:2011-12-17 10:03:41 【问题描述】:

我编写了一些代码来组织以不同频率采样的数据,但我大量使用了 for 循环,当数据集很大时,这会显着降低代码的运行速度。我一直在检查我的代码,想方设法去除 for 循环以加快它的速度,但其中一个循环让我很困惑。

举个例子,假设数据以 3Hz 的频率进行采样,所以我每秒得到三行数据。但是,变量 A、B 和 C 的采样频率分别为 1Hz,因此我将每三行得到一个值。变量在一秒内连续采样,导致数据呈对角线性质。

更复杂的是,有时原始数据集中会丢失一行。

我的目标是:在确定了我希望保留的行之后,我想将非 NA 值从后续行向上移动到保持行中。如果不是因为丢失数据的问题,我会始终保留包含第一个变量值的行,但如果其中一行丢失,我将保留下一行。

在下面的例子中,第六个样本和第十个样本都丢失了。

A <- c(1, NA, NA, 4, NA, 7, NA, NA, NA, NA)
B <- c(NA, 2, NA, NA, 5, NA, 8, NA, 11, NA)
C <- c(NA, NA, 3, NA, NA, NA, NA, 9, NA, 12)

test_df <- data.frame(A = A, B = B, C = C)

test_df
     A  B  C
 1   1 NA NA
 2  NA  2 NA
 3  NA NA  3
 4   4 NA NA
 5  NA  5 NA
 6   7 NA NA
 7  NA  8 NA
 8  NA NA  9
 9  NA 11 NA
10  NA NA 12

keep_rows <- c(1, 4, 6, 9)

将值向上移动到保持器行后,我将删除中间行,结果如下:

test_df <- test_df[keep_rows, ]
test_df
     A  B  C
 1   1  2  3
 2   4  5 NA
 3   7  8  9
 4  NA 11 12

最后,我只希望每一秒的数据只有一行,NA 值应该只保留在丢失一行原始数据的地方。

有没有人知道如何在不使用 for 循环的情况下向上移动数据?我会很感激任何帮助!对不起,如果这个问题太罗嗦了;我宁愿选择信息过多而不是信息不足。

【问题讨论】:

澄清:是否可以丢失两个或多个连续行?例如,如果您没有删除 6 和 10,而是删除了 4、5 和 6,那么您将如何检测到发生了这种情况? 是的,可能会丢失多个连续的行。在我的代码中的其他地方考虑到这一点后,我已经确定要保留哪些行,我最终会得到类似于我在示例中给出的“rows_to_keep”变量的东西,它是由代码生成的,而不是用户定义的。我不确定这是否会导致给出的解决方案出现问题,因为“rows_to_keep”变量没有实现。 【参考方案1】:

应该这样做:

test_df = with(test_df, cbind(A[1:(length(A)-2)], B[2:(length(B)-1)], C[3:length(C)]))
test_df = data.frame(test_df[!apply(test_df, 1, function(x) all(is.na(x))), ])
colnames(test_df) = c('A', 'B', 'C')
> test_df
   A  B  C
1  1  2  3
2  4  5 NA
3  7  8  9
4 NA 11 12

如果你想要更快的东西

test_df = data.frame(test_df[rowSums(is.na(test_df)) != ncol(test_df), ])

【讨论】:

循环不是for,但它仍然是一个循环。 查看编辑。它总是会是一个循环,但至少这个循环都是用 C 代码编写的。 它不会“总是”成为一个循环。我确定有一个矢量化的解决方案。今晚如果没人比我写,我会写出来。 @goodside 太好了,我很想知道是否有。上面的第二种方法在我的机器上 10^6 行只需要 50ms,但你可以将它与你在你的机器上做的比较。 谢谢你,约翰。这个解决方案对我有用!我需要让自己更熟悉“with”和“apply”函数,这样我才能自己想出这种类型的答案。【参考方案2】:

所以你的问题只是关于没有循环的向上移动。所以显然你已经解决了第一步。

> test_m <- with( test_df, cbind(A[1:(length(A)-2)], B[2:(length(B)-1)], C[3:length(C)]) )
> test_m
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]   NA   NA   NA
[3,]   NA   NA   NA
[4,]    4    5   NA
[5,]   NA   NA   NA
[6,]    7    8    9
[7,]   NA   NA   NA
[8,]   NA   11   12

现在是一个矩阵。您可以轻松地消除现在没有数据点的行而无需循环。如果您希望将其返回到 data.frame,那么您可以使用不同的方法,但是对于大量数据,这种方法运行速度最快。我喜欢让 NA 成为一个不可能的值……也许是 -1,但你会最了解你的数据……也许是 -pi。

test_m[is.na(test_m)] <- -1

现在只需选择那些不可能数字的属性的行

test_m <- test_m[rowSums(test_m) > -3,]

而且,如果你愿意,你可以把 NA 放回去。

test_m[test_m == -1] <- NA
test_m
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]    4    5   NA
[3,]    7    8    9
[4,]   NA   11   12

没有循环(forapply),并且跨矩阵行应用的一个函数经过特别优化并且运行速度非常快(rowSums)。

【讨论】:

谢谢你,约翰。您在这里建议的进出NA的方法将来肯定会对我有用。【参考方案3】:

在@John Colby 的出色回答的基础上,我们可以摆脱应用步骤并加快速度(大约 20 倍):

# Create a bigger test set 
A <- c(1, NA, NA, 4, NA, 7, NA, NA, NA, NA)
B <- c(NA, 2, NA, NA, 5, NA, 8, NA, 11, NA)
C <- c(NA, NA, 3, NA, NA, NA, NA, 9, NA, 12)
n=1e6; test_df = data.frame(A=rep(A, len=n), B=rep(B, len=n), C=rep(C, len=n))

# John Colby's method, 9.66 secs
system.time(
  df1 = with(test_df, cbind(A[1:(length(A)-2)], B[2:(length(B)-1)], C[3:length(C)]))
  df1 = data.frame(df1[!apply(df1, 1, function(x) all(is.na(x))), ])
  colnames(df1) = c('A', 'B', 'C')
)

# My method, 0.48 secs
system.time(
  df2 = with(test_df, data.frame(A=A[1:(length(A)-2)], B=B[2:(length(B)-1)], C=C[3:length(C)]))
  df2 = df2[is.finite(with(df2, A|B|C)),]
  row.names(df2) <- NULL
)

identical(df1, df2) # TRUE

...这里的窍门是A|B|C 只是NA 如果所有值都是NA。事实证明,这比使用 apply 在矩阵的每一行上调用 all(is.na(x)) 快得多。

EDIT @John 有一种不同的方法可以加快速度。我添加了一些代码将结果转换为具有正确名称的 data.frame 并对其进行计时。它似乎与我的解决方案几乎相同的速度。

# John's method, 0.50 secs
system.time(
  test_m = with(test_df, cbind(A[1:(length(A)-2)], B[2:(length(B)-1)], C[3:length(C)]))
  test_m[is.na(test_m)] <- -1
  test_m <- test_m[rowSums(test_m) > -3,]
  test_m[test_m == -1] <- NA
  df3 <- data.frame(test_m)
  colnames(df3) = c('A', 'B', 'C')
)

identical(df1, df3) # TRUE

再次编辑 ...@John Colby 的更新答案更快!

# John Colby's method, 0.39 secs
system.time(
  df4 = with(test_df, cbind(A[1:(length(A)-2)], B[2:(length(B)-1)], C[3:length(C)]))
  df4 = data.frame(df4[rowSums(is.na(df4)) != ncol(df4), ])
  colnames(df4) = c('A', 'B', 'C')
)

identical(df1, df4) # TRUE

【讨论】:

我不知道 system.time 函数,但我会从中得到很多好处!感谢您提供的建议和方法。

以上是关于在R中没有for循环的行之间移动值的主要内容,如果未能解决你的问题,请参考以下文章

在 for 循环 r markdown 中包含两个变量之间的空格(pdf 输出)

R中带有for循环的多个数据帧上的行名

使用 foreach 函数和 doParallel 库在 R 中嵌套 for 循环

无法从嵌套for循环访问父for循环

R组合具有相似值的行

如何用R语言for循环形成112358