Bereinigen von Inf-Werten aus einem R-Datenrahmen

101

In R habe ich eine Operation, die Infbeim Transformieren eines Datenrahmens einige Werte erstellt.

Ich möchte diese InfWerte in NAWerte verwandeln . Der Code, den ich habe, ist für große Datenmengen langsam. Gibt es eine schnellere Möglichkeit, dies zu tun?

Angenommen, ich habe den folgenden Datenrahmen:

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

Folgendes funktioniert in einem Einzelfall:

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

Also habe ich es mit folgender Schleife verallgemeinert

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

Aber ich denke nicht, dass dies wirklich die Kraft von R nutzt.

Ricardo
quelle

Antworten:

119

Option 1

Verwenden Sie die Tatsache, dass a data.frameeine Liste von Spalten ist, und do.callerstellen Sie dann a neu data.frame.

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

Option 2 -- data.table

Sie könnten data.tableund verwenden set. Dies vermeidet internes Kopieren.

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

Oder Spaltennummern verwenden (möglicherweise schneller, wenn viele Spalten vorhanden sind):

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.tableist das schnellste. Verwenden sapplyverlangsamt die Dinge spürbar.

mnel
quelle
1
Tolle Arbeit an den Timings und der Modifikation @mnel. Ich wünschte, es gäbe eine SO-Möglichkeit, Repräsentanten über Konten hinweg zu übertragen. Ich denke, ich werde rausgehen und einige andere Antworten von dir positiv bewerten.
IRTFM
Fehler in do.call (train, lapply (train, function (x) replace (x, is.infinite (x) ,: 'what' muss eine Zeichenkette oder eine Funktion sein
Hack-R
60

Verwenden Sie sapplyundis.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

Oder Sie können verwenden (@mnel gutschreiben, dessen Bearbeitung dies ist),

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

das ist deutlich schneller.

IRTFM
quelle
5
Der "Trick" bestand darin, zu erkennen is.na<-, dass ein Ergebnis von nicht akzeptiert, lapplyaber eines von akzeptiert werden würde sapply.
IRTFM
Ich habe einige Timings hinzugefügt. Ich bin mir nicht sicher, warum die is.na<-Lösung so viel langsamer ist.
mnel
Ein bisschen Profilerstellung, und ich habe Ihre Lösung so bearbeitet, dass sie viel schneller ist.
mnel
19

[<-mit mapplyist etwas schneller als sapply.

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

Mit den Daten von mnel ist das Timing

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

Hier ist eine dplyr / tidyverse-Lösung mit der Funktion na_if () :

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

Beachten Sie, dass dies nur die positive Unendlichkeit durch NA ersetzt. Muss wiederholt werden, wenn auch negative Unendlichkeitswerte ersetzt werden müssen.

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

Es gibt eine sehr einfache Lösung für dieses Problem im Hablar-Paket:

library(hablar)

dat %>% rationalize()

Welche einen Datenrahmen mit allen Inf zurückgeben, werden in NA konvertiert.

Timings im Vergleich zu einigen der oben genannten Lösungen. Code: Bibliothek (hablar) Bibliothek (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))

Ergebnis:

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

Scheint, als ob data.table schneller als hablar ist. Hat aber längere Syntax.

Davsjob
quelle
Timings bitte?
Ricardo
@ricardo fügte einige Timings hinzu
Davsjob
1

Feng Mai hat oben eine ordentliche Antwort, um negative und positive Unendlichkeiten zu erhalten:

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

Dies funktioniert gut, aber ein Wort der Warnung ist, hier nicht abs (.) Zu tauschen, um beide Zeilen gleichzeitig zu machen, wie in einem positiv bewerteten Kommentar vorgeschlagen. Es sieht so aus, als würde es funktionieren, ändert jedoch alle negativen Werte im Datensatz in positive! Sie können dies bestätigen:

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

Für eine Zeile funktioniert dies:

  mutate_if(is.numeric, ~ifelse(abs(.) == Inf,NA,.))
Mark E.
quelle
1
Guter Fang! Ich habe diesem Effekt einen Kommentar zum ursprünglichen Kommentar hinzugefügt. Ich denke, dies ist ein besserer Ort, um das Problem zu beheben, als eine neue Antwort. Ich habe auch einige Ihrer Posts gefunden, die es wert sind, positiv bewertet zu werden, um Sie ein wenig näher an die 50 Reputation heranzuführen, die erforderlich sind, um irgendwo Kommentare abzugeben.
Gregor Thomas
Vielen Dank! Ja, ich hätte einen Kommentar hinterlassen, wenn ich dazu in der Lage gewesen wäre.
Mark E.
0

Eine andere Lösung:

    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
Student
quelle
MusTheDataGuy, warum sollten Sie meine Antwort bearbeiten, aber keine eigene Lösung hinzufügen? Es gibt bereits die Schaltfläche "Weitere Antwort hinzufügen"!
Student
-1

Sie können auch die praktische Funktion replace_na verwenden: https://tidyr.tidyverse.org/reference/replace_na.html

Gang Su
quelle
1
Dies ist eine Borderline- Link-Antwort . Sie sollten Ihre Antwort erweitern, um möglichst viele Informationen hier aufzunehmen, und den Link nur als Referenz verwenden.
Auf Wiedersehen StackExchange