как кумулятивно добавить значения в один вектор в R

У меня есть набор данных, который выглядит так

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, потому что это больше отражает то, что я хочу делать с исходным набором данных. Ответы в этой теме работают только тогда, когда в наборе данных есть только менеджеры и боссы, поэтому любые предложения по этой работе будут отличными. буду очень признательна!!


person song0089    schedule 29.01.2014    source источник


Ответы (5)


Вот краткое 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.
person Ramnath    schedule 29.01.2014
comment
Та же идея фильтрации может быть использована с data.table: dt[, list(cum_job2=cumsum(job2[job!="Boss" | year==min(year)])), by=c('name', 'job')] - person Jean-Robert; 29.01.2014
comment
@Ramnath Я хотел бы знать, почему у меня это не работает — я не могу установить dplyr, а %.% не является функцией. - person song0089; 05.02.2014
comment
%.% — это функция в dplyr. dplyr находится в CRAN, поэтому его установка с помощью install_packages должна быть простой. - person Ramnath; 05.02.2014
comment
@Ramnath Предупреждение в install.packages: пакет «dplyr» недоступен (для версии R 3.0.0) Над какой версией R вы работаете? - person song0089; 05.02.2014
comment
@Ramnath Я также пробовал devtools::install_github(hadley/dplyr), но пишет ошибка клиента: 404 не найден - person song0089; 05.02.2014
comment
Итак, я обновил R, но это не сработало - он просто создал кумуляцию job2 до самого конца (независимо от каждого наблюдения). - person song0089; 07.02.2014
comment
Я не понимаю, что у вас получилось. Следует быть осторожным, чтобы убедиться, что plyr не загружается одновременно с dplyr, так как это может привести к некоторым конфликтам. Я обновил свой ответ выводом, который, я считаю, именно то, что вы искали. - person Ramnath; 07.02.2014

Предоставлено Мэтью Доулом:

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

Объяснение

  1. Возьмите набор данных
  2. Запустите фильтр и добавьте столбец в каждый Sподнабор Ddata (.SD)
  3. Сгруппировано по имени и должности

Старые версии:

Здесь у вас есть два разных комбайна сплит-приложения. Один для получения кумулятивных заданий, а другой для получения первой строки статуса босса. Вот реализация в 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), а затем просто выполнить кумулятивную сумму.

person BrodieG    schedule 29.01.2014
comment
Большое спасибо! У меня есть одна ошибка: тип RHS («целое») должен соответствовать LHS («двойной»). Проверка и принуждение слишком сильно повлияют на производительность в самых быстрых случаях. Либо измените тип целевого столбца, либо задайте правую часть := самостоятельно (например, используя 1L вместо 1) - person song0089; 29.01.2014
comment
Я действительно не понимаю этого, потому что я превратил векторы id и job2 в целочисленные векторы с помощью команды as.integer.... - person song0089; 29.01.2014
comment
Я прочитал stackoverflow.com/questions/16361225/ и решил проблему - вместо этого просто сделал cumujob:=as.numeric(cumsum(job2)) . - person song0089; 29.01.2014
comment
Что касается удобочитаемости, я бы выбрал: dt[, .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], by = list(name, job)] - person eddi; 29.01.2014
comment
@eddi Привет, у меня есть еще один вопрос! Итак, если у меня есть другие должности, кроме менеджеров, как я смогу сохранить всю информацию? Я не указал эту часть в своем вопросе, но я думаю, что dt[job2==1] выбрасывает много информации из моего набора данных. - person song0089; 05.02.2014
comment
Для последнего кода я получаю сообщение об ошибке [.data.frame(exemptacc, , .SD[ccmem == 0 | year == min(year)][, : неиспользуемый аргумент (by = list(name, prov.1)) - person song0089; 05.02.2014
comment
Второй код также вызывает ту же ошибку -- неиспользуемый аргумент (by = list(name1, ccmem)) - person song0089; 05.02.2014
comment
@ Rusuer9000, убедитесь, что вы добавили строку library(data.table) перед запуском этой строки. Последний у меня работает нормально. Я второй не тестировал. Если пакет не установлен, вам нужно сначала запустить install.packages("data.table"). - person BrodieG; 05.02.2014
comment
@ Rusuer9000 у вас уже есть эта информация в данных, поэтому вместо того, чтобы думать о том, как представить эту информацию в виде одного числа, как насчет того, чтобы пропустить эту часть и перейти прямо к следующему шагу того, что вы хотите сделать. - person eddi; 05.02.2014

Вот базовое решение с использованием within и ave. Мы предполагаем, что ввод 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)

ПЕРЕСМОТР: теперь используется within.

person G. Grothendieck    schedule 05.02.2014

Я думаю, что это делает то, что вы хотите, хотя данные должны быть отсортированы так, как вы их представили.

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
person Mark Miller    schedule 05.02.2014

@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
person Tyler Rinker    schedule 29.01.2014
comment
Полагаю, вы имеете что-то против краткости? - person pssguy; 29.01.2014
comment
@pssguy Я думаю, ты имеешь что-то против манер? Хотя ваш ответ был очень лаконичен. - person Tyler Rinker; 29.01.2014