【问题标题】:Dividing each cell in a data set by the column sum in R将数据集中的每个单元格除以 R 中的列总和
【发布时间】:2018-01-07 18:28:34
【问题描述】:

我试图将数据框中的每个单元格除以列的总和。例如,我有一个数据框 df:

sample   a   b   c
a2      1    4    6
a3      5    5    4

我想创建一个新的数据框,将每个单元格除以列的总和,如下所示:

sample   a   b   c
a2      .167  .444  .6
a3      .833  .556  .4

我已经看到了使用 sweep() 的答案,但这看起来像它的矩阵,而且我有数据框。我了解如何使用 colSums(),但我不确定如何编写一个循环遍历列中每个单元格然后除以列总和的函数。谢谢您的帮助!

【问题讨论】:

  • d[-1] <- prop.table(as.matrix(d[-1]), margin = 2)
  • @Henrik prop.table() 是什么?它似乎不是请求的输出......
  • 矩阵相关帖子:Dividing columns by colSums in R

标签: r dataframe


【解决方案1】:

解决方案 1

这里有两个 解决方案。我们可以使用mutate_atmutate_if 来有效地指定我们要对哪一列应用一个操作,或者我们要在什么条件下应用一个操作。

library(dplyr)

# Apply the operation to all column except sample
dat2 <- dat %>%
  mutate_at(vars(-sample), funs(./sum(.))) 
dat2
#   sample         a         b   c
# 1     a2 0.1666667 0.4444444 0.6
# 2     a3 0.8333333 0.5555556 0.4

# Apply the operation if the column is numeric 
dat2 <- dat %>%
  mutate_if(is.numeric, funs(./sum(.))) 
dat2
#   sample         a         b   c
# 1     a2 0.1666667 0.4444444 0.6
# 2     a3 0.8333333 0.5555556 0.4

解决方案 2

我们还可以使用 包中的map_atmap_if 函数。但是,由于输出是一个列表,我们需要来自基本 R 的as.data.frame 或来自as_data_frame 来将列表转换为数据框。

library(dplyr)
library(purrr)

# Apply the operation to column a, b, and c    
dat2 <- dat %>%
  map_at(c("a", "b", "c"), ~./sum(.)) %>% 
  as_data_frame()
dat2
# # A tibble: 2 x 4
#   sample     a     b     c
#   <chr>  <dbl> <dbl> <dbl>
# 1 a2     0.167 0.444 0.600
# 2 a3     0.833 0.556 0.400

# Apply the operation if the column is numeric
dat2 <- dat %>%
  map_if(is.numeric, ~./sum(.)) %>%
  as_data_frame()
dat2
# # A tibble: 2 x 4
#   sample     a     b     c
#   <chr>  <dbl> <dbl> <dbl>
# 1 a2     0.167 0.444 0.600
# 2 a3     0.833 0.556 0.400

解决方案 3

我们还可以使用 包中的.SD.SDcols

library(data.table)

# Convert to data.table
setDT(dat)
dat2 <- copy(dat)
dat2[, (c("a", "b", "c")) := lapply(.SD, function(x) x/sum(x)), .SDcols = c("a", "b", "c")]
dat2[]
#    sample         a         b   c
# 1:     a2 0.1666667 0.4444444 0.6
# 2:     a3 0.8333333 0.5555556 0.4

解决方案 4

我们还可以使用lapply函数循环遍历除第一列之外的所有列来执行操作。

dat2 <- dat
dat2[, -1] <- lapply(dat2[, -1], function(x) x/sum(x))
dat2
#   sample         a         b   c
# 1     a2 0.1666667 0.4444444 0.6
# 2     a3 0.8333333 0.5555556 0.4

我们也可以使用 apply 循环遍历所有列,但在函数中添加 if-else 语句以确保只对数字列执行操作。

dat2 <- dat
dat2[] <- lapply(dat2[], function(x){
  # Check if the column is numeric
  if (is.numeric(x)){
    return(x/sum(x))
  } else{
    return(x)
  }
})
dat2
#   sample         a         b   c
# 1     a2 0.1666667 0.4444444 0.6
# 2     a3 0.8333333 0.5555556 0.4

解决方案 5

基于gatherspread 解决方案。

library(dplyr)
library(tidyr)

dat2 <- dat %>%
  gather(Column, Value, -sample) %>%
  group_by(Column) %>%
  mutate(Value = Value/sum(Value)) %>%
  spread(Column, Value)
dat2
# # A tibble: 2 x 4
#   sample     a     b     c
# * <chr>  <dbl> <dbl> <dbl>
# 1 a2     0.167 0.444 0.600
# 2 a3     0.833 0.556 0.400

绩效评估

我很好奇哪种方法的性能最好。因此,我使用 包进行了以下性能评估,其中数据框的列名与 OP 的示例相同,但有 1000000 行。

library(dplyr)
library(tidyr)
library(purrr)
library(data.table)
library(microbenchmark)

set.seed(100)

dat <- data_frame(sample = paste0("a", 1:1000000),
                  a = rpois(1000000, lambda = 3),
                  b = rpois(1000000, lambda = 3),
                  c = rpois(1000000, lambda = 3))

# Convert the data frame to a data.table for later perofrmance evaluation
dat_dt <- as.data.table(dat)    

head(dat)
# # A tibble: 6 x 4
#   sample     a     b     c
#   <chr>  <int> <int> <int>
# 1 a1         2     5     2
# 2 a2         2     5     5
# 3 a3         3     2     4
# 4 a4         1     2     2
# 5 a5         3     3     1
# 6 a6         3     6     1

除了我提出的所有方法之外,我还对其他两个其他人提出的方法感兴趣:Henrik 在 cmets 中提出的prop.table 方法,以及Spacedman 提出的apply 方法。我用m1_1, m1_2, m2_1, ... to m5 调用了我所有的解决方案。如果一个解决方案中有两种方法,我使用_ 将它们分开。我还将prop.table 方法称为m6,将apply 方法称为m7。请注意,我修改了m6 以将输出作为数据框,以便所有方法都可以具有数据框、tibble 或 data.table 输出。

这是我用来评估性能的代码。

per <- microbenchmark(m1_1 = {dat2 <- dat %>% mutate_at(vars(-sample), funs(./sum(.)))},
                      m1_2 = {dat2 <- dat %>% mutate_if(is.numeric, funs(./sum(.)))},
                      m2_1 = {dat2 <- dat %>%
                        map_at(c("a", "b", "c"), ~./sum(.)) %>% 
                        as_data_frame()
                      },
                      m2_2 = {dat2 <- dat %>%
                        map_if(is.numeric, ~./sum(.)) %>%
                        as_data_frame()},
                      m3 = {dat_dt2 <- copy(dat_dt)
                            dat_dt2[, c("a", "b", "c") := lapply(.SD, function(x) x/sum(x)), 
                                      .SDcols = c("a", "b", "c")]},
                      m4_1 = {dat2 <- dat
                              dat2[, -1] <- lapply(dat2[, -1], function(x) x/sum(x))},
                      m4_2 = {dat2 <- dat
                              dat2[] <- lapply(dat2[], function(x){
                        if (is.numeric(x)){
                          return(x/sum(x))
                        } else{
                          return(x)
                        }
                      })},
                      m5 = {dat2 <- dat %>%
                        gather(Column, Value, -sample) %>%
                        group_by(Column) %>%
                        mutate(Value = Value/sum(Value)) %>%
                        spread(Column, Value)},
                      m6 = {dat2 <- dat
                            dat2[-1] <- prop.table(as.matrix(dat2[-1]), margin = 2)},
                      m7 = {dat2 <- dat
                            dat2[, -1] = apply(dat2[, -1], 2, function(x) {x/sum(x)})}
                      )
print(per)
# Unit: milliseconds
# expr         min          lq       mean      median          uq        max neval
# m1_1   23.335600   24.326445   28.71934   25.134798   27.465017   75.06974   100
# m1_2   20.373093   21.202780   29.73477   21.967439   24.897305  216.27853   100
# m2_1    9.452987    9.817967   17.83030   10.052634   11.056073  175.00184   100
# m2_2   10.009197   10.342819   16.43832   10.679270   11.846692  163.62731   100
#   m3   16.195868   17.154327   34.40433   18.975886   46.521868  190.50681   100
# m4_1    8.100504    8.342882   12.66035    8.778545    9.348634  181.45273   100
# m4_2    8.130833    8.499926   15.84080    8.766979    9.732891  172.79242   100
#   m5 5373.395308 5652.938528 5791.73180 5737.383894 5825.141584 6660.35354   100
#   m6  117.038355  150.688502  191.43501  166.665125  218.837502  325.58701   100
#   m7  119.680606  155.743991  199.59313  174.007653  215.295395  357.02775   100


library(ggplot2)
autoplot(per) 

结果显示基于lapplym4_1m4_2)的方法最快,而tidyr方法(m5)最慢,说明行数较大时使用gatherspread 方法不是一个好主意。

数据

dat <- read.table(text = "sample   a   b   c
a2      1    4    6
                  a3      5    5    4",
                  header = TRUE, stringsAsFactors = FALSE)

【讨论】:

  • 能否更新此答案以使用新的dplyr 功能,尤其是across()
【解决方案2】:

鉴于此:

> d = data.frame(sample=c("a2","a3"),a=c(1,5),b=c(4,5),c=c(6,4))
> d
  sample a b c
1     a2 1 4 6
2     a3 5 5 4

您可以通过应用其余列来替换除第一列之外的每一列:

> d[,-1] = apply(d[,-1],2,function(x){x/sum(x)})

> d
  sample         a         b   c
1     a2 0.1666667 0.4444444 0.6
2     a3 0.8333333 0.5555556 0.4

如果您不希望 d 被踩到,请事先复制一份。

【讨论】:

    【解决方案3】:

    您也可以在 dplyr 中执行此操作。

    sample <- c("a2", "a3")
    a <- c(1, 5)
    b <- c(4, 5)
    c <- c(6, 4)
    dat <- data.frame(sample, a, b, c)
    dat
    
    library(dplyr)
    
    dat %>%
        mutate(
            a.PCT = round(a/sum(a), 3),
            b.PCT = round(b/sum(b), 3),
            c.PCT = round(c/sum(c), 3))
    
      sample a b c a.PCT b.PCT c.PCT
    1     a2 1 4 6 0.167 0.444   0.6
    2     a3 5 5 4 0.833 0.556   0.4
    

    【讨论】:

      【解决方案4】:

      您可以使用矩阵的转置,然后再次转置:

      t(t(as.matrix(df))/colSums(df))
      

      【讨论】:

        【解决方案5】:

        尝试申请:

        mat <- matrix(1:6, ncol=3)
        apply(mat,2, function(x) x / sum(x))
        

        好的,如果您的列中没有数字值,您可以强制它们为数字:

        df <- data.frame( a=c('a', 'b'), b=c(3,4), d=c(1,6))
        apply(df,2, function(x) {
          x <- as.numeric(x)
          x / sum(x)
        })
        

        【讨论】:

        • 需要展示如何从数据框到矩阵并再次返回到数据框。
        • 求和会失败,因为示例中第一列是文本。
        猜你喜欢
        • 2022-01-13
        • 2022-06-22
        • 2015-11-06
        • 2016-06-11
        • 2016-04-02
        • 2019-06-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多