Limpiar los valores `Inf` de un marco de datos R

101

En R, tengo una operación que crea algunos Infvalores cuando transformo un marco de datos.

Me gustaría convertir estos Infvalores en NAvalores. El código que tengo es lento para datos grandes, ¿hay alguna forma más rápida de hacer esto?

Digamos que tengo el siguiente marco de datos:

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

Lo siguiente funciona en un solo caso:

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

Así que lo generalicé con el siguiente bucle.

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

Pero no creo que esto realmente esté usando el poder de R.

ricardo
fuente

Respuestas:

119

Opción 1

Use el hecho de que a data.framees una lista de columnas, luego use do.callpara recrear a data.frame.

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

Opcion 2 -- data.table

Podrías usar data.tabley set. Esto evita algunas copias internas.

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

O usando números de columna (posiblemente más rápido si hay muchas columnas):

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

Tiempos

# 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.tablees el más rápido. El uso sapplyralentiza notablemente las cosas.

mnel
fuente
1
Gran trabajo en los tiempos y la modificación de @mnel. Ojalá hubiera una forma SO de transferir representantes entre cuentas. Creo que saldré y votaré a favor de algunas otras respuestas tuyas.
IRTFM
error en do.call (train, lapply (train, function (x) replace (x, is.infinite (x),: 'what' must be a character string or a function
Hack-R
60

Utilice sapplyyis.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

O puede usar (dando crédito a @mnel, cuya edición es esta),

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

que es significativamente más rápido.

IRTFM
fuente
5
El "truco" consistía en darse cuenta de que el is.na<-no aceptaría un resultado de, lapplypero sí lo aceptaría sapply.
IRTFM
Agregué algunos horarios. No estoy seguro de por qué la is.na<-solución es mucho más lenta.
mnel
un poco de creación de perfiles y he editado su solución para que sea mucho más rápida.
mnel
19

[<-con mapplyes un poco más rápido que sapply.

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

Con los datos de mnel, el tiempo es

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

Aquí hay una solución dplyr / tidyverse usando la función na_if () :

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

Tenga en cuenta que esto solo reemplaza el infinito positivo con NA. Es necesario repetirlo si también es necesario reemplazar los valores infinitos negativos.

dat %>% mutate_if(is.numeric, list(~na_if(., Inf))) %>% 
  mutate_if(is.numeric, list(~na_if(., -Inf)))
Feng Mai
fuente
5

Hay una solución muy simple a este problema en el paquete hablar:

library(hablar)

dat %>% rationalize()

Los que devuelven un marco de datos con todos los Inf se convierten a NA.

Tiempos comparados con algunas soluciones anteriores. Código: biblioteca (hablar) biblioteca (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 es más rápido que hablar. Pero tiene una sintaxis más larga.

davsjob
fuente
¿Tiempos por favor?
ricardo
@ricardo agregó algunos horarios
davsjob
1

Feng Mai tiene una respuesta tidyverse arriba para obtener infinitos negativos y positivos:

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

Esto funciona bien, pero una advertencia es no intercambiar abs (.) Aquí para hacer ambas líneas a la vez como se propone en un comentario votado a favor. Parecerá que funciona, pero cambia todos los valores negativos en el conjunto de datos a positivos. Puedes confirmar con esto:

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 una línea, esto funciona:

  mutate_if(is.numeric, ~ifelse(abs(.) == Inf,NA,.))
Mark E.
fuente
1
¡Buena atrapada! Agregué un comentario a este efecto en el comentario original; creo que es un mejor lugar para abordar el problema que una nueva respuesta. También encontré algunas publicaciones tuyas dignas de votos positivos para acercarte un poco más a la reputación 50 requerida para comentar en cualquier lugar.
Gregor Thomas
¡Gracias! Sí, habría dejado un comentario si hubiera podido.
Mark E.
0

Otra solución:

    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
Estudiante
fuente
MusTheDataGuy, ¿por qué editaría mi respuesta pero no agregaría su propia solución? ¡Ya existe el botón "agregar otra respuesta"!
Estudiante