Cleaning `Inf` values from an R dataframe

In R, I have an operation which creates some Inf values when I transform a dataframe.

I would like to turn these Inf values into NA values. The code I have is slow for large data, is there a faster way of doing this?

Say I have the following dataframe:

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

The following works in a single case:

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

So I generalized it with following loop

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

But I don't think that this is really using the power of R.

143065 次浏览

选择一

Use the fact that a data.frame is a list of columns, then use do.call to recreate a data.frame.

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

选择2—— data.table

您可以使用 data.tableset,这样可以避免一些内部复制。

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

或者使用列号(如果有很多列,可能会更快) :

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

时机

# 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 is the quickest. Using sapply slows things down noticeably.

使用 sapplyis.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

或者,您可以使用(这是@mnel 的编辑) ,

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

速度要快得多。

使用 mapply[<-sapply稍微快一点。

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

根据 mnel 的数据,时间是

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

另一个解决办法:

    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

解决这个问题的方法很简单:

library(hablar)


dat %>% rationalize()

它返回一个包含所有 Inf 的数据帧,并将其转换为 NA。

代码: 图书馆 库(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))

结果:

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

Table 似乎比 hablar 更快,但语法更长。

您还可以使用方便的 place _ na 函数: Https://tidyr.tidyverse.org/reference/replace_na.html

下面是使用 Na _ if ()函数的 dplyr/tidyverse 解决方案:

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

注意,这只是用 NA 代替了正无穷大。需要重复如果负无穷大值也需要被替换。

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

冯迈在上面给出了一个简洁的答案,可以得到正面和负面的无穷大:

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

这个方法很有效,但是一个警告是不要交换 abs (.)在这里同时做两行,正如在一个反对意见中建议的那样。它将看起来像它的工作,但改变数据集中的所有负值为正!你可以用这个来确认:

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

有一句话是这么说的:

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

另外,如果有人需要信息源的坐标,可以这样做:

library(rlist)
list.clean(apply(df, 2, function(x){which(is.infinite(x))}), function(x) length(x) == 0L, TRUE)

结果:

$colname1
[1] row1 row2 ...
$colname2
[2] row1 row2 ...

有了这些信息,您就可以使用平均值、中位数或任何您想要的运算符来替换特定位置的 Inf 值。

例如(对于元素01) :

repInf = list.clean(apply(df, 2, function(x){which(is.infinite(x))}), function(x) length(x) == 0L, TRUE)
df[repInf[[1]], names(repInf)[[1]]] = median or mean(is.finite(df[ ,names(repInf)[[1]]]), na.rm = TRUE)

循环:

for (nonInf in 1:length(repInf)) {
df[repInf[[nonInf]], names(repInf)[[nonInf]]] = mean(is.finite(df[ , names(repInf)[[nonInf]]]))
}

在 dplyr 管道链中,您可以这样做。

%>% mutate_all(.,.funs = function(x){ifelse(is.infinite(x),NA,x)}) %>%

我觉得它简单,优雅,快捷。

已经有很多答案了,但是我想补充一点,这个 tidyverse解决方案对我来说一直都很有效:

%>% mutate_all(function(x) ifelse(is.nan(x) | is.infinite(x), NA, x)) %>%

插话说,我觉得这招挺管用的。

infNanReplace <- function (v, r = 0) {
v[!is.finite(v)] <- r
return(v)
}