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

时间:2011-10-28 15:57:23

标签: performance r for-loop dataframe

我编写了一些用于组织以不同频率采样的数据的代码,但是我广泛使用了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-loop的情况下移动数据有任何想法?我很感激任何帮助!对不起,如果这个问题太罗嗦了;我想在太多信息方面犯错,而不是不够。

3 个答案:

答案 0 :(得分:5)

这应该这样做:

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), ])

答案 1 :(得分: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。这比使用all(is.na(x))在矩阵的每一行上调用apply要快得多。

编辑 @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

答案 2 :(得分: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)。