通过R

时间:2018-01-07 18:28:35

标签: r dataframe

我试图将数据帧中的每个单元格除以列的总和。例如,我有一个数据框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(),但我不知道如何编写循环遍历列中每个单元格的函数,然后除以列总和。谢谢您的帮助!

5 个答案:

答案 0 :(得分:3)

解决方案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的{​​{3}}和解决方案。

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在评论中提出的prop.table方法,以及方法apply方法。我用m1_1, m1_2, m2_1, ... to m5调用了所有解决方案。如果一个解决方案中有两种方法,我使用_来分隔它们。我还将prop.table方法称为m6,将apply方法称为m7。请注意,我修改了m6以将输出作为数据框,以便所有方法都可以具有data frame,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) 

Spacedman

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

DATA

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

答案 1 :(得分: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踩踏,请事先制作副本。

答案 2 :(得分:1)

您也可以在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

答案 3 :(得分:-1)

尝试申请:

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)
})

答案 4 :(得分:-1)

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

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