Задача: перекодировать data.frame () - сделать это быстрее - PullRequest
15 голосов
/ 27 мая 2011

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

Самый быстрый код, который выполняет ту же задачу с предоставленными примерами данных 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))) не быстрее.

Ответы [ 6 ]

10 голосов
/ 27 мая 2011

Объединение @ ответа DWin и моего ответа от Наиболее эффективный метод списка для метода 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's answer
10 голосов
/ 27 мая 2011

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

> 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
8 голосов
/ 27 мая 2011

Попробуйте это:

m <- as.matrix(dat)

dat <- data.frame( matrix( re.codes[m], nrow = nrow(m)))
7 голосов
/ 25 сентября 2012

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.

6 голосов
/ 27 мая 2011

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

    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 столбцов.

3 голосов
/ 27 мая 2011

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...