在dplyr链中替换NA

41

问题已从原始版本进行了编辑

在阅读这个有趣的讨论之后,我想知道如何使用dplyr在列中替换缺失值,例如Lahman击球数据:

Source: local data frame [96,600 x 3]
Groups: teamID

   yearID teamID         G
1    2004    SFN        11
2    2006    CHN        43
3    2007    CHA         2
4    2008    BOS         5
5    2009    SEA         3
6    2010    SEA         4
7    2012    NYA        NA

以下的内容并不像我预期的那样起作用。
library(dplyr)
library(Lahman)

df <- Batting[ c("yearID", "teamID", "G") ]
df <- group_by(df, teamID )
df$G[is.na(df$G)] <- mean(df$G, na.rm = TRUE)

源代码:本地数据框[20 x 3] 分组:年份ID,团队ID
   yearID teamID         G
1    2004    SFN  11.00000
2    2006    CHN  43.00000
3    2007    CHA   2.00000
4    2008    BOS   5.00000
5    2009    SEA   3.00000
6    2010    SEA   4.00000
7    2012    NYA  **49.07894**

> mean(Batting$G_battin, na.rm = TRUE)
[1] **49.07894**

实际上,它归因于整体均值而不是组均值。在dplyr链中,你会如何做到这一点?使用基本R中的transform无法解决此问题,因为它归因于整体均值而不是组均值。此外,这种方法将数据转换为常规数据框。有没有更好的方法来解决这个问题?
df %.% 
  group_by( yearID ) %.%
  transform(G = ifelse(is.na(G), 
    mean(G, na.rm = TRUE), 
    G)
  )

编辑:将transform替换为mutate会导致以下错误。
Error in mutate_impl(.data, named_dots(...), environment()) : 
  INTEGER() can only be applied to a 'integer', not a 'double'

编辑:添加as.integer似乎解决了错误,并且确实产生了预期的结果。另请参阅@eddi的答案。
df %.% 
  group_by( teamID ) %.%
  mutate(G = ifelse(is.na(G), as.integer(mean(G, na.rm = TRUE)), G))

Source: local data frame [96,600 x 3]
Groups: teamID

   yearID teamID         G
1    2004    SFN        11
2    2006    CHN        43
3    2007    CHA         2
4    2008    BOS         5
5    2009    SEA         3
6    2010    SEA         4
7    2012    NYA        47

> mean_NYA <- mean(filter(df, teamID == "NYA")$G, na.rm = TRUE)
> as.integer(mean_NYA)
[1] 47

编辑:根据@Romain的评论,我从GitHub上安装了dplyr。
> head(df,10)
   yearID teamID         G
1    2004    SFN        11
2    2006    CHN        43
3    2007    CHA         2
4    2008    BOS         5
5    2009    SEA         3
6    2010    SEA         4
7    2012    NYA        NA
8    1954    ML1       122
9    1955    ML1       153
10   1956    ML1       153

> df %.% 
+   group_by(teamID)  %.%
+   mutate(G = ifelse(is.na(G), mean(G, na.rm = TRUE), G))
Source: local data frame [96,600 x 3]
Groups: teamID

   yearID teamID          G
1    2004    SFN          0
2    2006    CHN          0
3    2007    CHA          0
4    2008    BOS          0
5    2009    SEA          0
6    2010    SEA 1074266112
7    2012    NYA   90693125
8    1954    ML1        122
9    1955    ML1        153
10   1956    ML1        153
..    ...    ...        ...

所以我没有收到错误(很好),但是我得到了一个(看起来)奇怪的结果。

1
错误信息很令人困惑,但问题的根源在于ifelse模糊的语义。我已经添加了一个问题以确保我更加深入地思考它 https://github.com/hadley/dplyr/issues/254 - hadley
我无法使用dplyr的开发版本重现错误。 - Romain Francois
谢谢Hadley。@Romain,感谢您的建议。我安装了hadley/dplyr的主分支并获得了上面的结果。与您在问题#254中的结果不同。 - Vincent
2个回答

33
你遇到的主要问题是`mean`函数返回一个浮点数,而`G`列是整数。所以将`mean`函数用`as.integer`包裹起来就可以解决问题,或者你需要将整个列转换为`numeric`类型。
话虽如此,这里有几种`data.table`的替代方案 - 我没有检查哪个更快。
library(data.table)

# using ifelse
dt = data.table(a = 1:2, b = c(1,2,NA,NA,3,4,5,6,7,8))
dt[, b := ifelse(is.na(b), mean(b, na.rm = T), b), by = a]

# using a temporary column
dt = data.table(a = 1:2, b = c(1,2,NA,NA,3,4,5,6,7,8))
dt[, b.mean := mean(b, na.rm = T), by = a][is.na(b), b := b.mean][, b.mean := NULL]

这是我理想中想要做的事情(有关此事有一个FR):

# again, atm this is pure fantasy and will not work
dt[, b[is.na(b)] := mean(b, na.rm = T), by = a]

dplyr版本的ifelse(如同OP所述)是:
dt %>% group_by(a) %>% mutate(b = ifelse(is.na(b), mean(b, na.rm = T), b))

我不确定如何在一行中实现第二个data.table的想法在dplyr中。我也不确定如何阻止dplyr对数据进行混乱/排序(除了创建索引列)。

1
我认为出现了NaN,因为所有对应于该teamID, yearID的值都是NA - Arun
感谢 @eddi 提供的 data.table 替代方案。我真的得找出所有 [] 的工作原理(你的临时列选项)。 - Vincent
2
使用ifelsemutate正是我所需要的。但是,我能否在不显式命名列的情况下替换整个数据框中的所有NA值? - Bobby
你如何使用命令dt %.% group_by(a) %.% mutate(b = ifelse(is.na(b), mean(b, na.rm = T), b))来填充所有列,无论列名是什么? - hhh
7
@Bobby,你可以在dplyr中使用df %>% mutate_all(.funs = funs(ifelse(is.na(.), 0, .)))将数据框中的所有NA替换为0。或者只替换指定列(例如yearIDG_batting)的NA,可以使用df %>% mutate_at(.vars = vars(yearID, G_batting), .funs = funs(ifelse(is.na(.), 0, .))) - user2739472
显示剩余2条评论

0
使用现代的.by =参数和相对较新的if_else()函数:
Batting |>
  select(yearID, teamID, G) |>
  mutate(G = if_else(is.na(G), G, mean(G, na.rm = TRUE)), .by = teamID)

输出:

# A tibble: 112,184 × 3
   yearID teamID     G
    <int> <fct>  <dbl>
 1   1871 TRO    20.2 
 2   1871 RC1    20.5 
 3   1871 CL1    18.4 
 4   1871 WS3    13.3 
 5   1871 RC1    20.5 
 6   1871 FW1     9.67
 7   1871 RC1    20.5 
 8   1871 BS1    45.5 
 9   1871 FW1     9.67
10   1871 BS1    45.5 
# ℹ 112,174 more rows

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