r: цикл for с вложенными индексами работает очень медленно

У меня есть операция, которую я хотел бы выполнить для каждой строки фрейма данных, изменив один столбец. Я человек apply/ddply/sqldf, но я буду использовать циклы, когда они имеют смысл, и я думаю, что это один из таких случаев. Этот случай сложен, потому что столбец для изменения зависит от информации, которая изменяется по строке; в зависимости от информации в одной ячейке я должен внести изменения только в одну из десяти других ячеек в этой строке. С 75 столбцами и 20000 строк операция занимает 10 минут, тогда как любая другая операция в моем скрипте занимает 0-5 секунд, максимум десять секунд. Я сократил свою проблему до очень простого тестового примера ниже.

n <- 20000
t.df <- data.frame(matrix(1:5000, ncol=10, nrow=n) )
system.time(
 for (i in 1:nrow(t.df)) {
 t.df[i,(t.df[i,1]%%10 + 1)] <- 99
 }
)

Это занимает 70 секунд с десятью столбцами и 360, если ncol=50. Это безумие. Являются ли циклы неправильным подходом? Есть ли лучший, более эффективный способ сделать это?

Я уже пытался инициализировать вложенный термин (t.df[i,1]%%10 + 1) как список вне цикла for. Это экономит около 30 секунд (из 10 минут), но делает приведенный выше пример кода более сложным. Так что это помогает, но это не решение.

Моя текущая лучшая идея пришла во время подготовки этого тестового примера. Для меня актуальны только 10 столбцов (а 75-11 столбцов неактуальны). Поскольку время выполнения очень сильно зависит от количества столбцов, я могу просто запустить описанную выше операцию во фрейме данных, который исключает ненужные столбцы. Это сократит мое время до чуть более минуты. Но является ли «цикл for с вложенными индексами» лучшим способом подумать о моей проблеме?


person enfascination    schedule 30.11.2011    source источник
comment
+1 за урезанный тестовый пример, четко очерченную проблему и воспроизводимый пример.   -  person Ari B. Friedman    schedule 30.11.2011


Ответы (5)


Использование row и col кажется мне менее сложным:

t.df[col(t.df) == (row(t.df) %% 10) + 1]  <- 99

Я думаю, что Tommy's все еще быстрее, но использование row и col может быть проще для понимания.

person joran    schedule 30.11.2011
comment
сладкий! Я чувствую, что мне потребуется некоторое время, чтобы понять это, но я стремлюсь так думать. - person enfascination; 30.11.2011
comment
Я провел последний час, играя со всеми ответами. Это мой фаворит: он примерно такого же порядка, как и другие подходы, и кажется, что он лучше всего обобщает фреймы данных и другие типы данных (например, строки): t.df ‹- data.frame(matrix(as.character (1:5000), ncol=10, nrow=n)) t.df[col(t.df) == (row(t.df) %% 10) + 1] ‹- 99 Теперь мне просто нужно научиться как думать в этих функциях. Тем не менее, больше матриц для меня (@JD Long). - person enfascination; 01.12.2011
comment
К вашему сведению, да, не так быстро, как у Томми. С матрицей это занимает примерно в 4 раза больше времени. На самом деле, если вы сначала не сделаете это матрицей, это будет даже не так быстро, как простая вариация матричного типа Дж. Д. Лонга. Если t.df является матрицей, это быстрее, чем решение JD Long. - person John; 01.12.2011

Кажется, настоящим узким местом являются данные в виде data.frame. Я предполагаю, что в вашей реальной проблеме у вас есть веская причина использовать data.frame. Любой способ преобразовать ваши данные таким образом, чтобы они могли оставаться в матрице?

Кстати, отличный вопрос и очень хороший пример.

Вот иллюстрация того, насколько быстрее выполняются циклы в матрицах, чем в data.frames:

> n <- 20000
> t.df <- (matrix(1:5000, ncol=10, nrow=n) )
> system.time(
+   for (i in 1:nrow(t.df)) {
+     t.df[i,(t.df[i,1]%%10 + 1)] <- 99
+   }
+ )
   user  system elapsed 
  0.084   0.001   0.084 
> 
> n <- 20000
> t.df <- data.frame(matrix(1:5000, ncol=10, nrow=n) )
> system.time(
+   for (i in 1:nrow(t.df)) {
+     t.df[i,(t.df[i,1]%%10 + 1)] <- 99
+   }
+   )
   user  system elapsed 
 31.543  57.664  89.224 
person JD Long    schedule 30.11.2011
comment
Святой ад. Это сократило время с 15 минут до 0,15 секунды. Удивительный. Цикл против применения был неправильным вопросом; его кадр данных против матрицы. Спасибо! - person enfascination; 30.11.2011
comment
Однажды я поймал Джоша Ульриха, когда он был в Чикаго, и позвал его в офис, чтобы просмотреть часть моего кода. Я был уверен, что он собирается показать мне весь этот причудливый кунг-фу, чтобы сделать мой код быстрее. Он пожал плечами и сказал в своей спокойной манере, попробуйте больше использовать матрицы и меньше data.frames, а потом мы пошли пить кофе. Лучший. Обзор кода. Эвар. :) - person JD Long; 30.11.2011

@JD Лонг прав в том, что если t.df можно представить в виде матрицы, все будет намного быстрее.

... И тогда вы можете на самом деле векторизовать все это так, чтобы это было молниеносно:

n <- 20000
t.df <- data.frame(matrix(1:5000, ncol=10, nrow=n) )
system.time({
  m <- as.matrix(t.df)
  m[cbind(seq_len(nrow(m)), m[,1]%%10L + 1L)] <- 99
  t2.df <- as.data.frame(m)
}) # 0.00 secs

К сожалению, матричная индексация, которую я здесь использую, похоже, не работает на data.frame.

EDIT Вариант, в котором я создаю логическую матрицу для индексации, работает на data.frame и почти так же быстр:

n <- 20000
t.df <- data.frame(matrix(1:5000, ncol=10, nrow=n) )
system.time({
  t2.df <- t.df

  # Create a logical matrix with TRUE wherever the replacement should happen
  m <- array(FALSE, dim=dim(t2.df))
  m[cbind(seq_len(nrow(t2.df)), t2.df[,1]%%10L + 1L)] <- TRUE

  t2.df[m] <- 99
}) # 0.01 secs
person Tommy    schedule 30.11.2011
comment
Вы всегда можете просто использовать row и col, как в моем ответе! Но у вас все равно быстрее. - person joran; 30.11.2011
comment
да, мило. Я не осознавал, на какие компромиссы иду, работая с фреймами данных. - person enfascination; 30.11.2011

ОБНОВЛЕНИЕ: добавлена ​​матричная версия решения Томми для сравнительного анализа.

Вы можете векторизовать его. Вот мое решение и сравнение с циклом

n <- 20000
t.df <- (matrix(1:5000, ncol=10, nrow=n))

f_ramnath <- function(x){
  idx <- x[,1] %% 10 + 1
  x[cbind(1:NROW(x), idx)] <- 99  
  return(x)
}

f_long <- function(t.df){
  for (i in 1:nrow(t.df)) {
    t.df[i,(t.df[i,1]%%10 + 1)] <- 99
  }
  return(t.df)
}

f_joran <- function(t.df){
  t.df[col(t.df) == (row(t.df) %% 10) + 1]  <- 99
  return(t.df)
}

f_tommy <- function(t.df){
  t2.df <- t.df
  # Create a logical matrix with TRUE wherever the replacement should happen
  m <- array(FALSE, dim=dim(t2.df))
  m[cbind(seq_len(nrow(t2.df)), t2.df[,1]%%10L + 1L)] <- TRUE
  t2.df[m] <- 99
  return(t2.df)
}

f_tommy_mat <- function(m){
  m[cbind(seq_len(nrow(m)), m[,1]%%10L + 1L)] <- 99
}

Чтобы сравнить производительность различных подходов, мы можем использовать rbenchmark.

library(rbenchmark)
benchmark(f_long(t.df), f_ramnath(t.df), f_joran(t.df), f_tommy(t.df), 
  f_tommy_mat(t.df), replications = 20,  order = 'relative',
  columns = c('test', 'elapsed', 'relative')

               test elapsed  relative
5 f_tommy_mat(t.df)   0.135  1.000000
2   f_ramnath(t.df)   0.172  1.274074
4     f_tommy(t.df)   0.311  2.303704
3     f_joran(t.df)   0.705  5.222222
1      f_long(t.df)   2.411 17.859259
person Ramnath    schedule 30.11.2011
comment
это отличный инструмент, и я заменил им system.time. он выполняет несколько повторений, что обеспечивает лучшее сравнение, и, кроме того, делает хорошую сводку, сравнивающую результаты. - person Ramnath; 30.11.2011
comment
@Ramnath - я немного возражаю против версии f_tommy: моя первая версия была очень похожа на вашу (и опубликована ранее :-) и предполагала аргумент matrix. Версия f_tommy работает непосредственно с data.frame и, таким образом, решает другую проблему (фактически заданную проблему). Он также делает дополнительную копию, чтобы не перезаписывать оригинал. Ни одно из других решений не делает этого, поэтому сравнение неблагоприятно. - person Tommy; 01.12.2011
comment
@Томми. ты абсолютно прав. это не сравнение яблок с яблоками. я обновил упражнение по сравнительному анализу с помощью вашего исходного матричного решения. - person Ramnath; 01.12.2011

Другой вариант, когда вам нужны смешанные типы столбцов (и поэтому вы не можете использовать matrix), — это := в данные.таблица. Пример из ?":=" :

require(data.table)
m = matrix(1,nrow=100000,ncol=100)
DF = as.data.frame(m)
DT = as.data.table(m)    
system.time(for (i in 1:1000) DF[i,1] <- i)
    # 591 seconds 
system.time(for (i in 1:1000) DT[i,V1:=i])
    # 1.16 seconds  ( 509 times faster )
person Matt Dowle    schedule 01.12.2011