Очистка значений Inf из фрейма данных R

103

В R у меня есть операция, которая создает некоторые Infзначения при преобразовании фрейма данных.

Я хотел бы превратить эти Infценности в NAценности. Код, который у меня есть, работает медленно для больших данных, есть ли более быстрый способ сделать это?

Скажем, у меня есть следующий фрейм данных:

dat <- data.frame(a=c(1, Inf), b=c(Inf, 3), d=c("a","b"))

В одном случае работает следующее:

 dat[,1][is.infinite(dat[,1])] = NA

Итак, я обобщил это следующим циклом

cf_DFinf2NA <- function(x)
{
    for (i in 1:ncol(x)){
          x[,i][is.infinite(x[,i])] = NA
    }
    return(x)
}

Но я не думаю, что это действительно использование силы R.

Рикардо
источник

Ответы:

121

Опция 1

Используйте тот факт, что a data.frame- это список столбцов, а затем используйте do.callдля воссоздания a data.frame.

do.call(data.frame,lapply(DT, function(x) replace(x, is.infinite(x),NA)))

Вариант 2 - data.table

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

DT <- data.table(dat)
invisible(lapply(names(DT),function(.name) set(DT, which(is.infinite(DT[[.name]])), j = .name,value =NA)))

Или используя номера столбцов (возможно, быстрее, если столбцов много):

for (j in 1:ncol(DT)) set(DT, which(is.infinite(DT[[j]])), j, NA)

Сроки

# some `big(ish)` data
dat <- data.frame(a = rep(c(1,Inf), 1e6), b = rep(c(Inf,2), 1e6), 
                  c = rep(c('a','b'),1e6),d = rep(c(1,Inf), 1e6),  
                  e = rep(c(Inf,2), 1e6))
# create data.table
library(data.table)
DT <- data.table(dat)

# replace (@mnel)
system.time(na_dat <- do.call(data.frame,lapply(dat, function(x) replace(x, is.infinite(x),NA))))
## user  system elapsed 
#  0.52    0.01    0.53 

# is.na (@dwin)
system.time(is.na(dat) <- sapply(dat, is.infinite))
# user  system elapsed 
# 32.96    0.07   33.12 

# modified is.na
system.time(is.na(dat) <- do.call(cbind,lapply(dat, is.infinite)))
#  user  system elapsed 
# 1.22    0.38    1.60 


# data.table (@mnel)
system.time(invisible(lapply(names(DT),function(.name) set(DT, which(is.infinite(DT[[.name]])), j = .name,value =NA))))
# user  system elapsed 
# 0.29    0.02    0.31 

data.tableсамый быстрый. Использование sapplyзаметно замедляет работу.

Mnel
источник
1
Отличная работа по таймингу и модификации @mnel. Мне жаль, что не было ТАКОГО способа передачи представителя между аккаунтами. Думаю, я пойду и проголосую за некоторые другие ваши ответы.
IRTFM
ошибка в do.call (train, lapply (train, function (x) replace (x, is.infinite (x),: 'what' должно быть строкой символов или функцией)
Hack-R
60

Использование sapplyиis.na<-

> dat <- data.frame(a=c(1, Inf), b=c(Inf, 3), d=c("a","b"))
> is.na(dat) <- sapply(dat, is.infinite)
> dat
   a  b d
1  1 NA a
2 NA  3 b

Или вы можете использовать (отдавая должное @mnel, чье редактирование это),

> is.na(dat) <- do.call(cbind,lapply(dat, is.infinite))

что значительно быстрее.

IRTFM
источник
5
«Уловка» заключалась в том, чтобы понять, is.na<-что объект не примет результат от, lapplyно примет результат от sapply.
IRTFM,
Я добавил несколько таймингов. Я не уверен, почему is.na<-решение работает намного медленнее.
mnel
немного профилирования, и я отредактировал ваше решение, чтобы оно было намного быстрее.
mnel
19

[<-с mapplyнемного быстрее, чем sapply.

> dat[mapply(is.infinite, dat)] <- NA

С данными MNEL, время

> system.time(dat[mapply(is.infinite, dat)] <- NA)
#   user  system elapsed 
# 15.281   0.000  13.750 
Rich Scriven
источник
11

Вот решение dplyr / tidyverse с использованием функции na_if () :

dat %>% mutate_if(is.numeric, list(~na_if(., Inf)))

Обратите внимание, что это заменяет только положительную бесконечность на NA. Необходимо повторить, если значения отрицательной бесконечности также необходимо заменить.

dat %>% mutate_if(is.numeric, list(~na_if(., Inf))) %>% 
  mutate_if(is.numeric, list(~na_if(., -Inf)))
Фэн Май
источник
5

В пакете hablar есть очень простое решение этой проблемы:

library(hablar)

dat %>% rationalize()

Которые возвращают фрейм данных со всеми Inf, преобразуются в NA.

Сроки по сравнению с некоторыми вышеупомянутыми решениями. Код: библиотека (хаблар) библиотека (data.table)

dat <- data.frame(a = rep(c(1,Inf), 1e6), b = rep(c(Inf,2), 1e6), 
                  c = rep(c('a','b'),1e6),d = rep(c(1,Inf), 1e6),  
                  e = rep(c(Inf,2), 1e6))
DT <- data.table(dat)

system.time(dat[mapply(is.infinite, dat)] <- NA)
system.time(dat[dat==Inf] <- NA)
system.time(invisible(lapply(names(DT),function(.name) set(DT, which(is.infinite(DT[[.name]])), j = .name,value =NA))))
system.time(rationalize(dat))

Результат:

> system.time(dat[mapply(is.infinite, dat)] <- NA)
   user  system elapsed 
  0.125   0.039   0.164 
> system.time(dat[dat==Inf] <- NA)
   user  system elapsed 
  0.095   0.010   0.108 
> system.time(invisible(lapply(names(DT),function(.name) set(DT, which(is.infinite(DT[[.name]])), j = .name,value =NA))))
   user  system elapsed 
  0.065   0.002   0.067 
> system.time(rationalize(dat))
   user  system elapsed 
  0.058   0.014   0.072 
> 

Похоже, data.table быстрее, чем hablar. Но имеет более длинный синтаксис.

davsjob
источник
Пожалуйста, сроки?
ricardo
@ricardo добавил некоторые тайминги
davsjob
1

У Фенмая есть ответ на приведенный выше ответ для получения отрицательной и положительной бесконечностей:

dat %>% mutate_if(is.numeric, list(~na_if(., Inf))) %>% 
  mutate_if(is.numeric, list(~na_if(., -Inf)))

Это работает хорошо, но предупреждение - не менять здесь abs (.), Чтобы делать обе строки одновременно, как предлагается в комментариях, получивших одобрение. Это будет выглядеть так, как будто это работает, но все отрицательные значения в наборе данных меняются на положительные! Вы можете подтвердить это:

data(iris)
#The last line here is bad - it converts all negative values to positive
iris %>% 
  mutate_if(is.numeric, ~scale(.)) %>%
  mutate(infinities = Sepal.Length / 0) %>%
  mutate_if(is.numeric, list(~na_if(abs(.), Inf)))

Для одной строки это работает:

  mutate_if(is.numeric, ~ifelse(abs(.) == Inf,NA,.))
Марк Э.
источник
1
Хороший улов! Я добавил комментарий к этому влиянию на исходный комментарий - я думаю, что это лучшее место для решения проблемы, чем новый ответ. Также нашел несколько ваших постов, достойных положительных отзывов, чтобы вы немного приблизились к 50 репутации, необходимой для комментирования где угодно.
Грегор Томас
Спасибо! Да, я бы оставил комментарий, если бы мог.
Марк Э.
0

Другое решение:

    dat <- data.frame(a = rep(c(1,Inf), 1e6), b = rep(c(Inf,2), 1e6), 
                      c = rep(c('a','b'),1e6),d = rep(c(1,Inf), 1e6),  
                      e = rep(c(Inf,2), 1e6))
    system.time(dat[dat==Inf] <- NA)

#   user  system elapsed
#  0.316   0.024   0.340
Ученик
источник
MusTheDataGuy, зачем вам редактировать мой ответ, но не добавлять собственное решение? Уже есть кнопка «добавить еще один ответ»!
Студент
-1

Вы также можете использовать удобную функцию replace_na: https://tidyr.tidyverse.org/reference/replace_na.html

Ган Су
источник
1
Это пограничный ответ, содержащий только ссылку . Вам следует расширить свой ответ, включив сюда как можно больше информации, и использовать ссылку только для справки.
Прощай, StackExchange