2017-09-09 4 views
5

Étant donné un data.frame qui contient une série temporelle et un ou plusieurs champs de regroupement. Nous avons donc plusieurs séries chronologiques - une pour chaque combinaison de regroupement. Mais certaines dates sont manquantes. Alors, quel est le plus simple (en termes de "moyen le plus tidy") d'ajouter ces dates aux bonnes valeurs de regroupement?Remplir des dates manquantes dans une série chronologique groupée - une voie inverse?

Normalement, je dirais que je génère un data.frame avec toutes les dates et que je fais un full_join avec mes séries temporelles. Mais maintenant, nous devons le faire pour chaque combinaison de valeurs de regroupement - et remplir les valeurs de regroupement.

Regardons un exemple:

D'abord, je créer un data.frame avec des valeurs manquantes:

library(dplyr) 
library(lubridate) 

set.seed(1234) 
# Time series should run vom 2017-01-01 til 2017-01-10 
date <- data.frame(date = seq.Date(from=ymd("2017-01-01"), to=ymd("2017-01-10"), by="days"), v = 1) 
# Two grouping dimensions 
d1 <- data.frame(d1 = c("A", "B", "C", "D"), v = 1) 
d2 <- data.frame(d2 = c(1, 2, 3, 4, 5), v = 1) 

# Generate the data.frame 
df <- full_join(date, full_join(d1, d2)) %>% 
    select(date, d1, d2) 
# and ad to value columns 
df$v1 <- runif(200) 
df$v2 <- runif(200) 

# group by the dimension columns 
df <- df %>% 
    group_by(d1, d2) 

# create missing dates 
df.missing <- df %>% 
    filter(v1 <= 0.8) 

# So now 2017-01-01 and 2017-01-10, A, 5 are missing now 
df.missing %>% 
    filter(d1 == "A" & d2 == 5) 

# A tibble: 8 x 5 
# Groups: d1, d2 [1] 
     date  d1 d2   v1  v2 
     <date> <fctr> <dbl>  <dbl>  <dbl> 
1 2017-01-02  A  5 0.21879954 0.1335497 
2 2017-01-03  A  5 0.32977018 0.9802127 
3 2017-01-04  A  5 0.23902573 0.1206089 
4 2017-01-05  A  5 0.19617465 0.7378315 
5 2017-01-06  A  5 0.13373890 0.9493668 
6 2017-01-07  A  5 0.48613541 0.3392834 
7 2017-01-08  A  5 0.35698708 0.3696965 
8 2017-01-09  A  5 0.08498474 0.8354756 

Donc, pour ajouter les dates manquantes je générons une data.frame avec toutes les dates:

start <- min(df.missing$date) 
end <- max(df.missing$date) 

all.dates <- data.frame(date=seq.Date(start, end, by="day")) 

Non Je veux faire quelque chose comme (rappelez-vous: df.missing est group_by (d1, d2))

df.missing %>% 
    do(my_join()) 

Alors nous allons définir my_join():

my_join <- function(data) { 
    # get value of both dimensions 
    d1.set <- data$d1[[1]] 
    d2.set <- data$d2[[1]] 

    tmp <- full_join(data, all.dates) %>% 
    # First we need to ungroup. Otherwise we can't change d1 and d2 because they are grouping variables 
    ungroup() %>% 
    mutate(
     d1 = d1.set, 
     d2 = d2.set 
    ) %>% 
    group_by(d1, d2) 

    return(tmp) 
} 

Maintenant, nous pouvons appeler my_join() pour chaque combinaison et un coup d'oeil à "A/5"

df.missing %>% 
    do(my_join(.)) %>% 
    filter(d1 == "A" & d2 == 5) 

# A tibble: 10 x 5 
# Groups: d1, d2 [1] 
     date  d1 d2   v1  v2 
     <date> <fctr> <dbl>  <dbl>  <dbl> 
1 2017-01-02  A  5 0.21879954 0.1335497 
2 2017-01-03  A  5 0.32977018 0.9802127 
3 2017-01-04  A  5 0.23902573 0.1206089 
4 2017-01-05  A  5 0.19617465 0.7378315 
5 2017-01-06  A  5 0.13373890 0.9493668 
6 2017-01-07  A  5 0.48613541 0.3392834 
7 2017-01-08  A  5 0.35698708 0.3696965 
8 2017-01-09  A  5 0.08498474 0.8354756 
9 2017-01-01  A  5   NA  NA 
10 2017-01-10  A  5   NA  NA 

Great! C'est ce que nous cherchions. Mais nous devons définir d1 et d2 dans my_join et il se sent un peu maladroit.

Donc, y a-t-il une voie inverse de cette solution?

PS: J'ai mis le code dans un point essentiel: https://gist.github.com/JerryWho/1bf919ef73792569eb38f6462c6d7a8e

+0

JerryWho que vous avez reçu quelques bonnes réponses ci-dessous. Si quelqu'un vous a aidé, envisagez de l'accepter comme une réponse (coche à gauche). Il permet à la communauté de connaître la réponse qui a fonctionné pour votre cas. Vous pouvez changer votre réponse acceptée dans le futur si vous le souhaitez. – CPak

Répondre

6

tidyr a quelques bons outils pour ce genre de problèmes. Jetez un oeil à complete.


library(dplyr) 
library(tidyr) 
library(lubridate) 

want <- df.missing %>% 
    ungroup() %>% 
    complete(nesting(d1, d2), date = seq(min(date), max(date), by = "day")) 

want %>% filter(d1 == "A" & d2 == 5) 

#> # A tibble: 10 x 5 
#> # Groups: d1 [1] 
#>  d1 d2  date   v1  v2 
#> <fctr> <dbl>  <date>  <dbl>  <dbl> 
#> 1  A  5 2017-01-01   NA  NA 
#> 2  A  5 2017-01-02 0.21879954 0.1335497 
#> 3  A  5 2017-01-03 0.32977018 0.9802127 
#> 4  A  5 2017-01-04 0.23902573 0.1206089 
#> 5  A  5 2017-01-05 0.19617465 0.7378315 
#> 6  A  5 2017-01-06 0.13373890 0.9493668 
#> 7  A  5 2017-01-07 0.48613541 0.3392834 
#> 8  A  5 2017-01-08 0.35698708 0.3696965 
#> 9  A  5 2017-01-09 0.08498474 0.8354756 
#> 10  A  5 2017-01-10   NA  NA 
+1

Cela ne fonctionne pas pour moi. Certaines lignes sont ajoutées. Mais esp. A/5 est toujours manquant 2017-01-01 et 2017-01-10. Je pense que c'est parce que min (date) et max (date) sont par groupe. Peut-être que vous devez dégrouper avant complet et group_by après. – JerryWho

+0

Oh désolé, c'est parce que j'ai d'abord dissocié 'df.missing'. – austensen

+0

'complete' est parfait ici, j'aurais aimé y penser – CPak

0

Voici une manière tidyverse commençant par df.missing

library(tidyverse) 
ans <- df.missing %>% 
      nest(date) %>% 
      mutate(data = map(data, ~seq.Date(start, end, by="day"))) %>% 
      unnest(data) %>% 
      rename(date = data) %>% 
      left_join(., df.missing, by=c("date","d1","d2")) 

ans %>% filter(d1 == "A" & d2 == 5) 

Sortie

 d1 d2  date   v1  v2 
    <fctr> <dbl>  <date>  <dbl>  <dbl> 
1  A  5 2017-01-01   NA  NA 
2  A  5 2017-01-02 0.21879954 0.1335497 
3  A  5 2017-01-03 0.32977018 0.9802127 
4  A  5 2017-01-04 0.23902573 0.1206089 
5  A  5 2017-01-05 0.19617465 0.7378315 
6  A  5 2017-01-06 0.13373890 0.9493668 
7  A  5 2017-01-07 0.48613541 0.3392834 
8  A  5 2017-01-08 0.35698708 0.3696965 
9  A  5 2017-01-09 0.08498474 0.8354756 
10  A  5 2017-01-10   NA  NA 

------ -------------------------------------------------- -----------------------------------------
est ici une approche alternative qui utilise expand.grid et dplyr verbes

with(df.missing, expand.grid(unique(date), unique(d1), unique(d2))) %>% 
    setNames(c("date", "d1", "d2")) %>% 
    left_join(., df.missing, by=c("date","d1","d2")) 

sortie (tête)

  date d1 d2   v1   v2 
1 2017-01-01 A 1 0.113703411 0.660754634 
2 2017-01-02 A 1 0.316612455 0.422330675 
3 2017-01-03 A 1 0.553333591 0.424109178 
4 2017-01-04 A 1   NA   NA 
5 2017-01-05 A 1   NA   NA 
6 2017-01-06 A 1 0.035456727 0.352998502 
0

Ici read.zoo crée un grand objet sous forme de zoo et que nous fusionner les dates. Ensuite, nous le convertissons en une longue trame de données en utilisant fortify.zoo et étendons v1 et v2 en utilisant spread.

Notez que:

  • si on peut supposer que chaque date apparaît dans au moins une combinaison des variables de division, à savoir sort(unique(df.missing$date)) contient toutes les dates, nous pourrions omettre la ligne merge et aucun joint serait doit être fait du tout. Les données de test df.missing montré dans la question n'ont cette propriété:

    all(all.dates$date %in% df.missing$date) 
    ## [1] TRUE 
    
  • nous pourrions arrêter après la merge (ou après read.zoo si chaque jour est présente au moins une fois comme au point précédent) si une grande forme objet zoo peut être utilisé car cela a déjà toutes les dates.

Dans le code ci-dessous la ligne marquée ### peut être omise avec la version de développement du zoo (1.8.1):

library(dplyr) 
library(tidyr) 
library(zoo) 

split.vars <- c("d1", "d2") 
df.missing %>% 
    as.data.frame %>%  ### 
    read.zoo(split = split.vars) %>% 
    merge(zoo(, seq(start(.), end(.), "day"))) %>% 
    fortify.zoo(melt = TRUE) %>% 
    separate(Series, c("v", split.vars)) %>% 
    spread(v, Value) 

Mise à jour: simplification Note dans le zoo 1.8.1 .