Перемещение значений между строками без цикла for в R - PullRequest
7 голосов
/ 28 октября 2011

Я написал некоторый код, используемый для организации выборки данных на разных частотах, но я широко использовал циклы for, которые значительно замедляют работу кода, когда набор данных большой. Я просматривал свой код, находил способы удалить циклы for, чтобы ускорить его, но один из циклов поставил меня в тупик.

В качестве примера, скажем, данные были выбраны с частотой 3 Гц, поэтому я получаю три строки на каждую секунду данных. Тем не менее, переменные A, B и C дискретизируются с частотой 1 Гц каждая, поэтому я получу одно значение каждые три строки для каждой из них. Переменные выбираются последовательно в течение одного секунды, что приводит к диагональному характеру данных.

Чтобы усложнить ситуацию, иногда в исходном наборе данных теряется строка.

Моя цель заключается в следующем: определив строки, которые я хочу сохранить, я хочу переместить значения, не относящиеся к 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? Буду признателен за любую помощь! Извините, если этот вопрос слишком многословен; Я хотел ошибиться на стороне слишком большого количества информации, а не недостаточно.

Ответы [ 3 ]

5 голосов
/ 28 октября 2011

Это должно сделать это:

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), ])
3 голосов
/ 28 октября 2011

Опираясь на отличный ответ @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.

РЕДАКТИРОВАТЬ @Джон использует другой подход, который также ускоряет его. Я добавил немного кода, чтобы превратить результат в 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'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 голосов
/ 28 октября 2011

Итак, ваш вопрос был о том, чтобы двигаться без петли.Итак, по-видимому, вы уже решили первый шаг.

> 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

Там нет петли (for или apply) и одна функцияприменяется к строкам матрицы специально оптимизирован и работает очень быстро (rowSums).

...