18 mar 2020

Modificando la frecuencia de una serie temporal en R (I): agregando datos

Al trabajar series de tiempo es común necesitemos modificar la “frecuencia” con que aparecen nuestros datos, en el sentido de convertir en trimestrales datos mensuales o de forma contraria. Por ejemplo, podemos estar interasados en obtener el valor de producción de una economía durante un trimestre, o en conocer el número medios de muertes en un hospital infantil durante cada semana. En la siguiente entrada se muestran algunos ejemplos de cómo lograr esta modificaciones de frecuencia.

Ejemplo 1: de mensual a trimestral

La función aggregate permite modificar (agregando) la frecuencia de los datos. En los ejemplos siguientes se muestra como convertir de mensual a trimestral la data. En caso de querer convertirla en semestral o anual, solo es necesario cambiar el argumento nfrequency a los valores correspondientes.

set.seed(2)
price_sim <- ts(5+cumsum(rnorm(100)), start = c(2010, 1), frequency = 12)
head(price_sim)
[1] 4.540211 5.158137 4.437714 3.854202 4.070527 5.315518

p_mean <- aggregate(price_sim, nfrequency = 4, mean)
          Qtr1      Qtr2      Qtr3      Qtr4
2010  4.755600  4.736043  6.007220  7.717201

p_sum <- aggregate(price_sim, nfrequency = 4, sum)
Qtr1      Qtr2      Qtr3      Qtr4
2010 14.266800 14.208130 18.021661 23.151604

p_var <- aggregate(price_sim, nfrequency = 4, sd)
p_ult <- aggregate(price_sim, nfrequency = 4, function(x) tail(x,1))
p_prime <- aggregate(price_sim, nfrequency = 4, function(x) x[1])
p_ran <- aggregate(price_sim, nfrequency = 4, range)
p_ran2 <- aggregate(price_sim, nfrequency = 4, function(x) range(x)/2)

Ahora, usando la maravilla de tidyverse agregamos la serie combinando las funciones group_by y summarise. La primera de esta permite agrupar nuestros datos según el criterio que necesitemos, mensual, anual, semestral, etc; mientras la segunda permite incluir la agregación que necesitemos promedio, valores máximo, ...:

library(tidyverse)
library(zoo)

data.frame(price_sim) %>%
 mutate(trimestre = as.yearqtr(time(price_sim), format = "Q")) %>%
 group_by(trimestre) %>%
   summarise(p_mean = mean(price_sim),
             p_sum = sum(price_sim),
             p_var = sd(price_sim),
             p_ult = last(price_sim),
             p_prime = first(price_sim),
             p_ran = max(price_sim)-min(price_sim),
             p_ran2 = (max(price_sim)+min(price_sim))/2)

# A tibble: 34 x 8
   trimestre p_mean p_sum  p_var p_ult p_prime p_ran p_ran2
   <yearqtr>  <dbl> <dbl>  <dbl> <dbl>   <dbl> <dbl>  <dbl>
 1 2010 Q1     4.76  14.3 0.974   5.88    4.10 1.77    4.99
 2 2010 Q2     4.74  14.2 0.0667  4.80    4.75 0.132   4.73
 3 2010 Q3     6.01  18.0 1.08    7.25    5.51 1.98    6.26
 4 2010 Q4     7.72  23.2 0.718   8.51    7.11 1.40    7.81
 5 2011 Q1     8.02  24.1 0.895   8.86    8.12 1.78    7.97
 6 2011 Q2     7.15  21.4 0.518   7.46    6.55 0.914   7.01
 7 2011 Q3     9.46  28.4 1.35   11.0     8.48 2.52    9.74
 8 2011 Q4    11.5   34.5 1.78   13.3     9.80 3.54   11.6
 9 2012 Q1    11.9   35.6 1.30   11.4    13.3  2.45   12.1
10 2012 Q2    11.4   34.2 0.560  11.9    10.8  1.08   11.3
# ... with 24 more rows

Ejemplo 2: de diario a semanal

Adicionalmente podemos estar interesado en llevar datos en frecuencia diaria a semanales o mensuales, siendo la clave el elemento de agregación de las series. En el siguiente ejemplo se crean las variables semana, mes, año, … con el fin de obtener distintas agregaciones que nos permiten modificar la variable que incluimos como argumento en el comando by_group, por esta razón se colocan diversos niveles de agregación.

Generamos datos diarios y colocamos las distintas demarcaciones temporales, es decir, podemos obtener el día, el día del mes, mes, trimestre… estas variables nos permitirán obtener la frecuencia nueva que necesitamos:

library(lubridate)

price_sim <- ts(5+cumsum(rnorm(1e3)), start = c(2015,1,1), frequency = 365)
fechas <- seq(as.Date("2010/1/1"), by="days", length.out = 1e3)

data_diaria <- data.frame(price_sim, fecha = as.Date(fechas)) %>%
  mutate(dia_mes = format(fecha, "%d"),
         dia_sem = format(fecha, "%A"),
     dia_sem_num = format(fecha, "%w"),
          semana = format(fecha, "%W"),
       dia_labor = (!(dia_sem_num %in% c(0,6)))*1,
             mes = format(fecha, "%B"),
         mes_num = as.numeric(format(fecha, "%m")),
             tri = format(fecha, "%Q"),
        semestre = ifelse(mes_num<=6, 1,2),
            anio = format(fecha, "%Y")
         )

  head(data_diaria,10)
price_sim      fecha dia_mes   dia_sem dia_sem_num semana dia_labor   mes mes_num tri semestre anio
1   6.074459 2010-01-01      01   viernes           5     00         1 enero       1   Q        1 2010
2   6.335057 2010-01-02      02    sábado           6     00         0 enero       1   Q        1 2010
3   6.020785 2010-01-03      03   domingo           0     00         0 enero       1   Q        1 2010
4   5.271155 2010-01-04      04     lunes           1     01         1 enero       1   Q        1 2010
5   4.408957 2010-01-05      05    martes           2     01         1 enero       1   Q        1 2010
6   6.456997 2010-01-06      06 miércoles           3     01         1 enero       1   Q        1 2010
7   7.396917 2010-01-07      07    jueves           4     01         1 enero       1   Q        1 2010
8   9.405604 2010-01-08      08   viernes           5     01         1 enero       1   Q        1 2010
9   8.984231 2010-01-09      09    sábado           6     01         0 enero       1   Q        1 2010
10  8.633396 2010-01-10      10   domingo           0     01         0 enero       1   Q        1 2010

Ahora queremos convertir los datos a semanales solo debemos indicarle que esta es la variable de agregación que necesitamos. Como R enumera cada semana dentro de un año, debemos especificarle que necesitamos que la agrupe por año, para que no nos junte todas las primeras semanas de cada año. Se coloca el ejemplo con el promedio, luego aplican todas las funciones vista en la agregación de meses a trimestres vista en la sección anterior. Note que sem_media y sem_media1 arrojan el promedio semanal y el promedio de la semana solo para lunes a viernes, usando la variable binaria de dias laborales:

data_diaria %>%
  group_by(anio,semana) %>%
  mutate(sem_media = mean(price_sim),
         sem_madia1 = mean(price_sim[dia_labor==1]))%>%
  select(price_sim,starts_with("sem"),-semestre)

# A tibble: 1,000 x 5
# Groups:   anio, semana [146]
   anio  price_sim semana sem_media sem_madia1
   <chr>     <dbl> <chr>      <dbl>      <dbl>
 1 2010       6.07 00          6.14       6.07
 2 2010       6.34 00          6.14       6.07
 3 2010       6.02 00          6.14       6.07
 4 2010       5.27 01          7.22       6.59
 5 2010       4.41 01          7.22       6.59
 6 2010       6.46 01          7.22       6.59
 7 2010       7.40 01          7.22       6.59
 8 2010       9.41 01          7.22       6.59
 9 2010       8.98 01          7.22       6.59
10 2010       8.63 01          7.22       6.59
# ... with 990 more rows

Note que en el ejemplo anterior a cada día le colocamos el promedio de la semana, pero si quisiéramos solamente obtener el promedio semanal, necesitamos agregar la semana tomando un día en específico, por ejemplo, el lunes:

data_semanal <- data_diaria %>%
  group_by(anio,semana) %>%                  
  summarise(mean_sem_stot = sum(price_sim),
            mean_sem_mtot = mean(price_sim),
            mean_sem_lab = mean(price_sim[dia_labor==1]),
            lunes = mean(price_sim[dia_sem=="lunes"]),
            max_sem_lab = max(price_sim[dia_labor==1]),
            min_sem_lab = min(price_sim[dia_labor==1]))
 
head(data_semanal,5)
# A tibble: 5 x 8
# Groups:   anio [1]
  anio  semana mean_sem_stot mean_sem_mtot mean_sem_lab  lunes max_sem_lab min_sem_lab
  <chr> <chr>          <dbl>         <dbl>        <dbl>  <dbl>       <dbl>       <dbl>
1 2010  00              18.4          6.14         6.07 NaN           6.07        6.07
2 2010  01              50.6          7.22         6.59   5.27        9.41        4.41
3 2010  02              63.4          9.05         8.35   7.61        9.75        7.36
4 2010  03              93.2         13.3         12.7   12.6        13.3        11.9
5 2010  04              89.1         12.7         13.5   13.5        14.1        12.3

Finalmente mostramos como extraraer los elementos de una serie temporal u objeto ts. Se colocan algunos ejemplos (en internet hay un montón adicionales).

  price_sim <- ts(5+cumsum(rnorm(1e3)), start = c(2015,1,1), frequency = 365)

data.frame(price_sim) %>%
  mutate(fecha = time(price_sim),
    dia_semana = format(as.yearmon(time(price_sim)), "%A"),
       dia_mes = format(as.yearmon(time(price_sim)), "%d"),
        semana = format(as.yearmon(time(price_sim)), "%W"),
           mes = as.yearmon(time(price_sim)),
       mes_num = as.numeric(format(mes, "%m")),
     trimestre = as.yearqtr(time(price_sim), format = "Q"),
      semestre = ifelse(mes_num<=6, 1,2),
          anio = as.integer(time(price_sim))
         )  %>%
  head()

price_sim    fecha dia_semana dia_mes semana       mes mes_num trimestre semestre anio
1  5.468767 2015.000     jueves      01     00 ene. 2015       1   2015 Q1        1 2015
2  6.701727 2015.003     jueves      01     00 ene. 2015       1   2015 Q1        1 2015
3  5.160069 2015.005     jueves      01     00 ene. 2015       1   2015 Q1        1 2015
4  7.149561 2015.008     jueves      01     00 ene. 2015       1   2015 Q1        1 2015
5  7.394542 2015.011     jueves      01     00 ene. 2015       1   2015 Q1        1 2015
6  8.185899 2015.014     jueves      01     00 ene. 2015       1   2015 Q1        1 2015

Creando variables por grupos en dplyr (group_by + mutate)

  Simulemos una base de hogares, donde se identifica el hogar, el sexo (1 mujer) y provincia y edad para cada miembro.   # Definir la lista ...