【问题标题】:how to cumulatively add values in one vector in R如何在R中的一个向量中累积添加值
【发布时间】:2014-02-20 15:52:08
【问题描述】:

我有一个看起来像这样的数据集

id  name    year    job    job2
1   Jane    1980    Worker  0
1   Jane    1981    Manager 1
1   Jane    1982    Manager 1
1   Jane    1983    Manager 1
1   Jane    1984    Manager 1
1   Jane    1985    Manager 1
1   Jane    1986    Boss    0
1   Jane    1987    Boss    0
2   Bob     1985    Worker  0
2   Bob     1986    Worker  0
2   Bob     1987    Manager 1
2   Bob     1988    Boss    0
2   Bob     1989    Boss    0
2   Bob     1990    Boss    0
2   Bob     1991    Boss    0
2   Bob     1992    Boss    0

这里,job2 表示一个虚拟变量,表示一个人在该年是否为Manager。我想对这个数据集做两件事:首先,我只想保留该人第一次成为Boss 时的行。其次,我想查看一个人作为Manager 的累积年数,并将此信息存储在变量cumu_job2 中。因此我想拥有:

id  name    year    job    job2 cumu_job2
1   Jane    1980    Worker  0   0
1   Jane    1981    Manager 1   1
1   Jane    1982    Manager 1   2
1   Jane    1983    Manager 1   3
1   Jane    1984    Manager 1   4
1   Jane    1985    Manager 1   5
1   Jane    1986    Boss    0   0
2   Bob     1985    Worker  0   0
2   Bob     1986    Worker  0   0
2   Bob     1987    Manager 1   1
2   Bob     1988    Boss    0   0

我已经更改了我的示例并将 Worker 职位包括在内,因为这更多地反映了我想要对原始数据集执行的操作。此线程中的答案仅在数据集中只有 Managers 和 Boss 时才有效 - 所以任何有关使这项工作的建议都会很棒。将不胜感激!!

【问题讨论】:

    标签: r row cumulative-sum dplyr


    【解决方案1】:

    这是针对同一问题的简洁 dplyr 解决方案。

    注意:在读取数据时确保stringsAsFactors = FALSE

    library(dplyr)
    dat %>%
      group_by(name, job) %>%
      filter(job != "Boss" | year == min(year)) %>%
      mutate(cumu_job2 = cumsum(job2))
    

    输出:

       id name year     job job2 cumu_job2
    1   1 Jane 1980  Worker    0         0
    2   1 Jane 1981 Manager    1         1
    3   1 Jane 1982 Manager    1         2
    4   1 Jane 1983 Manager    1         3
    5   1 Jane 1984 Manager    1         4
    6   1 Jane 1985 Manager    1         5
    7   1 Jane 1986    Boss    0         0
    8   2  Bob 1985  Worker    0         0
    9   2  Bob 1986  Worker    0         0
    10  2  Bob 1987 Manager    1         1
    11  2  Bob 1988    Boss    0         0
    

    解释

    1. 获取数据集
    2. 按姓名和工作分组
    3. 根据条件过滤每个组
    4. 添加cumu_job2 列。

    【讨论】:

    • data.table可以使用相同的过滤思想:dt[, list(cum_job2=cumsum(job2[job!="Boss" | year==min(year)])), by=c('name', 'job')]
    • @Ramnath 我想知道为什么这对我不起作用——我无法安装 dplyr 并且 %.% 不是函数。
    • %.%dplyr 中的一个函数。 dplyr 在 CRAN 上,所以使用 install_packages 安装应该很简单。
    • @Ramnath install.packages 中的警告:包“dplyr”不可用(对于 R 版本 3.0.0)您正在使用哪个 R 版本?
    • @Ramnath 我也试过 devtools::install_github("hadley/dplyr") 但它说客户端错误:404 未找到
    【解决方案2】:

    这是使用withinave 的基本解决方案。我们假设输入是DF,并且数据按照问题排序。

    DF2 <- within(DF, {
        seq = ave(id, id, job, FUN = seq_along)
        job2 = (job == "Manager") + 0
        cumu_job2 = ave(job2, id, job, FUN = cumsum)
    })
    subset(DF2, job != 'Boss' | seq == 1, select = - seq)
    

    REVISION:现在使用within

    【讨论】:

      【解决方案3】:

      我认为这可以满足您的需求,尽管数据必须按照您提供的方式进行排序。

      my.df <- read.table(text = '
      id  name    year    job    job2
      1   Jane    1980    Worker  0
      1   Jane    1981    Manager 1
      1   Jane    1982    Manager 1
      1   Jane    1983    Manager 1
      1   Jane    1984    Manager 1
      1   Jane    1985    Manager 1
      1   Jane    1986    Boss    0
      1   Jane    1987    Boss    0
      2   Bob     1985    Worker  0
      2   Bob     1986    Worker  0
      2   Bob     1987    Manager 1
      2   Bob     1988    Boss    0
      2   Bob     1989    Boss    0
      2   Bob     1990    Boss    0
      2   Bob     1991    Boss    0
      2   Bob     1992    Boss    0
      ', header = TRUE, stringsAsFactors = FALSE)
      
      my.seq <- data.frame(rle(my.df$job)$lengths)
      
      my.df$cumu_job2 <- as.vector(unlist(apply(my.seq, 1, function(x) seq(1,x))))
      
      my.df2 <- my.df[!(my.df$job=='Boss' & my.df$cumu_job2 != 1),]
      my.df2$cumu_job2[my.df2$job != 'Manager'] <- 0
      
         id name year     job job2 cumu_job2
      1   1 Jane 1980  Worker    0         0
      2   1 Jane 1981 Manager    1         1
      3   1 Jane 1982 Manager    1         2
      4   1 Jane 1983 Manager    1         3
      5   1 Jane 1984 Manager    1         4
      6   1 Jane 1985 Manager    1         5
      7   1 Jane 1986    Boss    0         0
      9   2  Bob 1985  Worker    0         0
      10  2  Bob 1986  Worker    0         0
      11  2  Bob 1987 Manager    1         1
      12  2  Bob 1988    Boss    0         0
      

      【讨论】:

        【解决方案4】:

        由马修·道尔供稿:

        dt[, .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)],
             by = list(name, job)]
        

        解释

        1. 获取数据集
        2. 运行过滤器并在 Data (.SD) 的每个 S子集内添加一列
        3. 按姓名和工作分组

        旧版本:

        您在这里有两个不同的拆分应用组合。一个获取累积职位,另一个获取第一行boss状态。这是data.table 中的一个实现,我们基本上分别进行每个分析(嗯,有点),然后使用rbind 在一个地方收集所有内容。主要需要注意的是 by=id 部分,这基本上意味着针对数据中的每个 id 分组评估其他表达式,这是您正确指出的尝试中缺少的内容。

        library(data.table)
        dt <- as.data.table(df)
        dt[, cumujob:=0L]  # add column, set to zero
        dt[job2==1, cumujob:=cumsum(job2), by=id]  # cumsum for manager time by person 
        rbind(
          dt[job2==1],                     # this is just the manager portion of the data
          dt[job2==0, head(.SD, 1), by=id] # get first bossdom row
        )[order(id, year)]                 # order by id, year
        #       id name year     job job2 cumujob
        #   1:  1 Jane 1980 Manager    1       1
        #   2:  1 Jane 1981 Manager    1       2
        #   3:  1 Jane 1982 Manager    1       3
        #   4:  1 Jane 1983 Manager    1       4
        #   5:  1 Jane 1984 Manager    1       5
        #   6:  1 Jane 1985 Manager    1       6
        #   7:  1 Jane 1986    Boss    0       0
        #   8:  2  Bob 1985 Manager    1       1
        #   9:  2  Bob 1986 Manager    1       2
        #  10:  2  Bob 1987 Manager    1       3
        #  11:  2  Bob 1988    Boss    0       0
        

        请注意,这假定表格在每个 id 中按年份排序,但如果它不够容易修复。


        或者,您也可以通过以下方式实现相同的目的:

        ans <- dt[, .I[job != "Boss" | year == min(year)], by=list(name, job)]
        ans <- dt[ans$V1]
        ans[, cumujob := cumsum(job2), by=list(name,job)] 
        

        这个想法基本上是获取条件匹配的行号(使用.I - 内部变量),然后在这些行号上子集dt$v1 部分),然后只执行累积和。

        【讨论】:

        • 非常感谢!我有一个错误 - RHS 类型('integer')必须匹配 LHS('double')。对于最快的情况,检查和强制会影响性能。要么更改目标列的类型,要么自己强制 := 的 RHS(例如,使用 1L 而不是 1)
        • 这个我不太明白,因为我确实通过 as.integer 命令把 id 和 job2 向量变成了整数向量....
        • 我阅读了stackoverflow.com/questions/16361225/… 并解决了问题 - 只是做了 cumujob:=as.numeric(cumsum(job2)) 代替。
        • 就可读性而言,我会选择:dt[, .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], by = list(name, job)]
        • @eddi 嗨——我还有一个问题!因此,如果我有除经理以外的其他职位,我将如何保存所有信息?我没有在我的问题中明确指定这部分,但我认为 dt[job2==1] 这样做会在我的数据集中丢弃大量信息。
        【解决方案5】:

        @BrodieG 的更好:

        数据

        dat <- read.table(text="id  name    year    job    job2
        1   Jane    1980    Manager 1
        1   Jane    1981    Manager 1
        1   Jane    1982    Manager 1
        1   Jane    1983    Manager 1
        1   Jane    1984    Manager 1
        1   Jane    1985    Manager 1
        1   Jane    1986    Boss    0
        1   Jane    1987    Boss    0
        2   Bob     1985    Manager 1
        2   Bob     1986    Manager 1
        2   Bob     1987    Manager 1
        2   Bob     1988    Boss    0
        2   Bob     1989    Boss    0
        2   Bob     1990    Boss    0
        2   Bob     1991    Boss    0
        2   Bob     1992    Boss    0", header=TRUE)
        

        #代码:

        inds1 <- rle(dat$job2)
        inds2 <- cumsum(inds1[[1]])[inds1[[2]] == 1] + 1
        
        ends <- cumsum(inds1[[1]])
        starts <- c(1, head(ends + 1, -1))
        inds3 <- mapply(":", starts, ends)
        dat$id <- rep(1:length(inds3), sapply(inds3, length))
        dat <- do.call(rbind, lapply(split(dat[, 1:5], dat$id ), function(x) {
            if(x$job2[1] == 0){ 
                x$cumu_job2 <- rep(0, nrow(x))
            } else { 
                x$cumu_job2 <- 1:nrow(x)
            }
            x
        }))
        
        
        keeps <- dat$job2 > 0
        keeps[inds2] <- TRUE
        dat2 <- data.frame(dat[keeps, ], row.names = NULL)
        dat2
        
        ##    id name year     job job2 cumu_job2
        ## 1   1 Jane 1980 Manager    1         1
        ## 2   1 Jane 1981 Manager    1         2
        ## 3   1 Jane 1982 Manager    1         3
        ## 4   1 Jane 1983 Manager    1         4
        ## 5   1 Jane 1984 Manager    1         5
        ## 6   1 Jane 1985 Manager    1         6
        ## 7   2 Jane 1986    Boss    0         0
        ## 8   3  Bob 1985 Manager    1         1
        ## 9   3  Bob 1986 Manager    1         2
        ## 10  3  Bob 1987 Manager    1         3
        ## 11  4  Bob 1988    Boss    0         0
        

        【讨论】:

        • 猜你反对简洁?
        • @pssguy 我猜你对礼仪有偏见?不过你的回答很简洁。
        猜你喜欢
        • 1970-01-01
        • 2017-06-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-26
        • 2021-12-06
        • 2020-07-18
        相关资源
        最近更新 更多