简化遍历列的多个 rowSums

Simplify multiple rowSums looping through columns

我目前正在使用 R 尝试为 DF 创建具有前一列之和的多列。想象一下我有一个这样的 DF:

df=    
   sep-2016  oct-2016    nov-2016  dec-2016   jan-2017
1  70        153            NA        28        19
2  57         68            73       118        16
3  29         NA            19        32        36
4 177         36             3        54        53

我想在末尾添加我要报告的月份的前几行的总和,因此对于 10 月,您最终得到 9 月和 10 月的总和,而对于 11 月,您最终得到总和9 月、10 月和 11 月,结果是这样的:

 df=    
     sep-2016  oct-2016    nov-2016  dec-2016   jan-2017 status-Oct2016 status-Nov 2016
    1  70        153            NA        28        19      223       223
    2  57         68            73       118        16      105       198
    3  29         NA            19        32        36       29        48
    4 177         36             3        54        53      213        93

我想知道一种有效的方法来代替编写大量的 rowSums() 行,即使我能在每个月的迭代中获得标签也会很棒!

谢谢!

老实说,我不知道您为什么想要这种格式的数据,但这里有一种 tidyverse 方法可以实现它。它涉及在将数据传播回宽格式之前将数据转换为整洁的格式。需要注意的关键是,在整洁的格式中,month 是单个列中的变量而不是分布在多个列中,您可以简单地使用 group_by(rowid)cumsum 来计算所有你想要的值。最后几行正在构建 status- 列名称并将数据传播回宽格式。

library(tidyverse)
df <- read_table2(
  "sep-2016  oct-2016    nov-2016  dec-2016   jan-2017
  70        153            NA        28        19
  57         68            73       118        16
  29         NA            19        32        36
 177         36             3        54        53"
)

df %>%
  rowid_to_column() %>%
  gather("month", "value", -rowid) %>%
  arrange(rowid) %>%
  group_by(rowid) %>%
  mutate(
    value = replace_na(value, 0),
    status = cumsum(value)
    ) %>%
  gather("vartype", "number", value, status) %>%
  mutate(colname = ifelse(vartype == "value", month, str_c("status-", month))) %>%
  select(rowid, number, colname) %>%
  spread(colname, number)
#> # A tibble: 4 x 11
#> # Groups:   rowid [4]
#>   rowid `dec-2016` `jan-2017` `nov-2016` `oct-2016` `sep-2016`
#>   <int>      <dbl>      <dbl>      <dbl>      <dbl>      <dbl>
#> 1     1       28.0       19.0       0         153         70.0
#> 2     2      118         16.0      73.0        68.0       57.0
#> 3     3       32.0       36.0      19.0         0         29.0
#> 4     4       54.0       53.0       3.00       36.0      177  
#> # ... with 5 more variables: `status-dec-2016` <dbl>,
#> #   `status-jan-2017` <dbl>, `status-nov-2016` <dbl>,
#> #   `status-oct-2016` <dbl>, `status-sep-2016` <dbl>

reprex package (v0.2.0) 创建于 2018-02-16。

我们可以使用 lapply 遍历列以应用 rowSums

dat2 <- as.data.frame(lapply(2:ncol(dat), function(i){
  rowSums(dat[, 1:i], na.rm = TRUE)
}))

names(dat2) <- paste0("status-", names(dat[, -1]))

dat3 <- cbind(dat, dat2)

dat3
#   sep-2016 oct-2016 nov-2016 dec-2016 jan-2017 status-oct-2016 status-nov-2016 status-dec-2016 status-jan-2017
# 1       70      153       NA       28       19             223             223             251             270
# 2       57       68       73      118       16             125             198             316             332
# 3       29       NA       19       32       36              29              48              80             116
# 4      177       36        3       54       53             213             216             270             323

数据

dat <- read.table(text = "   'sep-2016'  'oct-2016'    'nov-2016'  'dec-2016'   'jan-2017'
1  70        153            NA        28        19
                  2  57         68            73       118        16
                  3  29         NA            19        32        36
                  4 177         36             3        54        53",
                  header = TRUE, stringsAsFactors = FALSE)

names(dat) <- c("sep-2016", "oct-2016", "nov-2016", "dec-2016", "jan-2017")

一种简洁的方法是将数据转换为长格式。

library(tibble)
library(tidyr)
library(dplyr)

your_data <- tribble(~"sep_2016",   ~"oct_2016",    ~"nov_2016",  ~"dec_2016",   ~"jan_2017",
  70,        153,            NA,        28,        19,
  57,         68,            73,       118,        16,
  29,         NA,            19,        32,        36,
 177,         36,             3,        54,        53)

您可以使用 tidyr 包中的 gather 更改 data.frame 的格式。

your_data_long <- your_data %>%
  rowid_to_column() %>% 
  gather(key = month_year, value = the_value, -rowid) 

head(your_data_long)
#> # A tibble: 6 x 3
#>   rowid month_year the_value
#>   <int>      <chr>     <dbl>
#> 1     1   sep_2016        70
#> 2     2   sep_2016        57
#> 3     3   sep_2016        29
#> 4     4   sep_2016       177
#> 5     1   oct_2016       153
#> 6     2   oct_2016        68

一旦您的 data.frame 为长格式。您可以使用 cumsumdplyr 函数 mutategroup_by.

计算累积和
result <- your_data_long %>%
  group_by(rowid) %>% 
  mutate(cumulative_value = cumsum(the_value)) 

result
#> # A tibble: 20 x 4
#> # Groups:   rowid [4]
#>    rowid month_year the_value cumulative_value
#>    <int>      <chr>     <dbl>            <dbl>
#>  1     1   sep_2016        70               70
#>  2     2   sep_2016        57               57
#>  3     3   sep_2016        29               29
#>  4     4   sep_2016       177              177
#>  5     1   oct_2016       153              223
#>  6     2   oct_2016        68              125
#>  7     3   oct_2016        NA               NA
#>  8     4   oct_2016        36              213
#>  9     1   nov_2016        NA               NA
#> 10     2   nov_2016        73              198
#> 11     3   nov_2016        19               NA
#> 12     4   nov_2016         3              216
#> 13     1   dec_2016        28               NA
#> 14     2   dec_2016       118              316
#> 15     3   dec_2016        32               NA
#> 16     4   dec_2016        54              270
#> 17     1   jan_2017        19               NA
#> 18     2   jan_2017        16              332
#> 19     3   jan_2017        36               NA
#> 20     4   jan_2017        53              323

如果要检索起始表格,可以使用 spread

我的首选解决方案是:

# library(matrixStats)

DF <- as.matrix(df)
DF[is.na(DF)] <- 0

RES <- matrixStats::rowCumsums(DF)
colnames(RES) <- paste0("status-", colnames(DF))

cbind.data.frame(df, RES)

这最接近您使用 rowSums 查找的内容。

一种选择是使用 tidyverse 中的 spreadgather 函数。

注意: 即使是 1st 月也添加了状态栏。并且状态列未按顺序排列,但值是正确的。

方法是:

# Data
df <- read.table(text = "sep-2016  oct-2016    nov-2016  dec-2016   jan-2017
70        153            NA        28        19
57         68            73       118        16
29         NA            19        32        36
177         36             3        54        53", header = T, stringsAsFactors = F)


library(tidyverse)

# Just add an row number as sl
df <- df %>% mutate(sl = row_number())

#Calculate the cumulative sum after gathering and arranging by date
mod_df <- df %>% 
  gather(key, value, -sl) %>%
  mutate(key = as.Date(paste("01",key, sep="."), format="%d.%b.%Y")) %>%
  arrange(sl, key) %>%
  group_by(sl) %>%
  mutate(status = cumsum(ifelse(is.na(value),0L,value) )) %>%
  select(-value) %>%
  mutate(key = paste("status",as.character(key, format="%b.%Y"))) %>%
  spread(key, status) 

# Finally join cumulative calculated sum columns with original df and then 
# remove sl column
inner_join(df, mod_df, by = "sl") %>% select(-sl)

#  sep.2016 oct.2016 nov.2016 dec.2016 jan.2017 status Dec.2016 status Jan.2017 status Nov.2016 status Oct.2016 status Sep.2016
#1       70      153       NA       28       19             251             270             223             223              70
#2       57       68       73      118       16             316             332             198             125              57
#3       29       NA       19       32       36              80             116              48              29              29
#4      177       36        3       54       53             270             323             216             213             177

另一个基本解决方案,我们构建一个矩阵来累积行总和:

status <- setNames(
  as.data.frame(t(apply(dat,1,function(x) Reduce(sum,'[<-'(x,is.na(x),0),accumulate = TRUE)))),
  paste0("status-",names(dat)))

status
#   status-sep-2016 status-oct-2016 status-nov-2016 status-dec-2016 status-jan-2017
# 1              70             223             223             251             270
# 2              57             125             198             316             332
# 3              29              29              48              80             116
# 4             177             213             216             270             323

然后根据需要将其绑定到您的原始数据:

cbind(dat,status[-1])