Задача: перекодировка data.frame() - ускорение работы

Перекодирование - обычная практика для данных опроса, но наиболее очевидные маршруты занимают больше времени, чем нужно.

Самый быстрый код, который выполняет ту же задачу с предоставленными образцами данных на system.time() на моей машине, выигрывает.

## Sample data
dat <- cbind(rep(1:5,50000),rep(5:1,50000),rep(c(1,2,4,5,3),50000))
dat <- cbind(dat,dat,dat,dat,dat,dat,dat,dat,dat,dat,dat,dat)
dat <- as.data.frame(dat)
re.codes <- c("This","That","And","The","Other")

Код для оптимизации.

for(x in 1:ncol(dat)) { 
    dat[,x] <- factor(dat[,x], labels=re.codes)
    }

Текущий system.time():

   user  system elapsed 
   4.40    0.10    4.49 

Подсказка: dat <- lapply(1:ncol(dat), function(x) dat[,x] <- factor(dat[,x],labels=rc))) не быстрее.

Ответы

Ответ 1

Сочетание @DWin answer и мой ответ от Самый эффективный список для метода data.frame?:

system.time({
  dat3 <- list()
  # define attributes once outside of loop
  attrib <- list(class="factor", levels=re.codes)
  for (i in names(dat)) {              # loop over each column in 'dat'
    dat3[[i]] <- as.integer(dat[[i]])  # convert column to integer
    attributes(dat3[[i]]) <- attrib    # assign factor attributes
  }
  # convert 'dat3' into a data.frame. We can do it like this because:
  # 1) we know 'dat' and 'dat3' have the same number of rows and columns
  # 2) we want 'dat3' to have the same colnames as 'dat'
  # 3) we don't care if 'dat3' has different rownames than 'dat'
  attributes(dat3) <- list(row.names=c(NA_integer_,nrow(dat)),
    class="data.frame", names=names(dat))
})
identical(dat2, dat3)  # 'dat2' is from @Dwin answer

Ответ 2

Мой компьютер, очевидно, намного медленнее, но структура - довольно быстрый способ сделать это:

> system.time({
+ dat1 <- dat
+ for(x in 1:ncol(dat)) {
+   dat1[,x] <- factor(dat1[,x], labels=re.codes)
+   }
+ })
   user  system elapsed 
 11.965   3.172  15.164 
> 
> system.time({
+ m <- as.matrix(dat)
+ dat2 <- data.frame( matrix( re.codes[m], nrow = nrow(m)))
+ })
   user  system elapsed 
  2.100   0.516   2.621 
> 
> system.time(dat3 <- data.frame(lapply(dat, structure, class='factor', levels=re.codes)))
   user  system elapsed 
  0.484   0.332   0.820 

# this isn't because the levels get re-ordered
> all.equal(dat1, dat2)

> all.equal(dat1, dat3)
[1] TRUE

Ответ 3

Попробуйте следующее:

m <- as.matrix(dat)

dat <- data.frame( matrix( re.codes[m], nrow = nrow(m)))

Ответ 4

A data.table ответьте на свое рассмотрение. Мы просто используем setattr() из него, который работает на data.frame и столбцах data.frame. Нет необходимости конвертировать в data.table.

Повторные тестовые данные:

dat <- cbind(rep(1:5,50000),rep(5:1,50000),rep(c(1L,2L,4L,5L,3L),50000)) 
dat <- cbind(dat,dat,dat,dat,dat,dat,dat,dat,dat,dat,dat,dat) 
dat <- as.data.frame(dat) 
re.codes <- c("This","That","And","The","Other") 

Теперь измените класс и установите уровни каждого столбца напрямую, по ссылке:

require(data.table)
system.time(for (i in 1:ncol(dat)) {
  setattr(dat[[i]],"levels",re.codes)
  setattr(dat[[i]],"class","factor")
}
# user  system elapsed 
#   0       0       0 

identical(dat, <result in question>)
# [1] TRUE

Побеждает ли 0,00? По мере увеличения размера данных этот метод остается на уровне 0,00.

Хорошо, я признаю, что я немного изменил входные данные, чтобы быть integer для всех столбцов (вопрос имеет double входные данные в третьем столбце). Те столбцы double должны быть преобразованы в integer, потому что factor применим только для векторов integer. Как упоминалось в других ответах.

Итак, строго с входными данными в вопросе, включая преобразование double в integer:

dat <- cbind(rep(1:5,50000),rep(5:1,50000),rep(c(1,2,4,5,3),50000))             
dat <- cbind(dat,dat,dat,dat,dat,dat,dat,dat,dat,dat,dat,dat)               
dat <- as.data.frame(dat)               
re.codes <- c("This","That","And","The","Other")           

system.time(for (i in 1:ncol(dat)) {
  if (!is.integer(dat[[i]]))
      set(dat,j=i,value=as.integer(dat[[i]]))
  setattr(dat[[i]],"levels",re.codes)
  setattr(dat[[i]],"class","factor")
})
#  user  system elapsed
#  0.06    0.01    0.08      # on my slow netbook

identical(dat, <result in question>)
# [1] TRUE

Обратите внимание, что set также работает и с data.frame. Вам не нужно конвертировать в data.table, чтобы использовать его.

Это очень маленькие времена, ясно. Поскольку это всего лишь небольшой набор входных данных:

dim(dat)
# [1] 250000     36 
object.size(dat)
# 68.7 Mb

Масштабирование этого должно выявить большие различия. Но даже в этом случае я думаю, что это должно быть (примерно) заметно быстрее. Не значительная разница в том, что кто-то думает об этом, но при этом.

Функция setattr также находится в пакете bit, кстати. Таким образом, метод 0,00 может быть выполнен либо с помощью data.table, либо bit. Чтобы сделать преобразование типа по ссылке (если требуется), требуется либо set, либо := (оба в data.table), afaik.

Ответ 5

Страница справки для класса() говорит, что класс < - устарел и используется как. методы. Я не совсем понял, почему ранее усилия сообщали 0 наблюдений, когда данные были явно в объекте, но этот метод приводит к полному объекту:

    system.time({ dat2 <- vector(mode="list", length(dat))
      for (i in 1:length(dat) ){ dat2[[i]] <- dat[[i]]
        storage.mode(dat2[[i]]) <- "integer"
               attributes(dat2[[i]]) <- list(class="factor", levels=re.codes)}
  names(dat2) <- names(dat)
  dat2 <- as.data.frame(dat2)})
#--------------------------  
  user  system elapsed 
  0.266   0.290   0.560 
> str(dat2)
'data.frame':   250000 obs. of  36 variables:
 $ V1 : Factor w/ 5 levels "This","That",..: 1 2 3 4 5 1 2 3 4 5 ...
 $ V2 : Factor w/ 5 levels "This","That",..: 5 4 3 2 1 5 4 3 2 1 ...
 $ V3 : Factor w/ 5 levels "This","That",..: 1 2 4 5 3 1 2 4 5 3 ...
 $ V4 : Factor w/ 5 levels "This","That",..: 1 2 3 4 5 1 2 3 4 5 ...
 $ V5 : Factor w/ 5 levels "This","That",..: 5 4 3 2 1 5 4 3 2 1 ...
 $ V6 : Factor w/ 5 levels "This","That",..: 1 2 4 5 3 1 2 4 5 3 ...
 $ V7 : Factor w/ 5 levels "This","That",..: 1 2 3 4 5 1 2 3 4 5 ...
 $ V8 : Factor w/ 5 levels "This","That",..: 5 4 3 2 1 5 4 3 2 1 ...
 snipped

Все 36 столбцов есть.

Ответ 6

Создание факторов дорого; только один раз это сопоставимо с командами, использующими structure, и, на мой взгляд, предпочтительнее, поскольку вам не нужно зависеть от того, как факторы будут построены.

rc <- factor(re.codes, levels=re.codes)
dat5 <- as.data.frame(lapply(dat, function(d) rc[d]))

РЕДАКТИРОВАТЬ 2: Интересно, что это похоже на случай, когда lapply ускоряет работу. Это для цикла значительно медленнее.

for(i in seq_along(dat)) {
  dat[[i]] <- rc[dat[[i]]]
}

РЕДАКТИРОВАТЬ 1: Вы также можете ускорить процесс, уточнив свои типы. Попробуйте любое из решений (но особенно ваше оригинальное), создавая ваши данные как целые числа, следующим образом. Подробнее см. Предыдущий ответ здесь.

dat <- cbind(rep(1:5,50000),rep(5:1,50000),rep(c(1L,2L,4L,5L,3L),50000))

Это также хорошая идея, поскольку преобразование в целые числа из плавающих точек, как это делается во всех более быстрых решениях здесь, может привести к неожиданному поведению, см. этот вопрос.