【问题标题】:Sum values from rows ignoring certain values in R忽略 R 中某些值的行的总和值
【发布时间】:2022-01-18 02:14:48
【问题描述】:

我有一个关于这个问题的跟进:Sum values from rows with conditions in R

这是我的数据:

ID <- c("A", "B", "C", "D", "E", "F")
Q1 <- c(0, 1, 7, 9, NA, 3) 
Q2 <- c(0, 3, 2, 2, NA, 3) 
Q3 <- c(0, 0, 7, 9, NA, 3) 

dta <- data.frame(ID, Q1, Q2, Q3) 

我需要对低于 7 的每个值求和,但在值超过 7 的行中,我需要对所有低于 7 的数字求和并忽略超过它的数字。应保留具有所有 NA 的行。结果应如下所示:

ProxySum
0
4
2
2
NA
9

我已经根据上一篇文章的回复尝试了这段代码:

dta2  <- dta %>% 
    rowwise() %>% 
    mutate(ProxySum = ifelse(all(c_across(Q1:Q3) < 7), Reduce(`+`, c_across(Q1:Q3)), (ifelse(any(c_across(Q1:Q3) > 7), sum(.[. <  7]), NA))))

但在数字超过 7 的行中,我最终得到所有行和列的总和。我错过了什么?

【问题讨论】:

  • 您的示例数据不起作用 - 您需要在字母周围加上引号,并且 NA 需要大写。
  • 添加到@GregorThomas 评论。请注意编码风格:逗号后的空格,vars 的小写名称,函数名和左括号之间没有空格,管道旨在使代码更具可读性 - 将您的调用放在管道之后的新行,嵌套 ifelse通话令人困惑。此外,您无需创建变量,只需调用 data.frame() 即可。可以使用data.frame(x = ..., y = ...)。您不需要as.data.frame(),而是需要data.frame()。
  • Nattys,不要太担心那些样式建议 - 虽然有用,但它们并不是完全必要的。关于as.data.frame - 请在您的控制台中尝试此操作,检查它返回的内容。如果你想使用as.data.frame(你可以),你应该使用as.data.frame(list(ID,Q1,Q2,Q3))
  • @tjebo 样式对于代码的评估不是必需的,但对于可读性非常重要。明确的提问方式,是正确答案的一半。我不明白如何代码的最后一行很清楚,为什么它比格式清晰的代码更好。某人可以做某事这一事实并不意味着某人应该去做。没有理由把裤子戴在头上,也没有理由在这里使用as.data.frame() 和额外的变量赋值。做你不应该做的事,仅仅因为你能做到,这就是罪的定义:)。
  • 谢谢大家的造型技巧,我会更加注意,我知道可读性很重要。至于没有引号的 ID 列,你是对的,我创建示例时忘记添加它们。我不是一个很有经验的用户,仍然犯了很多错误

标签: r dplyr


【解决方案1】:

稍微不同的方法怎么样 - 首先旋转更长的时间,然后按组按条件求和,然后返回。

在当前版本中,仅包含“一些”NA 的行将返回 NA 以外的值。 (NA 将被视为 0)。如果您想为这些行返回 NA,请将 all 更改为 any。

library(tidyverse)

ID <- c("A","B","C","D","E","F")
Q1 <- c(0,1,7,9,NA,3) 
Q2 <- c(0,3,2,2,NA,3) 
Q3 <- c(0,0,7,9,NA,3) 

dta <- data.frame(ID,Q1,Q2,Q3) 

dta %>%
  pivot_longer(-ID) %>%
  group_by(ID) %>%
  mutate(ProxySum = ifelse(all(is.na(value)), NA, sum(value[which(value<7)]))) %>%
  pivot_wider()
#> # A tibble: 6 × 5
#> # Groups:   ID [6]
#>   ID    ProxySum    Q1    Q2    Q3
#>   <chr>    <dbl> <dbl> <dbl> <dbl>
#> 1 A            0     0     0     0
#> 2 B            4     1     3     0
#> 3 C            2     7     2     7
#> 4 D            2     9     2     9
#> 5 E           NA    NA    NA    NA
#> 6 F            9     3     3     3

由reprex package (v2.0.1) 于 2021 年 12 月 14 日创建

【讨论】:

    【解决方案2】:

    另一个使用rowSums和dplyr::across的选项:

    ID <- LETTERS[1:6]
    Q1 <- c(0,1,7,9,NA,3) 
    Q2 <- c(0,3,2,2,NA,3) 
    Q3 <- c(0,0,7,9,NA,3) 
    
    dta <- data.frame(ID,Q1,Q2,Q3) 
    
    library(dplyr)
    
    dta %>% 
      mutate(ProxySum = rowSums(across(Q1:Q3, function(.x) { .x[.x >= 7] <- 0; .x })))
    #>   ID Q1 Q2 Q3 ProxySum
    #> 1  A  0  0  0        0
    #> 2  B  1  3  0        4
    #> 3  C  7  2  7        2
    #> 4  D  9  2  9        2
    #> 5  E NA NA NA       NA
    #> 6  F  3  3  3        9
    

    【讨论】:

    • 我可能会添加 na.rm = TRUE,但这当然取决于 OP 想要对 NA 做什么(在我的解决方案中,由于 OP 没有指定,所以我并没有为此费心应该发生什么)
    • @tjebo OP 表示他们希望保留 NA。
    • @stefan_aus_hannover 谢谢 - 我忽略了这一点,并相应地更新了我的答案。目前尚不清楚 OP 如何处理仅包含“一些”NA 的行。
    • @tjebo 关于NAs 的好点,不知何故我错过了。
    • @tjebo,在我的数据中,NA 编码为 7,因此没有 NA,除了没有答案的 ID,例如我的示例中的 ID E
    【解决方案3】:

    base 中的一种方法:

    rowSums(dta[, 2:4] * (dta[, 2:4] < 7))
    
    # [1]  0  4  2  2 NA  9
    

    添加解释,根据@tjebo评论

    • 使用dta[, 2:4] &lt; 7,您将生成一个填充有logical 值的数据框,其中TRUE 或FALSE 对应于小于 或大于 的值7。可以一行完成,因为这个操作是矢量化的;
    • 然后,您在 logical 数据框之上相乘,并使用您的原始值填充数据框。在底层,R 将 logical 类型转换为 numeric 类型,因此您的 logical 数据集中的所有 FALSE 和 TRUEs 都将转换为 0s 和 1s .这意味着如果原始值小于7,则将原始值乘以1,否则乘以0s;
    • 由于NA &lt; 7 产生NA,然后乘以NA 也将产生NAs - 您保留原来的NAs;
    • 最后一步是在结果数据帧上调用rowSums(),它将汇总每个特定行的值。由于其中超过7 的会变成0s,因此您将它们排除在结果总和之外;
    • 如果您想获得至少一个值不是NA的行的总和,您可以在rowSums() 调用中使用na.rm = TRUE 参数。但是,在这种情况下,对于带有 NAs 的行,您将获得 0。

    【讨论】:

    • 只是想评论一下我添加了您的代码进行变异以创建另一列。这是我在其他人感兴趣时使用的代码:dta2 &lt;- dta %&gt;% mutate(ProxySum = rowSums(dta[, 2:4] * (dta[, 2:4] &lt; 7)))
    • 使用此代码,您还可以使用自 R4.01 以来引入的 native 管道 (|&gt;)。
    【解决方案4】:

    更新:参见@tjebo 对与 stefan 相同解决方案的评论:

    这是一个不完全相同的解决方案:使用hablar:

    library(dplyr)
    library(hablar)
    
    dta %>% 
      rowwise() %>% 
      mutate(sum = sum_(across(Q1:Q3, ~case_when(.<7 ~sum_(.)))))
    

    第一个答案:可能与 stefan 的答案相同:

    这是另一个dplyr 解决方案:

    library(dplyr)
    dta %>% 
      mutate(across(where(is.numeric), ~ifelse(.>=7,0,.)),
             sum = rowSums(across(where(is.numeric))))
    
      ID Q1 Q2 Q3 sum
    1  A  0  0  0   0
    2  B  1  3  0   4
    3  C  0  2  0   2
    4  D  0  2  0   2
    5  E NA NA NA  NA
    6  F  3  3  3   9
    

    【讨论】:

    • 这和Stefan的代码基本一样,但是只有两行?
    • 嗯。你可能是对的!我赞成所有答案,因为我尝试了很长时间来解决它而没有遵循任何解决方案。我的第一次尝试是dta %&gt;% rowwise() %&gt;% mutate(across(Q1:Q3, ~ifelse(.&lt;7 &amp; sum(is.na(.)==0),sum(.), NA))) %&gt;% mutate(sum = sum(across(Q1:Q3, ~sum(., na.rm = TRUE))))。但是这个解决方案没有考虑到NA。所以总而言之,我认为值得留下我的答案,因为它被宣布为另一个(用你的话来说[多行]解决方案)。此外,就可读性而言,值得对其进行更改。你怎么看?
    • 当然,留下两个建议!我不知道hablar,谢谢分享。一般来说,我认为答案不一定需要完全不同,但如果它们非常相似,应该更清楚地指出这是如何不同的。我同意你的版本更具可读性!
    猜你喜欢
    • 1970-01-01
    • 2014-11-03
    • 2012-07-28
    • 1970-01-01
    • 2020-08-02
    • 2020-11-28
    • 1970-01-01
    • 1970-01-01
    • 2013-06-10
    相关资源
    最近更新 更多