按组高效地填充 NA
Efficiently fill NAs by group
我有一个数据集,我在其中观察了一些人的变量而不是其他人的变量。对于我观察变量的那些人,我只观察一次。但是,每个人的观察次数以及观察值的位置各不相同。
我想用非 NA 值填充给定个体的所有 NA 值,以防存在非 NA 值。否则,NA 应该保持为 NA。
这是一个示例数据集:
#data.frame of 100 individuals with 10 observations each
data <- data.frame(group = rep(1:100,each=10),value = NA)
#first 50 individuals get a value at the fifth observation, others don't have value
data$value[seq(5,500,10)] <- rnorm(50)
到目前为止一切顺利,问题不大。取自另一个线程,我们可以使用 dplyr
和 tidyr
:
做这样的事情
data <- data %>%
group_by(group) %>% #by group
fill(value) %>% #default direction down
fill(value, .direction = "up") #also fill NAs upwards
这很好地解决了问题。但是,我必须为大约 80mio 执行此操作。观察,这需要几个小时。有没有更快的方法可用?我认为 data.table
可能是一个不错的候选人。
如果可以调整方法以仅填充出现在值之前的 NA,那也很棒。
谢谢!
我们可以使用data.table
就地赋值。这里,zoo
中的na.locf
用于用相邻的非NA元素
填充NA元素
library(data.table)
library(zoo)
setDT(data)[, value := na.locf(na.locf(value, na.rm = FALSE), fromLast = TRUE), group]
基准
set.seed(24)
data1 <- data.frame(group = rep(1:1e6,each=10),value = NA)
data1$value[seq(5,1e6,10)] <- rnorm(100000)
data2 <- copy(data1)
system.time({setDT(data2)[, value := na.locf(na.locf(value,
na.rm = FALSE), fromLast = TRUE), group]})
# user system elapsed
# 70.681 0.294 70.917
system.time({
data1 %>%
group_by(group) %>% #by group
fill(value) %>% #default direction down
fill(value, .direction = "up")
})
# 17% ~33 m remaining
注意:这花了很多时间。所以必须中止会话。
NOTE2 : 这种方法是基于这样的假设,即我们想用非 NA 相邻元素替换 NA 元素,并且每组有一个以上的非 NA 元素
您可以对 data.table 和 dplyr 使用一种非常简单的方法,我相信这将非常快速和高效:
在data.table中:
library(data.table)
setDT(data)
data[, value := value[!is.na(value)][1L], by = group]
或 dplyr:
library(dplyr)
data <- data %>%
group_by(group) %>%
mutate(value = value[!is.na(value)][1L])
关键是你有一个非 NA 值每组恰好 o 或 1 次。因此,您不需要最后观察结转逻辑。取第一个非 NA 值(如果存在)。
这是我用过的代码:你的代码对比 akrun 对比我的。有时动物园不是最快的过程,但它是最干净的。反正你可以试试。
更新:
它已经用更多数据 (100.000) 进行了测试,到目前为止,进程 03(子集和合并)获胜。
最后更新
与rbenchmark的功能对比:
library(dplyr)
library(tidyr)
library(base)
library(data.table)
library(zoo)
library(rbenchmark)
#data.frame of 100 individuals with 10 observations each
data <- data.frame(group = rep(1:10000,each=10),value = NA)
data$value[seq(5,5000,10)] <- rnorm(50) #first 50 individuals get a value at the fifth observation, others don't have value
#Process01
P01 <- function (data){
data01 <- data %>%
group_by(group) %>% #by group
fill(value) %>% #default direction down
fill(value, .direction = "up") #also fill NAs upwards
return(data01)
}
#Process02
P02 <- function (data){
data02 <- setDT(data)[, value := na.locf(na.locf(value, na.rm = FALSE),
fromLast = TRUE), group]
return(data02)
}
#Process03
P03 <- function (data){
dataU <- subset(unique(data), value!='NA') #keep row number
dataM <- merge(data, dataU, by = "group", all=T) #merge tables
data03 <- data.frame(group=dataM$group, value = dataM$value.y) #idem shape of data
return(data03)
}
benchmark("P01_dplyr" = {data01 <- P01(data)},
"P02_zoo" = {data02 <- P02(data)},
"P03_data.table" = {data03 <- P03(data)},
replications = 10,
columns = c("test", "replications", "elapsed")
)
数据=10.000、10 次重复和 I5 7400 的结果:
test replications elapsed
1 P01_dplyr 10 257.78
2 P02_zoo 10 10.35
3 P03_data.table 10 0.09
我有一个数据集,我在其中观察了一些人的变量而不是其他人的变量。对于我观察变量的那些人,我只观察一次。但是,每个人的观察次数以及观察值的位置各不相同。
我想用非 NA 值填充给定个体的所有 NA 值,以防存在非 NA 值。否则,NA 应该保持为 NA。
这是一个示例数据集:
#data.frame of 100 individuals with 10 observations each
data <- data.frame(group = rep(1:100,each=10),value = NA)
#first 50 individuals get a value at the fifth observation, others don't have value
data$value[seq(5,500,10)] <- rnorm(50)
到目前为止一切顺利,问题不大。取自另一个线程,我们可以使用 dplyr
和 tidyr
:
data <- data %>%
group_by(group) %>% #by group
fill(value) %>% #default direction down
fill(value, .direction = "up") #also fill NAs upwards
这很好地解决了问题。但是,我必须为大约 80mio 执行此操作。观察,这需要几个小时。有没有更快的方法可用?我认为 data.table
可能是一个不错的候选人。
如果可以调整方法以仅填充出现在值之前的 NA,那也很棒。
谢谢!
我们可以使用data.table
就地赋值。这里,zoo
中的na.locf
用于用相邻的非NA元素
library(data.table)
library(zoo)
setDT(data)[, value := na.locf(na.locf(value, na.rm = FALSE), fromLast = TRUE), group]
基准
set.seed(24)
data1 <- data.frame(group = rep(1:1e6,each=10),value = NA)
data1$value[seq(5,1e6,10)] <- rnorm(100000)
data2 <- copy(data1)
system.time({setDT(data2)[, value := na.locf(na.locf(value,
na.rm = FALSE), fromLast = TRUE), group]})
# user system elapsed
# 70.681 0.294 70.917
system.time({
data1 %>%
group_by(group) %>% #by group
fill(value) %>% #default direction down
fill(value, .direction = "up")
})
# 17% ~33 m remaining
注意:这花了很多时间。所以必须中止会话。
NOTE2 : 这种方法是基于这样的假设,即我们想用非 NA 相邻元素替换 NA 元素,并且每组有一个以上的非 NA 元素
您可以对 data.table 和 dplyr 使用一种非常简单的方法,我相信这将非常快速和高效:
在data.table中:
library(data.table)
setDT(data)
data[, value := value[!is.na(value)][1L], by = group]
或 dplyr:
library(dplyr)
data <- data %>%
group_by(group) %>%
mutate(value = value[!is.na(value)][1L])
关键是你有一个非 NA 值每组恰好 o 或 1 次。因此,您不需要最后观察结转逻辑。取第一个非 NA 值(如果存在)。
这是我用过的代码:你的代码对比 akrun 对比我的。有时动物园不是最快的过程,但它是最干净的。反正你可以试试。
更新: 它已经用更多数据 (100.000) 进行了测试,到目前为止,进程 03(子集和合并)获胜。
最后更新 与rbenchmark的功能对比:
library(dplyr)
library(tidyr)
library(base)
library(data.table)
library(zoo)
library(rbenchmark)
#data.frame of 100 individuals with 10 observations each
data <- data.frame(group = rep(1:10000,each=10),value = NA)
data$value[seq(5,5000,10)] <- rnorm(50) #first 50 individuals get a value at the fifth observation, others don't have value
#Process01
P01 <- function (data){
data01 <- data %>%
group_by(group) %>% #by group
fill(value) %>% #default direction down
fill(value, .direction = "up") #also fill NAs upwards
return(data01)
}
#Process02
P02 <- function (data){
data02 <- setDT(data)[, value := na.locf(na.locf(value, na.rm = FALSE),
fromLast = TRUE), group]
return(data02)
}
#Process03
P03 <- function (data){
dataU <- subset(unique(data), value!='NA') #keep row number
dataM <- merge(data, dataU, by = "group", all=T) #merge tables
data03 <- data.frame(group=dataM$group, value = dataM$value.y) #idem shape of data
return(data03)
}
benchmark("P01_dplyr" = {data01 <- P01(data)},
"P02_zoo" = {data02 <- P02(data)},
"P03_data.table" = {data03 <- P03(data)},
replications = 10,
columns = c("test", "replications", "elapsed")
)
数据=10.000、10 次重复和 I5 7400 的结果:
test replications elapsed
1 P01_dplyr 10 257.78
2 P02_zoo 10 10.35
3 P03_data.table 10 0.09