data.frame
)的简单示例:> y <- c(NA, 2, 2, NA, NA, 3, NA, 4, NA, NA)
我想要一个函数
fill.NAs()
,它允许我构造yy
,使得: /> > yy
[1] NA NA NA 2 2 2 2 3 3 3 4 4
我需要对许多(总计〜1 Tb)小型
data.frame
s(〜30-50 Mb)重复此操作,其中所有行都为NA是。解决这个问题的好方法是什么? 我煮的丑陋溶液使用此功能:
last <- function (x){
x[length(x)]
}
fill.NAs <- function(isNA){
if (isNA[1] == 1) {
isNA[1:max({which(isNA==0)[1]-1},1)] <- 0 # first is NAs
# can't be forward filled
}
isNA.neg <- isNA.pos <- isNA.diff <- diff(isNA)
isNA.pos[isNA.diff < 0] <- 0
isNA.neg[isNA.diff > 0] <- 0
which.isNA.neg <- which(as.logical(isNA.neg))
if (length(which.isNA.neg)==0) return(NULL) # generates warnings later, but works
which.isNA.pos <- which(as.logical(isNA.pos))
which.isNA <- which(as.logical(isNA))
if (length(which.isNA.neg)==length(which.isNA.pos)){
replacement <- rep(which.isNA.pos[2:length(which.isNA.neg)],
which.isNA.neg[2:max(length(which.isNA.neg)-1,2)] -
which.isNA.pos[1:max(length(which.isNA.neg)-1,1)])
replacement <- c(replacement, rep(last(which.isNA.pos), last(which.isNA) - last(which.isNA.pos)))
} else {
replacement <- rep(which.isNA.pos[1:length(which.isNA.neg)], which.isNA.neg - which.isNA.pos[1:length(which.isNA.neg)])
replacement <- c(replacement, rep(last(which.isNA.pos), last(which.isNA) - last(which.isNA.pos)))
}
replacement
}
函数
fill.NAs
的用法如下:y <- c(NA, 2, 2, NA, NA, 3, NA, 4, NA, NA)
isNA <- as.numeric(is.na(y))
replacement <- fill.NAs(isNA)
if (length(replacement)){
which.isNA <- which(as.logical(isNA))
to.replace <- which.isNA[which(isNA==0)[1]:length(which.isNA)]
y[to.replace] <- y[replacement]
}
输出
> y
[1] NA 2 2 2 2 3 3 3 4 4 4
...似乎有效。但是,伙计,这是丑陋的!有什么建议吗?
#1 楼
您可能希望使用zoo软件包中的na.locf()
函数来进行最后的观察以替换您的NA值。 这是帮助页面上其用法示例的开头:
library(zoo)
az <- zoo(1:6)
bz <- zoo(c(2,NA,1,4,5,2))
na.locf(bz)
1 2 3 4 5 6
2 2 1 4 5 2
na.locf(bz, fromLast = TRUE)
1 2 3 4 5 6
2 1 1 4 5 2
cz <- zoo(c(NA,9,3,2,3,2))
na.locf(cz)
2 3 4 5 6
9 3 2 3 2
评论
另请注意,动物园中的na.locf可与普通矢量以及动物园对象一起使用。其na.rm参数在某些应用程序中可能很有用。
– G. Grothendieck
16年11月11日13:37
使用na.locf(cz,na.rm = FALSE)保持领先的NA。
– BallpointBen
18年5月17日在16:21
@BallpointBen的评论很重要,应该包含在答案中。谢谢!
–本
3月6日下午16:43
#2 楼
对不起,我发现了一个老问题。我无法在火车上查找完成此功能的功能,所以我自己写了一个信。
我很自豪地发现它的速度要快一点。
虽然灵活性较差。
但是它与
ave
配合使用,这正是我所需要的。repeat.before = function(x) { # repeats the last non NA value. Keeps leading NA
ind = which(!is.na(x)) # get positions of nonmissing values
if(is.na(x[1])) # if it begins with a missing, add the
ind = c(1,ind) # first position to the indices
rep(x[ind], times = diff( # repeat the values at these indices
c(ind, length(x) + 1) )) # diffing the indices + length yields how often
} # they need to be repeated
x = c(NA,NA,'a',NA,NA,NA,NA,NA,NA,NA,NA,'b','c','d',NA,NA,NA,NA,NA,'e')
xx = rep(x, 1000000)
system.time({ yzoo = na.locf(xx,na.rm=F)})
## user system elapsed
## 2.754 0.667 3.406
system.time({ yrep = repeat.before(xx)})
## user system elapsed
## 0.597 0.199 0.793
编辑
由于这是我最不赞成的答案,所以经常提醒我不要使用自己的函数,因为我经常需要使用Zoo的
maxgap
参数。因为当我使用无法调试的dplyr +日期时,zoo在某些极端情况下会出现一些奇怪的问题,所以今天我回到了这一点来改进我的旧功能。我对改进后的功能和所有其他条目在这里。对于基本功能集,
tidyr::fill
是最快的,同时也不会使边缘情况失败。 @BrandonBertelsen的Rcpp条目仍然更快,但是对于输入的类型却不灵活(由于对all.equal
的误解,他对边缘情况进行了错误的测试)。如果需要
maxgap
,我的以下函数比动物园(日期没有奇怪的问题)。 我放置了测试文档。
新功能
repeat_last = function(x, forward = TRUE, maxgap = Inf, na.rm = FALSE) {
if (!forward) x = rev(x) # reverse x twice if carrying backward
ind = which(!is.na(x)) # get positions of nonmissing values
if (is.na(x[1]) && !na.rm) # if it begins with NA
ind = c(1,ind) # add first pos
rep_times = diff( # diffing the indices + length yields how often
c(ind, length(x) + 1) ) # they need to be repeated
if (maxgap < Inf) {
exceed = rep_times - 1 > maxgap # exceeding maxgap
if (any(exceed)) { # any exceed?
ind = sort(c(ind[exceed] + 1, ind)) # add NA in gaps
rep_times = diff(c(ind, length(x) + 1) ) # diff again
}
}
x = rep(x[ind], times = rep_times) # repeat the values at these indices
if (!forward) x = rev(x) # second reversion
x
}
我也放置了函数在我的formr程序包中(仅限Github)。
评论
+1,但我想如果要将其应用于具有多列的df,则需要按列循环此操作?
–朱巴布
14-10-6在16:11
@Ruben再次感谢您的报告。现在,该错误已在R-Forge上修复。另外,我还调整并导出了主力函数na.locf0,该函数现在在范围和性能上类似于您的repeat_last函数。提示是使用diff而不是cumsum并避免ifelse。主要的na.locf.default函数仍然稍慢一些,因为它执行更多检查并处理多个列等。
– Achim Zeileis
17 Mar 2 '17 at 15:28
#3 楼
一个data.table
解决方案:dt <- data.table(y = c(NA, 2, 2, NA, NA, 3, NA, 4, NA, NA))
dt[, y_forward_fill := y[1], .(cumsum(!is.na(y)))]
dt
y y_forward_fill
1: NA NA
2: 2 2
3: 2 2
4: NA 2
5: NA 2
6: 3 3
7: NA 3
8: 4 4
9: NA 4
10: NA 4
此方法也可以与正向填充零一起使用:
dt <- data.table(y = c(0, 2, -2, 0, 0, 3, 0, -4, 0, 0))
dt[, y_forward_fill := y[1], .(cumsum(y != 0))]
dt
y y_forward_fill
1: 0 0
2: 2 2
3: -2 -2
4: 0 -2
5: 0 -2
6: 3 3
7: 0 3
8: -4 -4
9: 0 -4
10: 0 -4
此该方法对于大规模数据以及您希望按组进行正向填充非常有用,这对于
data.table
来说是微不足道的。只需将组添加到by
逻辑之前的cumsum
子句中。dt <- data.table(group = sample(c('a', 'b'), 20, replace = TRUE), y = sample(c(1:4, rep(NA, 4)), 20 , replace = TRUE))
dt <- dt[order(group)]
dt[, y_forward_fill := y[1], .(group, cumsum(!is.na(y)))]
dt
group y y_forward_fill
1: a NA NA
2: a NA NA
3: a NA NA
4: a 2 2
5: a NA 2
6: a 1 1
7: a NA 1
8: a 3 3
9: a NA 3
10: a NA 3
11: a 4 4
12: a NA 4
13: a 1 1
14: a 4 4
15: a NA 4
16: a 3 3
17: b 4 4
18: b NA 4
19: b NA 4
20: b 2 2
评论
按组执行此操作的功能非常棒!
– JCWong
4月1日17:54
我对tidyverse很熟悉,但对data.table还是陌生的-请问这是做什么的? dt [,y_forward_fill:= y [1],。(cumsum(!is.na(y)))]具体来说,y [1]以及为什么。(cumsum(!is.na(y)))正向填充NA ?
–德斯蒙德
10月25日13:39
data.table语法以dt [i,j,by]的形式表示。简介小插图非常好。如果您来自管道世界,这确实需要一些习惯。
–托尼·迪弗兰科(Tony DiFranco)
10月27日17:34
#4 楼
为了处理更大的数据量,为了提高效率,我们可以使用data.table包。require(data.table)
replaceNaWithLatest <- function(
dfIn,
nameColNa = names(dfIn)[1]
){
dtTest <- data.table(dfIn)
setnames(dtTest, nameColNa, "colNa")
dtTest[, segment := cumsum(!is.na(colNa))]
dtTest[, colNa := colNa[1], by = "segment"]
dtTest[, segment := NULL]
setnames(dtTest, "colNa", nameColNa)
return(dtTest)
}
评论
可以添加一个lapply,以便将其直接应用于多个NA列:replaceNaWithLatest <-function(dfIn,nameColsNa = names(dfIn)[1]){dtTest <-data.table(dfIn)invisible(lapply(nameColsNa,function (nameColNa){setnames(dtTest,nameColNa,“ colNa”)dtTest [,segment:= cumsum(!is.na(colNa))]] dtTest [,colNa:= colNa [1],通过=“ segment”] dtTest [ ,segment:= NULL] setnames(dtTest,“ colNa”,nameColNa)}))return(dtTest)}
–xclotet
17年1月10日在17:28
刚开始,我对这种解决方案感到兴奋,但实际上它根本没有做相同的事情。问题是关于用另一个填充一个数据集。这个答案只是推论。
– Hack-R
18年6月10日在20:10
#5 楼
戴上帽子:library(Rcpp)
cppFunction('IntegerVector na_locf(IntegerVector x) {
int n = x.size();
for(int i = 0; i<n; i++) {
if((i > 0) && (x[i] == NA_INTEGER) & (x[i-1] != NA_INTEGER)) {
x[i] = x[i-1];
}
}
return x;
}')
设置基本样本和基准:
x <- sample(c(1,2,3,4,NA))
bench_em <- function(x,count = 10) {
x <- sample(x,count,replace = TRUE)
print(microbenchmark(
na_locf(x),
replace_na_with_last(x),
na.lomf(x),
na.locf(x),
repeat.before(x)
), order = "mean", digits = 1)
}
并运行一些基准:
bench_em(x,1e6)
Unit: microseconds
expr min lq mean median uq max neval
na_locf(x) 697 798 821 814 821 1e+03 100
na.lomf(x) 3511 4137 5002 4214 4330 1e+04 100
replace_na_with_last(x) 4482 5224 6473 5342 5801 2e+04 100
repeat.before(x) 4793 5044 6622 5097 5520 1e+04 100
na.locf(x) 12017 12658 17076 13545 19193 2e+05 100
以防万一:
all.equal(
na_locf(x),
replace_na_with_last(x),
na.lomf(x),
na.locf(x),
repeat.before(x)
)
[1] TRUE
更新
数字矢量,其功能略有不同:
NumericVector na_locf_numeric(NumericVector x) {
int n = x.size();
LogicalVector ina = is_na(x);
for(int i = 1; i<n; i++) {
if((ina[i] == TRUE) & (ina[i-1] != TRUE)) {
x[i] = x[i-1];
}
}
return x;
}
#6 楼
这对我有用: replace_na_with_last<-function(x,a=!is.na(x)){
x[which(a)[c(1,1:sum(a))][cumsum(a)+1]]
}
> replace_na_with_last(c(1,NA,NA,NA,3,4,5,NA,5,5,5,NA,NA,NA))
[1] 1 1 1 1 3 4 5 5 5 5 5 5 5 5
> replace_na_with_last(c(NA,"aa",NA,"ccc",NA))
[1] "aa" "aa" "aa" "ccc" "ccc"
速度也很合理:
> system.time(replace_na_with_last(sample(c(1,2,3,NA),1e6,replace=TRUE)))
user system elapsed
0.072 0.000 0.071
评论
当存在领先的NA时,此功能无法满足您的期望。 replace_na_with_last(c(NA,1:4,NA))(即,它们用以下值填充)。这也是imputeTS :: na.locf(x,na.remaining =“ rev”)的默认行为。
–鲁本
17年1月12日在18:32
最好为这种情况添加默认值,方法略有不同:replace_na_with_last <-function(x,p = is.na,d = 0)c(d,x)[cummax(seq_along(x)*(!p(x) ))+ 1]
–尼克·纳苏菲斯(Nick Nassuphis)
1月16日23:44
@NickNassuphis的回答简短,甜美,不依赖于软件包,并且与dplyr管道一起使用效果很好!
–金
5月26日8:34
#7 楼
您可以使用data.table
的nafill
函数data.table >= 1.12.3
。library(data.table)
nafill(y, type = "locf")
# [1] NA 2 2 2 2 3 3 4 4 4
如果向量是
data.table
中的列,则还可以通过引用setnafill
对其进行更新:d <- data.table(x = 1:10, y)
setnafill(d, type = "locf", cols = "y")
d
# x y
# 1: 1 NA
# 2: 2 2
# 3: 3 2
# 4: 4 2
# 5: 5 2
# 6: 6 3
# 7: 7 3
# 8: 8 4
# 9: 9 4
# 10: 10 4
如果在几列中都有
NA
... d <- data.table(x = c(1, NA, 2), y = c(2, 3, NA), z = c(4, NA, 5))
# x y z
# 1: 1 2 4
# 2: NA 3 NA
# 3: 2 NA 5
...您可以通过引用一次性填写它们:
setnafill(d, type = "locf")
d
# x y z
# 1: 1 2 4
# 2: 1 3 4
# 3: 2 3 5
请注意:
仅双精度和整数当前支持[
data.table 1.12.6
] 数据类型。
功能很可能会很快扩展;请参见请参阅未解决的问题nafill,针对字符,因子和其他类型的setnafill,在这里您还可以找到临时的解决方法。
#8 楼
试试这个功能。它不需要ZOO软件包:# last observation moved forward
# replaces all NA values with last non-NA values
na.lomf <- function(x) {
na.lomf.0 <- function(x) {
non.na.idx <- which(!is.na(x))
if (is.na(x[1L])) {
non.na.idx <- c(1L, non.na.idx)
}
rep.int(x[non.na.idx], diff(c(non.na.idx, length(x) + 1L)))
}
dim.len <- length(dim(x))
if (dim.len == 0L) {
na.lomf.0(x)
} else {
apply(x, dim.len, na.lomf.0)
}
}
示例:
> # vector
> na.lomf(c(1, NA,2, NA, NA))
[1] 1 1 2 2 2
>
> # matrix
> na.lomf(matrix(c(1, NA, NA, 2, NA, NA), ncol = 2))
[,1] [,2]
[1,] 1 2
[2,] 1 2
[3,] 1 2
评论
要改善它,您可以添加以下内容:if(!anyNA(x))return(x)。
– Artem Klevtsov
18年5月27日在5:15
#9 楼
有一个领先的NA
有点皱纹,但是我发现在不缺少该主要术语的情况下进行LOCF的一种非常易读(和矢量化)的方法是:>可读性较低的修改通常可以正常工作:
na.omit(y)[cumsum(!is.na(y))]
提供所需的输出:
c(NA, na.omit(y))[cumsum(!is.na(y))+1]
评论
这是相当优雅的。不知道它是否在所有情况下都有效,但是对我来说肯定有效!
– ABT
18年2月5日在7:31
#10 楼
tidyverse软件包提出了一种简单的方法:y = c(NA, 2, 2, NA, NA, 3, NA, 4, NA, NA)
# first, transform it into a data.frame
y = as.data.frame(y)
y
1 NA
2 2
3 2
4 NA
5 NA
6 3
7 NA
8 4
9 NA
10 NA
fill(y, y, .direction = 'down')
y
1 NA
2 2
3 2
4 2
5 2
6 3
7 3
8 4
9 4
10 4
#11 楼
有很多软件包提供na.locf
(NA
最后观察到结转)功能:xts
-xts::na.locf
zoo
- zoo::na.locf
imputeTS
-imputeTS::na.locf
spacetime
-spacetime::na.locf
以及具有此功能的其他软件包命名不同。
#12 楼
跟进Brandon Bertelsen的Rcpp贡献。对我而言,NumericVector版本不起作用:它仅替换了第一个NA。这是因为ina
向量在函数的开头仅被评估一次。相反,可以采用与IntegerVector函数完全相同的方法。以下对我有用:
library(Rcpp)
cppFunction('NumericVector na_locf_numeric(NumericVector x) {
R_xlen_t n = x.size();
for(R_xlen_t i = 0; i<n; i++) {
if(i > 0 && !R_finite(x[i]) && R_finite(x[i-1])) {
x[i] = x[i-1];
}
}
return x;
}')
如果您需要CharacterVector版本,则相同的基本方法也适用:
cppFunction('CharacterVector na_locf_character(CharacterVector x) {
R_xlen_t n = x.size();
for(R_xlen_t i = 0; i<n; i++) {
if(i > 0 && x[i] == NA_STRING && x[i-1] != NA_STRING) {
x[i] = x[i-1];
}
}
return x;
}')
评论
int n = x.size()和for(int i = 0; i
–斯特芬·莫里茨(Steffen Moritz)
17年3月18日在21:56
看起来此函数返回“ R_xlen_t”。如果R是在长向量支持下编译的,则定义为ptrdiff_t;如果不是,则为整数。感谢您的指正!
– Evan Cortens
17年3月19日在22:38
#13 楼
这是@AdamO解决方案的修改。由于它绕过了na.omit
函数,因此运行速度更快。这将覆盖向量NA
中的y
值(前导NA
除外)。 z <- !is.na(y) # indicates the positions of y whose values we do not want to overwrite
z <- z | !cumsum(z) # for leading NA's in y, z will be TRUE, otherwise it will be FALSE where y has a NA and TRUE where y does not have a NA
y <- y[z][cumsum(z)]
#14 楼
我想添加一个使用runner
r cran软件包的下一个解决方案。 library(runner)
y <- c(NA, 2, 2, NA, NA, 3, NA, 4, NA, NA)
fill_run(y, FALSE)
[1] NA 2 2 2 2 3 3 4 4 4
整个软件包都经过了优化并且是主要的用cpp编写。因此提供了很大的效率。
#15 楼
我个人使用此功能。我不知道它有多快。但这无需使用库即可完成它的工作。replace_na_with_previous<-function (vector) {
if (is.na(vector[1]))
vector[1] <- na.omit(vector)[1]
for (i in 1:length(vector)) {
if ((i - 1) > 0) {
if (is.na(vector[i]))
vector[i] <- vector[i - 1]
}
}
return(vector)
}
如果要在数据框中应用此功能,如果您的数据框名为df,则只需
df[]<-lapply(df,replace_na_with_previous)
#16 楼
我尝试了以下操作:nullIdx <- as.array(which(is.na(masterData$RequiredColumn)))
masterData$RequiredColumn[nullIdx] = masterData$RequiredColumn[nullIdx-1]
nullIdx在任何masterData $ RequiredColumn具有Null / NA值的地方获取idx号。
在下一行中,我们将其替换具有相应的Idx-1值,即每个NULL / NA之前的最后一个好值
评论
如果存在多个连续的缺失值,则此方法不起作用-1 NA NA变成1 1 NA。另外,我认为as.array()是不必要的。
–格雷戈·托马斯(Gregor Thomas)
17年3月19日在23:12
#17 楼
这对我有用,尽管我不确定它是否比其他建议更有效。rollForward <- function(x){
curr <- 0
for (i in 1:length(x)){
if (is.na(x[i])){
x[i] <- curr
}
else{
curr <- x[i]
}
}
return(x)
}
#18 楼
fill.NAs <- function(x) {is_na<-is.na(x); x[Reduce(function(i,j) if (is_na[j]) i else j, seq_len(length(x)), accumulate=T)]}
fill.NAs(c(NA, 2, 2, NA, NA, 3, NA, 4, NA, NA))
[1] NA 2 2 2 2 3 3 4 4 4
Reduce是一个很好的函数式编程概念,可能对类似的任务很有用。不幸的是,在R中它比上述答案中的
repeat.before
慢约70倍。
评论
从这之后的其他问题来看,我认为您现在在data.table中发现roll = TRUE。正在引入一种新方法来填充R
此外,请查看tidyr :: fill()。
另请参阅:stackoverflow.com/questions/12607465/…