Czyszczenie wartości „Inf” z ramki danych R.

103

W R mam operację, która tworzy pewne Infwartości, gdy przekształcam ramkę danych.

Chciałbym zamienić te Infwartości na NAwartości. Kod, który mam, jest wolny w przypadku dużych danych, czy istnieje szybszy sposób na zrobienie tego?

Powiedzmy, że mam następującą ramkę danych:

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

W jednym przypadku działa:

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

Więc uogólniłem to następującą pętlą

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

Ale nie sądzę, że to naprawdę wykorzystuje moc R.

Ricardo
źródło

Odpowiedzi:

121

opcja 1

Skorzystaj z faktu, że a data.framejest listą kolumn, a następnie użyj do.calldo odtworzenia data.frame.

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

Opcja 2 -- data.table

Możesz użyć data.tablei set. Pozwala to uniknąć wewnętrznego kopiowania.

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

Lub używając numerów kolumn (prawdopodobnie szybciej, jeśli jest wiele kolumn):

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

Czasy

# 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.tablejest najszybsza. Używanie sapplyznacznie spowalnia działanie.

mnel
źródło
1
Świetna praca nad timingami i modyfikacją @mnel. Chciałbym, żeby istniał TAKI sposób na przeniesienie przedstawiciela między kontami. Myślę, że wyjdę i zagłosuję za innymi Twoimi odpowiedziami.
IRTFM
błąd w do.call (train, lapply (train, function (x) replace (x, is.infinite (x)),: 'what' musi być ciągiem znaków lub funkcją
Hack-R
60

Użyj sapplyiis.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

Lub możesz użyć (dając kredyt @mnel, którego zmiana to jest),

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

co jest znacznie szybsze.

IRTFM
źródło
5
„Sztuczka” polegała na uświadomieniu sobie, is.na<-że nie zaakceptuje wyniku od, lapplyale zaakceptuje wynik od sapply.
IRTFM
Dodałem kilka czasów. Nie jestem pewien, dlaczego is.na<-rozwiązanie jest o wiele wolniejsze.
mnel
trochę profilowania i zredagowałem twoje rozwiązanie, aby było znacznie szybsze.
mnel
19

[<-z mapplyjest nieco szybszy niż sapply.

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

W przypadku danych mnella czas jest

> system.time(dat[mapply(is.infinite, dat)] <- NA)
#   user  system elapsed 
# 15.281   0.000  13.750 
Rich Scriven
źródło
11

Oto rozwiązanie dplyr / tidyverse wykorzystujące funkcję na_if () :

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

Zauważ, że to zastępuje dodatnią nieskończoność tylko NA. Należy powtórzyć, jeśli ujemne wartości nieskończoności również muszą zostać zastąpione.

dat %>% mutate_if(is.numeric, list(~na_if(., Inf))) %>% 
  mutate_if(is.numeric, list(~na_if(., -Inf)))
Feng Mai
źródło
5

W pakiecie hablar jest bardzo proste rozwiązanie tego problemu:

library(hablar)

dat %>% rationalize()

Które zwracają ramkę danych ze wszystkimi Inf, są konwertowane na NA.

Czasy w porównaniu z niektórymi powyższymi rozwiązaniami. Kod: biblioteka (hablar) biblioteka (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))

Wynik:

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

Wygląda na to, że data.table jest szybsza niż hablar. Ale ma dłuższą składnię.

davsjob
źródło
Proszę o czasy?
ricardo
@ricardo dodał trochę czasu
davsjob
1

Feng Mai ma schludną, odwrotną odpowiedź powyżej, aby uzyskać ujemne i dodatnie nieskończoności:

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

Działa to dobrze, ale słowo ostrzeżenia nie polega na zamianie w abs (.) Tutaj, aby wykonać obie linie naraz, jak proponuje się w komentarzu za głosowaniem. Będzie wyglądać, jakby to działało, ale zmienia wszystkie wartości ujemne w zbiorze danych na dodatnie! Możesz to potwierdzić:

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

Dla jednej linii to działa:

  mutate_if(is.numeric, ~ifelse(abs(.) == Inf,NA,.))
Mark E.
źródło
1
Dobry chwyt! Dodałem komentarz dotyczący tego wpływu na oryginalny komentarz - myślę, że jest to lepsze miejsce na rozwiązanie problemu niż nowa odpowiedź. Znalazłem także kilka Twoich postów, które zasługują na uznanie, aby zbliżyć się do 50 punktów reputacji wymaganych do komentowania w dowolnym miejscu.
Gregor Thomas
Dzięki! Tak, zostawiłbym komentarz, gdybym był w stanie.
Mark E.
0

Inne rozwiązanie:

    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
źródło
MusTheDataGuy, dlaczego miałbyś edytować moją odpowiedź, ale nie dodawać własnego rozwiązania? Jest już przycisk „dodaj kolejną odpowiedź”!
Student
-1

Możesz także skorzystać z przydatnej funkcji replace_na: https://tidyr.tidyverse.org/reference/replace_na.html

Gang Su
źródło
1
To jest odpowiedź z pogranicza tylko linków . Powinieneś rozszerzyć swoją odpowiedź, tak aby zawierała jak najwięcej informacji, i użyj łącza tylko w celach informacyjnych.
Goodbye StackExchange