Вопрос по r, dataframe – Самый быстрый способ добавить строки для пропущенных временных шагов?

30

У меня есть столбец в моих наборах данных, где периоды времени (Time) являются целыми числами от a-b. Иногда для любой группы могут отсутствовать периоды времени. Я хотел бы заполнить эти строкиNA, Ниже приведен пример данных для 1 (из нескольких 1000) групп.

structure(list(Id = c(1, 1, 1, 1), Time = c(1, 2, 4, 5), Value = c(0.568780482159894, 
-0.7207749516298, 1.24258192959273, 0.682123081696789)), .Names = c("Id", 
"Time", "Value"), row.names = c(NA, 4L), class = "data.frame")


  Id Time      Value
1  1    1  0.5687805
2  1    2 -0.7207750
3  1    4  1.2425819
4  1    5  0.6821231

Как видите, время 3 отсутствует. Часто один или несколько могут отсутствовать. Я могу решить это самостоятельно, но боюсь, что не смогу сделать это наиболее эффективным способом. Мой подход заключается в создании функции, которая:

Генерация последовательности периодов времени изmin(Time) вmax(Time)

Тогда сделайsetdiff схватить пропавшего без вестиTime ценности.

Преобразовать этот вектор вdata.frame

Вытащить переменные уникального идентификатора (Id и другие, не перечисленные выше), и добавьте это к этому data.frame.

Слияние двух.

Возврат из функции.

Таким образом, весь процесс будет выполняться следующим образом:

   # Split the data into individual data.frames by Id.
    temp_list <- dlply(original_data, .(Id)) 
    # pad each data.frame
    tlist2 <- llply(temp_list, my_pad_function)
    # collapse the list back to a data.frame
    filled_in_data <- ldply(tlist2)

Лучший способ добиться этого?

Я бы сделал в основном то, что вы описываете, только используяexpand.grid а потомmerge сall = TRUE, Не уверен, что сначала нужно разделить по Id. joran
У меня это работает сейчас, но я все еще могу использовать общее решение, так как оно будет в пакете, а я не работаю. знать, что пользователь может представить в качестве исходных данных. Maiasaura
Здесь добавленная сложность, есть множество переменных id. Мне нужно только добавитьTime и установитьValue вNA и дополнить остальное. Итак, это становитсяdata_to_merge <- data.frame(id=unique(data$id),...)  (это действительно длинная строка и не переносимая, если структура данных изменяется). Хотел бы я просто объединить отсутствующее время, добавить NA и эффективно извлечь все остальное из исходных данных. Maiasaura
Для разгруппированных данных, см. Также несколько хороших ответов вHow to Add zero to missing value in r. Henrik

Ваш Ответ

4   ответа
6

tidyr за это.

использованиеtidyr::complete заполнять строки дляTimeи по умолчанию значения заполняютсяNA.

Create Data

Я расширил пример данных, чтобы показать, что он работает для несколькихIdи даже когда в пределахId полный спектрTime нет

library(dplyr)
library(tidyr)


df <- tibble(
  Id = c(1, 1, 1, 1, 2, 2, 2),
  Time = c(1, 2, 4, 5, 2, 3, 5),
  Value = c(0.56, -0.72, 1.24, 0.68, 1.46, 0.74, 0.99)
)

df
#> # A tibble: 7 x 3
#>      Id  Time Value
#>   <dbl> <dbl> <dbl>
#> 1     1     1  0.56
#> 2     1     2 -0.72
#> 3     1     4  1.24
#> 4     1     5  0.68
#> 5     2     2  1.46
#> 6     2     3  0.74
#> 7     2     5  0.99
Fill in the missing rows
df %>% complete(nesting(Id), Time = seq(min(Time), max(Time), 1L))

#> # A tibble: 10 x 3
#>       Id  Time Value
#>    <dbl> <dbl> <dbl>
#> 1      1     1  0.56
#> 2      1     2 -0.72
#> 3      1     3    NA
#> 4      1     4  1.24
#> 5      1     5  0.68
#> 6      2     1    NA
#> 7      2     2  1.46
#> 8      2     3  0.74
#> 9      2     4    NA
#> 10     2     5  0.99
Мне показалось, что с этим конкретным решением проще всего ответить на тот же вопрос, что и выше, особенно при использовании tidyverse для манипулирования данными.
0

freqTable <- as.data.frame(table(idvar1, idvar2, idvarN)) затем вытащить ряды, гдеFreq==0, добавьте, если необходимо, а затем вернитесь к исходным данным.

4

Вот то, что используетdata.table пакет, и это может помочь, когда существует более одной переменной ID. Это также может быть быстрее, чемmergeв зависимости от того, как вы хотите, чтобы ваши результаты. Я был бы заинтересован в бенчмаркинге и / или предложенных улучшениях.

Сначала создайте более требовательные данные с двумя переменными идентификатора.

library(data.table)

set.seed(1)

mydf3<-data.frame(Id=sample(1:100,10000,replace=TRUE),
  Value=rnorm(10000))
mydf3<-mydf3[order(mydf3$Id),]

mydf3$Time<-unlist(by(mydf3,mydf3$Id,
  function(x)sample(1:(nrow(x)+3),nrow(x)),simplify=TRUE))

mydf3$Id2<-sample(1:2,nrow(mydf3),replace=TRUE)

Создать функцию (это былоEDITED - смотри историю)

padFun<-function(data,idvars,timevar){
# Coerce ID variables to character
  data[,idvars]<-lapply(data[,idvars,drop=FALSE],as.character)
# Create global ID variable of all individual ID vars pasted together
  globalID<-Reduce(function(...)paste(...,sep="SOMETHINGWACKY"),
    data[,idvars,drop=FALSE])
# Create data.frame of all possible combinations of globalIDs and times
  allTimes<-expand.grid(globalID=unique(globalID),
    allTime=min(data[,timevar]):max(data[,timevar]),
    stringsAsFactors=FALSE)
# Get the original ID variables back
  allTimes2<-data.frame(allTimes$allTime,do.call(rbind,
    strsplit(allTimes$globalID,"SOMETHINGWACKY")),stringsAsFactors=FALSE)
# Convert combinations data.frame to data.table with idvars and timevar as key
  allTimesDT<-data.table(allTimes2)
  setnames(allTimesDT,1:ncol(allTimesDT),c(timevar,idvars))
  setkeyv(allTimesDT,c(idvars,timevar))
# Convert data to data.table with same variables as key
  dataDT<-data.table(data,key=c(idvars,timevar))
# Join the two data.tables to create padding
  res<-dataDT[allTimesDT]
  return(res)
}

Используйте функцию

(padded2<-padFun(data=mydf3,idvars=c("Id"),timevar="Time"))

#       Id Time        Value Id2
#  [1,]  1    1 -0.262482283   2
#  [2,]  1    2 -1.423935165   2
#  [3,]  1    3  0.500523295   1
#  [4,]  1    4 -1.912687398   1
#  [5,]  1    5 -1.459766444   2
#  [6,]  1    6 -0.691736451   1
#  [7,]  1    7           NA  NA
#  [8,]  1    8  0.001041489   2
#  [9,]  1    9  0.495820559   2
# [10,]  1   10 -0.673167744   1
# First 10 rows of 12800 printed.

(padded<-padFun(data=mydf3,idvars=c("Id","Id2"),timevar="Time"))

#      Id Id2 Time      Value
#  [1,]  1   1    1         NA
#  [2,]  1   1    2         NA
#  [3,]  1   1    3  0.5005233
#  [4,]  1   1    4 -1.9126874
#  [5,]  1   1    5         NA
#  [6,]  1   1    6 -0.6917365
#  [7,]  1   1    7         NA
#  [8,]  1   1    8         NA
#  [9,]  1   1    9         NA
# [10,]  1   1   10 -0.6731677
# First 10 rows of 25600 printed.

Отредактированная функция разделяет globalID на его составные части в комбинации data.frame перед объединением с исходными данными. Это должно (я думаю) быть лучше.

Правильный пакет, но слишком сложный. Вы как-то скучалиroll=TRUE что конкретно для этого? Сохраняйте данные нерегулярными в таблице, затем присоединяйте к ней регулярные временные ряды. См. 3-й раздел & quot; введение в data.table & quot; виньетка и пример в?data.table с помощьюroll=TRUE, Это одна из главных особенностей пакета.
Ах да, вы правы. Затем добавили ответ, надеюсь, хорошо.
@ MatthewDowle, Вы знаете свои функции намного лучше меня, и любые предложения по улучшению приветствуются. Поскольку ОП хотел "дополнить" переменные без идентификатора и без времени сNA за каждого пропавшегоTimeЯ думал, используяroll=TRUE был бы неправильный подход, так как это будет "pad" с предыдущим значением, верно? Кажется, что большая часть сложности в функции связана с рассмотрением нескольких переменных ID, а не с частью соединения, что очень хорошо достижимо сdata.table.
34

mydf3 :

DT = as.data.table(mydf3)
setkey(DT,Id,Time)
DT[CJ(unique(Id),seq(min(Time),max(Time)))]
      Id Time        Value Id2
 [1,]  1    1 -0.262482283   2
 [2,]  1    2 -1.423935165   2
 [3,]  1    3  0.500523295   1
 [4,]  1    4 -1.912687398   1
 [5,]  1    5 -1.459766444   2
 [6,]  1    6 -0.691736451   1
 [7,]  1    7           NA  NA
 [8,]  1    8  0.001041489   2
 [9,]  1    9  0.495820559   2
[10,]  1   10 -0.673167744   1
First 10 rows of 12800 printed. 

setkey(DT,Id,Id2,Time)
DT[CJ(unique(Id),unique(Id2),seq(min(Time),max(Time)))]
      Id Id2 Time      Value
 [1,]  1   1    1         NA
 [2,]  1   1    2         NA
 [3,]  1   1    3  0.5005233
 [4,]  1   1    4 -1.9126874
 [5,]  1   1    5         NA
 [6,]  1   1    6 -0.6917365
 [7,]  1   1    7         NA
 [8,]  1   1    8         NA
 [9,]  1   1    9         NA
[10,]  1   1   10 -0.6731677
First 10 rows of 25600 printed. 

CJ выступает за Cross Join, см.?CJ, Обивка сNAэто происходит потому, чтоnomatch по умолчаниюNA, Задаватьnomatch в0 вместо этого, чтобы удалить нет совпадений. Если вместо заполненияNAs требуется преобладающая строка, просто добавьтеroll=TRUE, Это может быть более эффективным, чем заполнениеNAс, а затем заполнениеNAс потом. Смотрите описаниеroll в?data.table.

setkey(DT,Id,Time)
DT[CJ(unique(Id),seq(min(Time),max(Time))),roll=TRUE]
      Id Time        Value Id2
 [1,]  1    1 -0.262482283   2
 [2,]  1    2 -1.423935165   2
 [3,]  1    3  0.500523295   1
 [4,]  1    4 -1.912687398   1
 [5,]  1    5 -1.459766444   2
 [6,]  1    6 -0.691736451   1
 [7,]  1    7 -0.691736451   1
 [8,]  1    8  0.001041489   2
 [9,]  1    9  0.495820559   2
[10,]  1   10 -0.673167744   1
First 10 rows of 12800 printed. 

setkey(DT,Id,Id2,Time)
DT[CJ(unique(Id),unique(Id2),seq(min(Time),max(Time))),roll=TRUE]
      Id Id2 Time      Value
 [1,]  1   1    1         NA
 [2,]  1   1    2         NA
 [3,]  1   1    3  0.5005233
 [4,]  1   1    4 -1.9126874
 [5,]  1   1    5 -1.9126874
 [6,]  1   1    6 -0.6917365
 [7,]  1   1    7 -0.6917365
 [8,]  1   1    8 -0.6917365
 [9,]  1   1    9 -0.6917365
[10,]  1   1   10 -0.6731677
First 10 rows of 25600 printed. 
@sirallen Смотрите, например,Efficient way to Fill Time-Series per group а такжеFilling missing dates by group
@rbatt Курс DataCamp data.table лучше всего объясняет это с Аруном и мной на экране с анимированной графикой. Глава 3 посвящена соединениям и подвижным соединениям. Это & APOS; shere.
@sirallen Для заполнения сseq() пожалуйста, задайте новый вопрос. Слишком долго для комментариев.
Как бы вы подошли сseq(min(Time), max(Time)) по группе?
Я никогда полностью не понимал использованиеroll; как это может помочь мне получить АН?

Похожие вопросы