Nettoyage des valeurs "Inf" d'une trame de données R

101

Dans R, j'ai une opération qui crée des Infvaleurs lorsque je transforme un dataframe.

Je voudrais transformer ces Infvaleurs en NAvaleurs. Le code que j'ai est lent pour les données volumineuses, existe-t-il un moyen plus rapide de le faire?

Disons que j'ai le dataframe suivant:

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

Ce qui suit fonctionne dans un seul cas:

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

Alors je l'ai généralisé avec la boucle suivante

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

Mais je ne pense pas que cela utilise vraiment le pouvoir de R.

Ricardo
la source

Réponses:

119

Option 1

Utilisez le fait que a data.frameest une liste de colonnes, puis utilisez do.callpour recréer un fichier data.frame.

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

Option 2 -- data.table

Vous pouvez utiliser data.tableet set. Cela évite une copie interne.

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

Ou en utilisant des numéros de colonne (peut-être plus rapide s'il y a beaucoup de colonnes):

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

Timings

# 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.tableest le plus rapide. L'utilisation sapplyralentit sensiblement les choses.

mnel
la source
1
Excellent travail sur les horaires et la modification @mnel. J'aurais aimé qu'il y ait un moyen SO de transférer des représentants entre les comptes. Je pense que je vais aller voter pour quelques autres réponses à vous.
IRTFM
erreur dans do.call (train, lapply (train, function (x) replace (x, is.infinite (x),: 'what' must be a character string or a function
Hack-R
60

Utiliser sapplyetis.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 vous pouvez utiliser (attribuer le crédit à @mnel, dont il s'agit de modifier),

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

ce qui est nettement plus rapide.

IRTFM
la source
5
Le "truc" consistait à réaliser que le is.na<-n'accepterait pas un résultat lapplymais en accepterait un sapply.
IRTFM
J'ai ajouté quelques horaires. Je ne sais pas pourquoi la is.na<-solution est tellement plus lente.
mnel
un peu de profilage, et j'ai édité votre solution pour qu'elle soit beaucoup plus rapide.
mnel
19

[<-avec mapplyest un peu plus rapide que sapply.

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

Avec les données de mnel, le timing est

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

Voici une solution dplyr / tidyverse utilisant la fonction na_if () :

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

Notez que cela ne remplace que l'infini positif par NA. Il faut répéter si les valeurs infinies négatives doivent également être remplacées.

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

Il existe une solution très simple à ce problème dans le package hablar:

library(hablar)

dat %>% rationalize()

Qui renvoient une trame de données avec tous les Inf sont convertis en NA.

Timings par rapport à certaines solutions ci-dessus. Code: bibliothèque (hablar) bibliothèque (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))

Résultat:

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

On dirait que data.table est plus rapide que hablar. Mais a une syntaxe plus longue.

davsjob
la source
Les horaires s'il vous plaît?
ricardo
@ricardo a ajouté quelques horaires
davsjob
1

Feng Mai a une réponse tidyverse ci-dessus pour obtenir des infinis négatifs et positifs:

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

Cela fonctionne bien, mais un mot d'avertissement est de ne pas permuter en abs (.) Ici pour faire les deux lignes à la fois comme cela est proposé dans un commentaire voté. Il semblera que cela fonctionne, mais change toutes les valeurs négatives de l'ensemble de données en positives! Vous pouvez confirmer avec ceci:

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

Pour une ligne, cela fonctionne:

  mutate_if(is.numeric, ~ifelse(abs(.) == Inf,NA,.))
Mark E.
la source
1
Bonne prise! J'ai ajouté un commentaire à cet effet sur le commentaire original - je pense que c'est un meilleur endroit pour aborder le problème qu'une nouvelle réponse. Vous avez également trouvé certains de vos messages dignes de votes positifs pour vous rapprocher un peu plus des 50 points de réputation requis pour commenter n'importe où.
Gregor Thomas le
Merci! Oui j'aurais laissé un commentaire si j'avais pu.
Mark E.
0

Une autre solution:

    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
Étudiant
la source
MusTheDataGuy, pourquoi éditeriez-vous ma réponse sans ajouter votre propre solution? Il existe déjà un bouton "ajouter une autre réponse"!
Étudiant