使用 dplyr 按组将 NA 替换为上一个或下一个值
Posted
技术标签:
【中文标题】使用 dplyr 按组将 NA 替换为上一个或下一个值【英文标题】:Replace NA with previous or next value, by group, using dplyr 【发布时间】:2017-02-23 17:57:41 【问题描述】:我有一个按日期降序排列的数据框。
ps1 = data.frame(userID = c(21,21,21,22,22,22,23,23,23),
color = c(NA,'blue','red','blue',NA,NA,'red',NA,'gold'),
age = c('3yrs','2yrs',NA,NA,'3yrs',NA,NA,'4yrs',NA),
gender = c('F',NA,'M',NA,NA,'F','F',NA,'F')
)
我希望用以前的值估算(替换)NA 值 并按用户 ID 分组 如果用户 ID 的第一行有 NA,则替换为该用户 ID 组的下一组值。
我正在尝试使用类似这样的 dplyr 和 zoo 包...但它不起作用
cleanedFUG <- filteredUserGroup %>%
group_by(UserID) %>%
mutate(Age1 = na.locf(Age),
Color1 = na.locf(Color),
Gender1 = na.locf(Gender) )
我需要这样的结果 df:
userID color age gender
1 21 blue 3yrs F
2 21 blue 2yrs F
3 21 red 2yrs M
4 22 blue 3yrs F
5 22 blue 3yrs F
6 22 blue 3yrs F
7 23 red 4yrs F
8 23 red 4yrs F
9 23 gold 4yrs F
【问题讨论】:
看看这是否有帮助。 Replacing NAs with latest non-NA value 或这个replace NA value with the group value 【参考方案1】:require(tidyverse) #fill is part of tidyr
ps1 %>%
group_by(userID) %>%
fill(color, age, gender) %>% #default direction down
fill(color, age, gender, .direction = "up")
这给了你:
Source: local data frame [9 x 4]
Groups: userID [3]
userID color age gender
<dbl> <fctr> <fctr> <fctr>
1 21 blue 3yrs F
2 21 blue 2yrs F
3 21 red 2yrs M
4 22 blue 3yrs F
5 22 blue 3yrs F
6 22 blue 3yrs F
7 23 red 4yrs F
8 23 red 4yrs F
9 23 gold 4yrs F
【讨论】:
fill()
函数已更新为允许同时填充两个方向,而无需使用两次。 .direction
选项现在包括 downup
和 updown
。
如果你的数据集很大并且你不能写每个列的名字怎么办
@Amit 你可以使用 fill(everything()) 来填充所有列【参考方案2】:
无论userID
组如何,直接在整个data.frame 上使用zoo::na.locf
都会填充NA。不幸的是,包 dplyr 的分组对 na.locf
函数没有影响,这就是我选择拆分的原因:
library(dplyr); library(zoo)
ps1 %>% split(ps1$userID) %>%
lapply(function(x) na.locf(na.locf(x), fromLast=T)) %>%
do.call(rbind, .)
#### userID color age gender
#### 21.1 21 blue 3yrs F
#### 21.2 21 blue 2yrs F
#### 21.3 21 red 2yrs M
#### 22.4 22 blue 3yrs F
#### 22.5 22 blue 3yrs F
#### 22.6 22 blue 3yrs F
#### 23.7 23 red 4yrs F
#### 23.8 23 red 4yrs F
#### 23.9 23 gold 4yrs F
它的作用是首先将数据拆分为 3 个 data.frame,然后我应用第一次插补(向下),然后使用 lapply
中的匿名函数向上,最终使用 rbind
带来data.frames 重新组合在一起。你有预期的输出。
【讨论】:
您可以将do.call()
替换为更惯用的bind_rows()
,将split(ps1$userID)
替换为split(.$userID)
使用purrr
的另一种选择也可以是:library(purrr); ps1 %>% slice_rows("userID") %>% by_slice(function(x) na.locf(na.locf(x), fromLast=T) , .collate = "rows")
@StevenBeaupré 不错!这本身就值得一个新的答案;-)【参考方案3】:
我写了这个函数,它肯定比 fill 快,可能比 na.locf 快:
fill_NA <- function(x)
which.na <- c(which(!is.na(x)), length(x) + 1)
values <- na.omit(x)
if (which.na[1] != 1)
which.na <- c(1, which.na)
values <- c(values[1], values)
diffs <- diff(which.na)
return(rep(values, times = diffs))
【讨论】:
感谢您分享您的代码,该代码独立于分组变量(我的数据中没有)并且不需要安装 zoo 包。 除了我一直在使用 tidyr,我发现 tidyr::fill 使用管道更优雅地完成了这项工作,而无需使用 mutate()。【参考方案4】:将@agenis 方法与na.locf()
结合使用purrr
,您可以这样做:
library(purrr)
library(zoo)
ps1 %>%
slice_rows("userID") %>%
by_slice(function(x)
na.locf(na.locf(x), fromLast=T) ,
.collate = "rows")
【讨论】:
【参考方案5】:几年后,我发现情况发生了变化。 使用@Steven Beaupré 的方法,
1) 添加na.rm=F
确保不会删除/排除任何行。
2)slide_rows()
函数可以在purrrlyr
包中找到。
library(purrrlyr)
library(zoo)
ps1 %>%
slice_rows("userID") %>%
by_slice(function(x)
na.locf(na.locf(x, na.rm=F), fromLast=T, na.rm=F) ,
.collate = "rows")
【讨论】:
以上是关于使用 dplyr 按组将 NA 替换为上一个或下一个值的主要内容,如果未能解决你的问题,请参考以下文章