根据特定规则替换 NA 值

4

我正在处理一个数据集,该数据集是基于从临床记录中收集的数据计算出来的分数。在某些情况下,这些数据被省略,因此无法计算分数,并将其记录为NA。

在某些情况下,我能够用之前的值替换NA值。这种方法的限制如下:

如果分数为NA,则检查前面和后面的值是否为NA。如果前面和后面的值都不是NA,则插入这些分数的平均值。

如果分数为NA,则检查前面和后面的值是否为NA。如果只有前面的值不是NA,则用前面的值替换第一个NA值。

如果有两个或更多连续的NA值,则仅替换第一个NA值,其他值保持为NA。

我尝试了zoo::na.locf()函数,但它会不加区别地替换所有NA值,或者仅限于替换大于一定数量的NA值的间隙。

我看了看tidy fill,但文档没有包括设置填充限制的任何内容。

以下是数据:

ID,episode,score
1,1,1
1,2,1
1,3,1
1,4,NA
1,5,NA
1,6,NA
1,7,2
1,8,NA
1,9,4
1,10,NA
2,1,NA
2,2,2
2,3,3
2,4,4
2,5,NA
2,6,NA
2,7,3
2,8,NA
2,9,NA
2,10,NA

我认为下面的嵌套ifelse mutate是正确的,但我缺少关于可用于限制我的替换为某些NA值数量的函数的知识。

data <- data %>%
group_by(ID) %>%
arrange(episode) %>%
mutate(score = ifelse(is.na(score) & lag(!is.na(score)) & lead(!is.na(score)), average(sum(lag(score),lead(score))),
    ifelse(is.na(score) & lag(!is.na(score)) & lead(is.na(score)), lag(score), ...) #And this is where I get stuck as I am unsure how to code for NA runs greater than 1

我期望的输出应该是:

ID,episode,score
1,1,1
1,2,1
1,3,1
1,4,*1
1,5,NA
1,6,NA
1,7,2
1,8,*3
1,9,4
1,10,*4
2,1,NA
2,2,2
2,3,3
2,4,4
2,5,*4
2,6,NA
2,7,3
2,8,*3
2,9,NA
2,10,NA

为了清晰表述从哪里复制的值,添加了 *s。

3个回答

2
如果我理解正确,针对每个ID,在列score中替换NA值的规则只有两个:
  1. 如果有单个NA值,则将其替换为前后(非NA值)之间的平均值。
  2. 如果有两个或更多连续的NA值,则仅将第一个NA值替换为前一个(非NA值),并将其他NA值保持不变。
这两个规则的实现归结为两个简单的mutate()语句: 首先,通过调用zoo::na.approx()和maxgap = 1L,根据规则1替换所有单个NA值。因此,仅剩下两个或两个以上的NA值序列(如果存在)。最后,通过if_else()和lag()将每个NA值替换为前面的值,以满足规则2。
library(dplyr)
data %>% 
  group_by(ID) %>% 
  mutate(new_score = zoo::na.approx(score, x = row_number(), maxgap = 1, na.rm = FALSE)) %>% 
  mutate(new_score = if_else(is.na(new_score), lag(new_score), new_score))
# A tibble: 20 x 4
# Groups:   ID [2]
      ID episode score new_score
   <dbl>   <dbl> <dbl>     <dbl>
 1     1       1     1         1
 2     1       2     1         1
 3     1       3     1         1
 4     1       4    NA         1
 5     1       5    NA        NA
 6     1       6    NA        NA
 7     1       7     2         2
 8     1       8    NA         3
 9     1       9     4         4
10     1      10    NA         4
11     2       1    NA        NA
12     2       2     2         2
13     2       3     3         3
14     2       4     4         4
15     2       5    NA         4
16     2       6    NA        NA
17     2       7     3         3
18     2       8    NA         3
19     2       9    NA        NA
20     2      10    NA        NA
请注意,这里创建了一个新列new_score以进行比较。
要替换score,请使用以下方法:
data %>% 
  group_by(ID) %>% 
  mutate(score = zoo::na.approx(score, x = row_number(), maxgap = 1, na.rm = FALSE)) %>% 
  mutate(score = if_else(is.na(score), lag(score), score))

数据

data <- readr::read_csv("ID,episode,score
1,1,1
1,2,1
1,3,1
1,4,NA
1,5,NA
1,6,NA
1,7,2
1,8,NA
1,9,4
1,10,NA
2,1,NA
2,2,2
2,3,3
2,4,4
2,5,NA
2,6,NA
2,7,3
2,8,NA
2,9,NA
2,10,NA")

2

从计算的角度来看,你可以将你的三个规则简化为一个复合条件:

如果 is.na(score[i]) && !is.na(score[i - 1]),即元素为 NA 且前一个元素不是 NA,则用其邻居的平均值替换每个 NA

为了使此方法起作用,您只需要将 na.rm = T 传递给 mean(),即 mean(x[(i-1):(i+1)], na.rm = T),您可以在 *apply 函数或 map 中使用它,就像下面我所做的一样。请注意,我还选择通过索引位置引用和分配值,而不是使用 leadlag,因为这会生成额外的向量。这可能不太令人兴奋,但也更有效率:

Original Answer 翻译成 "最初的回答"。

library(dplyr)
library(purrr)

mutate(df, score = map(seq_along(score),
                       ~ ifelse(
                           is.na(score[.]) && !is.na(score[. - 1]),
                           mean(score[(. - 1):(. + 1)], na.rm = T),
                           score[.]
                       )))

#### OUTPUT ####

   ID episode score
1   1       1     1
2   1       2     1
3   1       3     1
4   1       4     1
5   1       5    NA
6   1       6    NA
7   1       7     2
8   1       8     3
9   1       9     4
10  1      10     4
11  2       1    NA
12  2       2     2
13  2       3     3
14  2       4     4
15  2       5     4
16  2       6    NA
17  2       7     3
18  2       8     3
19  2       9    NA
20  2      10    NA

1
一个选项可能是:
library(dplyr)
data %>%
   group_by(ID) %>% 
  group_by(grp = cumsum(lead(is.na(score) & !is.na(lead(score) & 
      !is.na(lag(score)) ))), add = TRUE) %>% 
  mutate(score1 = if(n() == 3 & is.na(score[2]) & sum(is.na(score))== 1) 
    replace(score, is.na(score), mean(score, na.rm = TRUE)) else score) %>% 
  ungroup %>% 
  select(-grp) %>%
  mutate(score1 = coalesce(score1, lag(score1)))
# A tibble: 20 x 4
#      ID episode score score1
#   <int>   <int> <int>  <dbl>
# 1     1       1     1      1
# 2     1       2     1      1
# 3     1       3     1      1
# 4     1       4    NA      1
# 5     1       5    NA     NA
# 6     1       6    NA     NA
# 7     1       7     2      2
# 8     1       8    NA      3
# 9     1       9     4      4
#10     1      10    NA      4
#11     2       1    NA     NA
#12     2       2     2      2
#13     2       3     3      3
#14     2       4     4      4
#15     2       5    NA      4
#16     2       6    NA     NA
#17     2       7     3      3
#18     2       8    NA      3
#19     2       9    NA     NA
#20     2      10    NA     NA

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接