【问题标题】:Changing various columns depending on multiple other columns - R根据多个其他列更改各个列 - R
【发布时间】:2020-11-23 23:49:58
【问题描述】:

我正在尝试根据其他列中的条件找到一种方便的方法来更改列中的值,以便当存在 NA 并且满足条件时,它将替换该值。我有大约 6 列,类似于 A:D,然后是一个二进制列。还有与这些特定行相关的其他数据,例如(日期、位置等)

我在下面做了一些虚拟代码

`%notin%` <- Negate(`%in%`)
ops <- c("Zoo", "Fun", "Party")

df <- data.frame(A = c("Zoo", "Beer", "Rave", "Fun", "school"),
                 B = c("school", NA, "Beer", "exams", "Beer"),
                 C = c("Fun", NA, NA, "Party", "Rave"),
                 D = c(NA, NA, NA, "Rave", NA), 
                 X = c(1,0,1,0,0))

df$B[which((df$A %notin% ops | df$B %notin% ops |
                           df$C %notin% ops) & df$X == 0 & is.na(df$B))] <- "HELP"
df$C[which((df$A %notin% ops & df$B %notin% ops &
                           df$C %notin% ops) & df$X == 0 & 
                          df$B != "HELP" & is.na(df$C))] <- "HELP"
df$D[which((df$A %notin% ops & df$B %notin% ops &
                           df$C %notin% ops) & df$X == 0 & 
                          df$B != "HELP" & df$C != "HELP" &
                          is.na(df$D))] <- "HELP"

如您所见,此代码逐渐变得更加混乱且难以遵循。当我发现新的问题时,它会以这种方式逐渐增长......我知道必须有一种更清洁的方式来写这个,但我无法为我的生活弄明白。我对 tidyverse(ish) 非常熟悉——更不用说寻找 data.table 解决方案了……但基本 R 解决方案也是天赐之物。

目前,我还有其他几个带有类似条件的代码块(这就是为什么拥有一个真正有效的解决方案只是清理这段代码的巨大飞跃)。

编辑:在要求解释我正在尝试做的事情时,让我更清楚地解释一下。

当我想要更改满足某些条件的行值时,我想要一个更优雅的解决方案。在这里,每一行是一个分类调查响应,其中ops 包含我感兴趣的 3 个分类变量。X 是一个虚拟变量(实际上只是布尔值)。

为方便起见(为了不混淆下面的答案,我将稍微更改 df。

df <- data.frame(A = c("Zoo", "Beer", "Rave", "Fun", "school", "blah"),
                 B = c("school", NA, "Beer", "exams", "Beer", "blah"),
                 C = c("Fun", NA, NA, "Party", "Rave", NA),
                 D = c(NA, NA, NA, "Rave", NA, NA), 
                 X = c(1,0,1,0,0,1))

目标是在行不包含 ops & X==0

的情况下更改第一个 NA

但是,我也不想更改所有 NA - 我只想更改第一个 NA,如果它满足条件(即行包含 ops & X == 0)并且我不知道 在哪里 第一个 NA 将出现在数据中,但我知道在第一个 NA 之后,如果有更多列,其余的将是 NA,直到 X。

如果我们选择第 5 行:

ops 不在 A, B, C, D & X == 0 & is.na(D) 因此 [5,5] == "HELP"

如果我们选择第 2 行:

ops不在A, B, C,D & B is.na & C is.na, D is.na & X==0 但我只想改变NA的第一次出现

添加的新行 (6)

ops 不在 A、B、C、D 中,但 X == 0。在这种情况下,我仍然希望“帮助”出现在第一个 NA 所在的位置。

我不知道我是否让这更令人困惑 - 抱歉,我不是计算机科学家,但这基本上是我试图实现的逻辑结构,而不必更改每一行。根据不同的条件,我有 2 次使用不同版本的“HELP”再执行 2 次,然后排除了 HELP 这个词......所以它开始变得难以解析。

【问题讨论】:

  • 很好地提供了您的编码尝试,但如果您退后一步并只是解释您正在尝试做什么,这将是有帮助的。似乎当二进制列是0 并且该行不包含ops 中的任何单词时,您希望将第一个逐行NA 替换为HELP。是这样吗?
  • 是的,对不起。我正在处理机密数据(暂时) - 所以我试图提供一个有点好玩的虚拟数据。 ^上面的代码实际上适用于我需要它做的事情 - 它只是凌乱且难以使用。
  • @27ϕ9 我已经添加了更深入的解释,但是基本上就是这样。我的问题是,在此之后我必须再次执行此操作,然后也写!=“帮助”...也许它只是需要弄乱,但是当条件 A、B 时必须有一种更简单的方法来替换第一个行& C 满足

标签: r dataframe tidyverse


【解决方案1】:

这里有一个更简洁,在我看来,更容易阅读的解决方案:

library(tidyverse)

df %>% 
  mutate(B = case_when((!A %in% ops | !B %in% ops | !C %in% ops) & X == 0 & is.na(B) ~ "HELP",
                       TRUE ~ B),
         C = case_when((!A %in% ops | !B %in% ops | !C %in% ops) & X == 0 & B != "HELP" & is.na(C) ~ "HELP",
                       TRUE ~ C),
         D = case_when(!A %in% ops & !B %in% ops & !C %in% ops & X == 0 & B != "HELP" & C != "HELP" & is.na(D) ~ "HELP",
                       TRUE ~ D))

这给了我们:

    A      B     C    D X
1    Zoo school   Fun <NA> 1
2   Beer   HELP  <NA> <NA> 0
3   Rave   Beer  <NA> <NA> 1
4    Fun  exams Party Rave 0
5 school   Beer  Rave HELP 0

注意df %&gt;% mutate(!variable %in% vector)可以用来代替%notin%&lt;- Negate(%in%)

【讨论】:

  • 它更容易阅读 - 但是当我必须做我的其他变量时,这只会变得更加混乱。您是否认为可以使用迭代的解决方案,或者这只是不可行。我也更喜欢使用 %notin% 因为我可以在代码的开头将它设置为函数并忘记它,但这是偏好
  • 如果我没有收到其他回复,我会 100% 将此标记为检查。但我现在要把它打开,希望我能得到一些更优雅的东西
【解决方案2】:

使用基础 R,您可以通过创建行和列索引矩阵来更改以可扩展的方式执行此操作(当您使用 tidyverse 标记此问题时,请注意这种类型的替换不适用于小标题):

# Create column index of first NA value
ci <- seq_along(df)[max.col(cbind(is.na(df), TRUE), ties.method = "first")]
# Create logical index of the rows that meet the conditions
ri <- rowSums(sapply(df, `%in%`, ops)) == 0 & df$X == 0
# Replace values using the indices
df[na.omit(cbind(which(ri), ci[ri]))] <- "HELP"
df

       A      B    C    D X
1    Zoo school  Fun <NA> 1
2   Beer   HELP <NA> <NA> 0
3   Rave   Beer <NA> <NA> 1
4   Rave  exams Beer Rave 0
5 school   Beer Rave HELP 0

编辑:

如果需要处理其他列,您只需要使用索引数据框而不是整体。例如:

# Create additional columns
df <- data.frame(id = 1:5, df[1:3], month = month.abb[1:5], df[4:5])

所以数据框现在看起来像:

  id      A      B     C month    D X
1  1    Zoo school   Fun   Jan <NA> 1
2  2   Beer   <NA>  <NA>   Feb <NA> 0
3  3   Rave   Beer  <NA>   Mar <NA> 1
4  4    Fun  exams Party   Apr Rave 0
5  5 school   Beer  Rave   May <NA> 0

您可以按位置索引,但通常按名称索引更安全。请注意,我们现在可以将列 X 排除在外,因为我们只需要使用一次就可以直接使用它:

colidx <- c("A", "B", "C", "D")

然后在原始代码中引用整个数据框的地方,只需将其替换为索引数据框:

ci <- seq_along(df[colidx])[max.col(cbind(is.na(df[colidx]), TRUE), ties.method = "first")]
ri <- rowSums(sapply(df[colidx], `%in%`, ops)) == 0 &  df$X == 0
df[colidx][na.omit(cbind(which(ri), ci[ri]))] <- "HELP"
df
  id      A      B     C month    D X
1  1    Zoo school   Fun   Jan <NA> 1
2  2   Beer   HELP  <NA>   Feb <NA> 0
3  3   Rave   Beer  <NA>   Mar <NA> 1
4  4    Fun  exams Party   Apr Rave 0
5  5 school   Beer  Rave   May HELP 0

【讨论】:

  • 你能指导我完成这个解决方案吗?试图弄清楚如何将其应用于我的真实数据。如上所述,df 中还有一些其他变量列(例如,日期、位置)。我想知道如何把它拉回来。我有一个 ID 列,但我不确定如果有意义,我将如何将它们重新匹配?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-04
  • 2022-09-23
相关资源
最近更新 更多