расширить data.frame до длинного формата и увеличить значение

Я хотел бы преобразовать свои данные из короткого формата в длинный формат, и я думаю, что есть простой способ сделать это (возможно, с помощью reshape2, plyr, dplyr и т. д.?).

Например, у меня есть:

foo <- data.frame(id = 1:5, 
              y = c(0, 1, 0, 1, 0),
              time = c(2, 3, 4, 2, 3))

id y time
1  0  2
2  1  3
3  0  4
4  1  2
5  0  3

Я хотел бы развернуть/скопировать каждую строку n раз, где n - это значение этой строки в столбце «время». Однако я также хотел бы, чтобы переменная «время» увеличивалась с 1 до n. То есть я хотел бы произвести:

id  y time
1   0   1
1   0   2
2   1   1
2   1   2
2   1   3
3   0   1
3   0   2
3   0   3
3   0   4
4   1   1
4   1   2
5   0   1
5   0   2
5   0   3

В качестве бонуса я также хотел бы сделать своего рода увеличение переменной «y», где для тех идентификаторов с y = 1 y устанавливается равным 0 до наибольшего значения «время». То есть я хотел бы произвести:

id  y time
1   0   1
1   0   2
2   0   1
2   0   2
2   1   3
3   0   1
3   0   2
3   0   3
3   0   4
4   0   1
4   1   2
5   0   1
5   0   2
5   0   3

Похоже, что dplyr уже может это сделать, но я просто не знаю, где искать. В любом случае полезно любое решение, исключающее циклы.


person user1231088    schedule 02.10.2014    source источник


Ответы (4)


Вы можете создать новый фрейм данных с правильными столбцами id и time для длинного формата, а затем объединить его с оригиналом. Это оставляет NA для несовпадающих значений, которые затем можно заменить на 0:

merge(foo, 
      with(foo, 
           data.frame(id=rep(id,time), time=sequence(time))
      ), 
      all.y=TRUE
)
##    id time  y
## 1   1    1 NA
## 2   1    2  0
## 3   2    1 NA
## 4   2    2 NA
## 5   2    3  1
## 6   3    1 NA
## 7   3    2 NA
## 8   3    3 NA
## 9   3    4  0
## 10  4    1 NA
## 11  4    2  1
## 12  5    1 NA
## 13  5    2 NA
## 14  5    3  0

Аналогичное слияние работает для первого расширения. Объедините foo без столбца time с тем же созданным фреймом данных, что и выше:

merge(foo[c('id','y')], 
      with(foo, 
           data.frame(id=rep(id,time), time=sequence(time))
      )
) 
##    id y time
## 1   1 0    1
## 2   1 0    2
## 3   2 1    1
## 4   2 1    2
## 5   2 1    3
## 6   3 0    1
## 7   3 0    2
## 8   3 0    3
## 9   3 0    4
## 10  4 1    1
## 11  4 1    2
## 12  5 0    1
## 13  5 0    2
## 14  5 0    3

Нет необходимости указывать all (или all.y) в последнем выражении, потому что для каждого совпадающего значения id есть несколько значений time, и они расширяются. В предыдущем случае значения time были сопоставлены из обоих фреймов данных, и без указания all (или all.y) вы вернули исходные данные.

person Matthew Lundberg    schedule 02.10.2014

Начальное расширение может быть достигнуто с помощью:

newdat <- transform( 
  foo[rep(rownames(foo),foo$time),], 
  time = sequence(foo$time)
)

#    id y time
#1    1 0    1
#1.1  1 0    2
#2    2 1    1
#2.1  2 1    2
#2.2  2 1    3
# etc

Чтобы получить полное решение, включая бонусную часть, выполните:

newdat$y[-cumsum(foo$time)] <- 0

#    id y time
#1    1 0    1
#1.1  1 0    2
#2    2 0    1
#2.1  2 0    2
#2.2  2 1    3
#etc

Если бы вы были действительно возбудимы, вы могли бы сделать все это за один шаг, используя within:

within(
  foo[rep(rownames(foo),foo$time),],
  {
    time <- sequence(foo$time)
    y[-cumsum(foo$time)] <- 0
  }
)
person thelatemail    schedule 02.10.2014
comment
@MatthewLundberg - cumsum(foo$time) всегда будет давать последнюю строку групп в расширенных данных. Подстановка этого значения для y==1 и его инвертирование устанавливает для всех строк перед последней строкой в ​​каждой группе значение 0. - person thelatemail; 02.10.2014
comment
+1. Я не проводил никаких измерений, но это выглядит более эффективным, чем слияние. - person Matthew Lundberg; 02.10.2014
comment
@MatthewLundberg, ну, теперь с решением dplyr и data.table в опциях, кто будет проводить бенчмаркинг ;-) - person A5C1D2H2I1M1N2O1R2T1; 02.10.2014
comment
На самом деле, с некоторыми быстрыми тестами это работает очень хорошо. - person A5C1D2H2I1M1N2O1R2T1; 02.10.2014

Если вы хотите использовать "data.table", вы можете попробовать:

library(data.table)
fooDT <- as.data.table(foo)
fooDT[, list(time = sequence(time)), by = list(id, y)]
#     id y time
#  1:  1 0    1
#  2:  1 0    2
#  3:  2 1    1
#  4:  2 1    2
#  5:  2 1    3
#  6:  3 0    1
#  7:  3 0    2
#  8:  3 0    3
#  9:  3 0    4
# 10:  4 1    1
# 11:  4 1    2
# 12:  5 0    1
# 13:  5 0    2
# 14:  5 0    3

И бонусный вопрос:

fooDT[, list(time = sequence(time)), 
      by = list(id, y)][, y := {y[1:(.N-1)] <- 0; y}, 
                        by = id][]
#     id y time
#  1:  1 0    1
#  2:  1 0    2
#  3:  2 0    1
#  4:  2 0    2
#  5:  2 1    3
#  6:  3 0    1
#  7:  3 0    2
#  8:  3 0    3
#  9:  3 0    4
# 10:  4 0    1
# 11:  4 1    2
# 12:  5 0    1
# 13:  5 0    2
# 14:  5 0    3

В качестве альтернативы бонусному вопросу:

fooDT[, list(time=seq_len(time)), by=list(id,y)][y == 1, 
                y := c(rep.int(0, .N-1L), 1), by=id][]
person A5C1D2H2I1M1N2O1R2T1    schedule 02.10.2014

С dplyr (и magritte для лучшей разборчивости):

library(magrittr)
library(dplyr)

foo[rep(1:nrow(foo), foo$time), ] %>%
    group_by(id) %>%
    mutate(y = !duplicated(y, fromLast = TRUE),
                  time = 1:n())

Надеюсь, поможет

person Athos    schedule 02.10.2014
comment
Вы можете попробовать foo[rep(1:nrow(foo), foo$time),] %>% group_by(id) %>% mutate(time=1:n(), y=replace(y, !!y, c(rep(0, n()-1), 1))) - person akrun; 02.10.2014