在行之间计算标准偏差

11

假设我有以下数据:

colA <- c("SampA", "SampB", "SampC")
colB <- c(21, 20, 30)
colC <- c(15, 14, 12)
colD <- c(10, 22, 18)
df <- data.frame(colA, colB, colC, colD)
df
#    colA colB colC colD
# 1 SampA   21   15   10
# 2 SampB   20   14   22
# 3 SampC   30   12   18

我想要获取B-D列中数值的行均值和标准差。

计算行均值可以按以下方式进行:

library(dplyr)
df %>% select(., matches("colB|colC|colD")) %>% mutate(rmeans = rowMeans(.))
#   colB colC colD   rmeans
# 1   21   15   10 15.33333
# 2   20   14   22 18.66667
# 3   30   12   18 20.00000

但是当我尝试使用sd()函数来计算标准偏差时,它会抛出一个错误。

df %>% select(., matches("colB|colC|colD")) %>% mutate(rsds = sapply(., sd(.)))
Error in is.data.frame(x) : 
  (list) object cannot be coerced to type 'double'
所以我的问题是:我如何在这里计算标准偏差?
编辑:我尝试了sapply(),并阅读了第一个答案here
附加编辑:不一定要寻找“整洁”的解决方案(基本R也可以正常工作)。
7个回答

6

我不确定相对于本页面之前的答案,dplyrc_across 功能是多新的/旧的,但这里有一个解决方案,几乎直接从 dplyr::c_across 的文档中剪切和粘贴而来:

df %>% 
  rowwise() %>% 
  mutate(
     mean = mean(c_across(colB:colD)),
     sd = sd(c_across(colB:colD))
  )

# A tibble: 3 x 6
# Rowwise: 
  colA   colB  colC  colD  mean    sd
  <fct> <dbl> <dbl> <dbl> <dbl> <dbl>
1 SampA    21    15    10  15.3  5.51
2 SampB    20    14    22  18.7  4.16
3 SampC    30    12    18  20    9.17

1
这绝对是我现在会做的方法。而且,我猜 c_across 出现得更晚了? Hadley Wickham 在 这篇文章 中提到于 2020 年。 - Dunois
1
我很感激这个答案,因为它适用于我想要在逐行计算中使用的许多列的用例。这样就免去了我一个个打字的麻烦。 - Andrew Jackson

5

请使用 (此链接),并结合matrixStats软件包中的rowSds函数进行尝试。

library(dplyr)
library(matrixStats)

columns <- c('colB', 'colC', 'colD')

df %>% 
  mutate(Mean= rowMeans(.[columns]), stdev=rowSds(as.matrix(.[columns])))

返回
   colA colB colC colD     Mean    stdev
1 SampA   21   15   10 15.33333 5.507571
2 SampB   20   14   22 18.66667 4.163332
3 SampC   30   12   18 20.00000 9.165151

您的数据

colA <- c("SampA", "SampB", "SampC")
colB <- c(21, 20, 30)
colC <- c(15, 14, 12)
colD <- c(10, 22, 18)
df <- data.frame(colA, colB, colC, colD)
df

4
一个不同的`dplyr`和`tidyr`方法可能是:
df %>% 
 pivot_longer(-1) %>%
 group_by(colA) %>%
 mutate(rsds = sd(value)) %>%
 pivot_wider(names_from = "name",
             values_from = "value")

  colA   rsds  colB  colC  colD
  <fct> <dbl> <dbl> <dbl> <dbl>
1 SampA  5.51    21    15    10
2 SampB  4.16    20    14    22
3 SampC  9.17    30    12    18

或者,可以使用 rowwise()do()

 df %>% 
 rowwise() %>%
 do(data.frame(., rsds = sd(unlist(.[2:length(.)]))))

  colA   colB  colC  colD  rsds
* <fct> <dbl> <dbl> <dbl> <dbl>
1 SampA    21    15    10  5.51
2 SampB    20    14    22  4.16
3 SampC    30    12    18  9.17

dplyr 1.0.0 版本以来,还有一个选项:
df %>% 
 rowwise() %>%
 mutate(rsds = sd(c_across(-1)))

3

这里有另一种使用pmap来获取逐行的平均值标准差(sd)的方法

library(purrr)
library(dplyr)
library(tidur_
f1 <- function(x) tibble(Mean = mean(x), SD = sd(x))
df %>% 
  # select the numeric columns
  select_if(is.numeric) %>%
  # apply the f1 rowwise to get the mean and sd in transmute
  transmute(out = pmap(.,  ~ f1(c(...)))) %>% 
  # unnest the list column
  unnest %>%
  # bind with the original dataset
  bind_cols(df, .)
#   colA colB colC colD     Mean       SD
#1 SampA   21   15   10 15.33333 5.507571
#2 SampB   20   14   22 18.66667 4.163332
#3 SampC   30   12   18 20.00000 9.165151

我相信这个问题可能已经在某个地方被问过了(而且我似乎无法从快速的谷歌搜索中得到答案),但是 c(...) 的意义是什么? - Dunois
1
@Dunois 我们正在使用 ... 捕获所有行元素,并将它们连接(c)成一个向量。 - akrun

3

您可以使用pmap,或者使用rowwise(或者按照colA进行分组)与mutate一起使用:

library(tidyverse)
df %>% mutate(sd = pmap(.[-1], ~sd(c(...)))) # same as transform(df, sd = apply(df[-1],1,sd))
#>    colA colB colC colD       sd
#> 1 SampA   21   15   10 5.507571
#> 2 SampB   20   14   22 4.163332
#> 3 SampC   30   12   18 9.165151

df %>% rowwise() %>% mutate(sd = sd(c(colB,colC,colD)))
#> Source: local data frame [3 x 5]
#> Groups: <by row>
#> 
#> # A tibble: 3 x 5
#>   colA   colB  colC  colD    sd
#>   <fct> <dbl> <dbl> <dbl> <dbl>
#> 1 SampA    21    15    10  5.51
#> 2 SampB    20    14    22  4.16
#> 3 SampC    30    12    18  9.17

df %>% group_by(colA) %>% mutate(sd = sd(c(colB,colC,colD)))
#> # A tibble: 3 x 5
#> # Groups:   colA [3]
#>   colA   colB  colC  colD    sd
#>   <fct> <dbl> <dbl> <dbl> <dbl>
#> 1 SampA    21    15    10  5.51
#> 2 SampB    20    14    22  4.16
#> 3 SampC    30    12    18  9.17

我注意到对于dplyr 0.8.3和tidyverse 1.2.1,无论是使用group_by还是rowwise,都不能使用select解决方案,例如使用sd(select(.,-colA))。你有什么想法吗? - Fourier
我不确定你尝试了什么,但你可能误解了magrittr的点插入规则。df %>% sd(select(.,-colA))等同于df %>% sd(., select(.,-colA)) - moodymudskipper
在进行操作之前,如果要选择列(带或不带rowwise()),哪种语法才是正确的? - Fourier
1
类似于 df %>% select(-colA) %>% mutate(sd = pmap(., ~sd(c(...)))) 这样的东西? - moodymudskipper
1
是的,谢谢。pmap 必须在这里。它运行得非常好! - Fourier

3

我看到这篇文章有点旧,但是有些答案比较复杂,所以我想提出一个更简单(更快)的方法。

计算行的平均值很简单,只需使用rowMeans:

rowMeans(df[, c('colB', 'colC', 'colD')])

这是矢量化且非常快速的。

虽然没有“rowSd”功能,但编写一个并不难。以下是我使用的“rowVars”函数。

rowVars <- function(x, na.rm=F) {
    # Vectorised version of variance filter
    rowSums((x - rowMeans(x, na.rm=na.rm))^2, na.rm=na.rm) / (ncol(x) - 1)
}

计算标准差:

sqrt(rowVars(df[, c('colB', 'colC', 'colD')]))

再次强调,向量化和快速性对于大型输入矩阵的情况非常重要。


2

使用magrittr包的管道符%>% 不是逐行处理的好方法。
也许以下内容是您想要的。

最初的回答:

df %>% 
  select(-colA) %>%
  t() %>% as.data.frame() %>%
  summarise_all(sd)
#        V1       V2       V3
#1 5.507571 4.163332 9.165151

谢谢你指出来。我不确定何时尝试“tidyverse”方法,何时坚持使用基本的R语言。我可能应该在原始帖子中提到,我并不一定寻找一个管道解决方案? - Dunois
2
@Dunois 可能是的,但问题被标记为 tidyverse 并且管道是处理数据的一种非常好的方式。我主要提到它是因为我尝试过使用 rowwise(),但无法使其正常工作,所以最终采用了 t() %>% as.data.frame() - Rui Barradas
2
这里有一种使 rowwise 生效的方法:df %>% rowwise() %>% summarize(sd = sd(c(colB,colC,colD))) - moodymudskipper
@Moody_Mudskipper 你应该将它发布为答案。 - Rui Barradas

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