Limpando valores `Inf` de um dataframe R


101

Em R, tenho uma operação que cria alguns Infvalores quando transformo um dataframe.

Eu gostaria de transformar esses Infvalores em NAvalores. O código que tenho é lento para grandes dados. Existe uma maneira mais rápida de fazer isso?

Digamos que eu tenha o seguinte dataframe:

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

O seguinte funciona em um único caso:

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

Então eu generalizei com o seguinte loop

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

Mas não acho que isso esteja realmente usando o poder de R.

Respostas:


119

Opção 1

Use o fato de que a data.frameé uma lista de colunas e, em seguida, use do.callpara recriar a data.frame.

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

Opção 2 -- data.table

Você pode usar data.tablee set. Isso evita algumas cópias internas.

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

Ou usando números de coluna (possivelmente mais rápido se houver muitas colunas):

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

Horários

# 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é o mais rápido. O uso sapplytorna as coisas perceptivelmente mais lentas.


1
Excelente trabalho nos tempos e na modificação do @mnel. Eu gostaria que houvesse uma maneira de transferir representantes entre contas. Acho que vou sair e votar a favor de algumas outras respostas suas.
IRTFM

erro em do.call (train, lapply (train, function (x) replace (x, is.infinite (x)),: 'o que' deve ser uma string de caracteres ou uma função
Hack-R de

60

Use sapplyeis.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

Ou você pode usar (dando crédito a @mnel, cuja edição é),

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

o que é significativamente mais rápido.


5
O "truque" era perceber que o is.na<-não aceitaria um resultado de, lapplymas aceitaria um de sapply.
IRTFM

Eu adicionei alguns intervalos. Não sei por que a is.na<-solução é tão mais lenta.
mês de

um pouco de criação de perfil, e editei sua solução para ser muito mais rápida.
mês de

19

[<-com mapplyé um pouco mais rápido do que sapply.

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

Com os dados da mnel, o momento é

> system.time(dat[mapply(is.infinite, dat)] <- NA)
#   user  system elapsed 
# 15.281   0.000  13.750 

11

Aqui está uma solução dplyr / tidyverse usando a função na_if () :

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

Observe que isso apenas substitui o infinito positivo por NA. É necessário repetir se os valores de infinito negativos também precisarem ser substituídos.

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

5

Existe uma solução muito simples para este problema no pacote hablar:

library(hablar)

dat %>% rationalize()

Que retornam um quadro de dados com todos os Inf são convertidos em NA.

Timings em comparação com algumas soluções acima. Código: library (hablar) library (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))

Resultado:

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

Parece que data.table é mais rápido que hablar. Mas tem sintaxe mais longa.


Horários, por favor?
ricardo

@ricardo adicionou alguns horários
davsjob

1

Feng Mai tem uma resposta inversa acima para obter infinitos negativos e positivos:

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

Isso funciona bem, mas uma palavra de advertência não é trocar abs (.) Aqui para fazer as duas linhas ao mesmo tempo, como é proposto em um comentário votado. Vai parecer que funciona, mas altera todos os valores negativos no conjunto de dados para positivos! Você pode confirmar com isto:

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

Para uma linha, isso funciona:

  mutate_if(is.numeric, ~ifelse(abs(.) == Inf,NA,.))

1
Boa pegada! Eu adicionei um comentário a esse efeito no comentário original - acho que é um lugar melhor para resolver o problema do que uma nova resposta. Também encontrei alguns posts seus dignos de votos positivos para chegar um pouco mais perto dos 50 pontos de reputação necessários para comentar em qualquer lugar.
Gregor Thomas

Obrigado! Sim, teria deixado um comentário se pudesse.
Mark E.

0

Outra solução:

    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, por que você editaria minha resposta, mas não adicionaria sua própria solução? Já existe o botão "adicionar outra resposta"!
Estudante de

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.