【问题标题】:Find rows with incomplete set depending on a factor, then replace values that exist by NA for the incomplete set根据一个因素查找具有不完整集合的行,然后用 NA 替换不完整集合中存在的值
【发布时间】:2023-12-24 06:42:01
【问题描述】:

我无法解决这个问题。

我有一个不完整的数据集(许多行和变量),其中一个因素指定所有其他变量是预先还是后期。我需要获取所有变量前后的汇总统计数据,仅包括前后值不为 NA 的行。

如果每个变量的集合分别不完整,我正在尝试找到一种方法用 NA 替换现有值。

以下是我想要实现的一个简单示例:

  df = data.frame(
    id = c(1,1,2,2),
    myfactor = as.factor(c(1,2,1,2)),
    var2change = c(10,10,NA,20),
    var3change = c(5,10,15,20),
    var4change = c(NA,2,3,8)
     )

导致:

  id myfactor var2change var3change var4change
1  1        1         10          5         NA
2  1        2         10         10          2
3  2        1         NA         15          3
4  2        2         20         20          8

我想要的输出是:

  id myfactor var2change var3change var4change
1  1        1         10          5         NA
2  1        2         10         10         NA
3  2        1         NA         15          3
4  2        2         NA         20          8

我要处理的变量不止一个,而且对于每个变量,该集合以不同的方式不完整。我觉得这可以通过巧妙地使用 plyr / tidyr 包中的现有功能来实现,但我找不到一种优雅的方式来将这些概念应用于我的问题。

任何帮助将不胜感激。

【问题讨论】:

  • 我不确定我是否清楚地理解了这个问题。您能否将列数增加到 4-5 并显示示例和预期输出。还要解释一下改值到NA的逻辑。
  • 好的,我已经编辑了问题。逻辑是我已经配对了两个时间点(或上面的两个 myfactor)的数据,我希望汇总统计信息只包含我在两个时间点拥有的数据。

标签: r tidyr plyr


【解决方案1】:

我假设您拥有的数据集是有序的,因此每对观察值都按它们的行索引分组。

默认情况下,mean() 函数将返回一个 NA,如果它的任何输入是 NA。因此,这是使用dplyr 按组获取NA 的一种巧妙方法。

library(dplyr)
df = data.frame(
  myfactor = as.factor(c(1,2,1,2)),
  var2change = c(10,10,NA,20)
)

# 1 Create ID variable to group rows in pairs
id = c()
j = 0
for (i in 1:length(df$var2change)){
  k = floor(j/2)
  id = c(id, k)
  j = j + 1
}
df$id = id

# Set all variables within group to NA if one of them is
df = df %>% 
  group_by(id) %>%
  mutate(var_changed = mean(var2change)) 

如果您的数据中有明确的 ID 变量,则可以替换此解决方案的第一部分。

编辑:对多个变量执行此操作(基于对问题的更改):

df = data.frame(
  id = c(1,1,2,2),
  myfactor = as.factor(c(1,2,1,2)),
  var2change = c(10,10,NA,20),
  var3change = c(5,10,15,20),
  var4change = c(NA,2,3,8)
)
for (col in 2:4) {
  col = paste0("var", col, "change")
  df = df %>% 
    group_by(id) %>%
    mutate(new_col = mean(get(col))) 
  df[["new_col"]] = ifelse(is.na(df["new_col"]), NA, df[[col]])
  df[col] = NULL
  names(df)[names(df) == "new_col"] <- col
}

如果速度是个问题,您可以通过将 group_by 移出循环来加快速度

【讨论】:

  • 谢谢,这很聪明。但是,我还有许多其他变量,我想避免重复调用 mutate。
【解决方案2】:

您可以按id 分组,如果任何值中包含NA,则将它们全部替换为NA。要将函数应用于多个列,我们使用across

library(dplyr)

df %>%
  group_by(id) %>%
  mutate(across(starts_with('var'), ~if(any(is.na(.))) NA else .))
  #for dplyr < 1.0.0 we can use `mutate_at`
  #mutate_at(vars(starts_with('var')), ~if(any(is.na(.))) NA else .)

#     id myfactor var2change var3change var4change
#  <dbl> <fct>         <dbl>      <dbl>      <dbl>
#1     1 1                10          5         NA
#2     1 2                10         10         NA
#3     2 1                NA         15          3
#4     2 2                NA         20          8

【讨论】:

  • 太棒了!现在我需要找到一种方法来使用starts_with和其他函数来获取我想要的所有变量(我的变量名不像var...那样系统化)。
  • 您可以使用列范围var2change:var4change 或在其中的位置3:5
  • 好的,非常感谢!仅供参考,我不得不取消分组(),因为我需要摆脱 group_by 变量。
【解决方案3】:

拥有一个分组变量 (group) 以及您的时间变量 (myfactor) 会有所帮助。然后你可以用dplyr 做一些修改来创建你想要的变量。

library(dplyr)

df = data.frame(
  group = rep(c(1,2), each = 2),
  myfactor = as.factor(c(1,2,1,2)),
  var2change = c(10,10,NA,20)
)

df %>% group_by(group) %>%
  mutate(var3change = all(!is.na(var2change)),
         var4change = if_else(var3change, var2change, as.numeric(NA)))

【讨论】:

  • 谢谢,是的,我确实有一个分组变量。但是,我想避免多次调用 mutate 因为我有很多变量。