Tengo una tabla de 2 columnas (tibble), compuesta por un objeto de fecha y una variable numérica. Hay un máximo de una entrada por día, pero no todos los días tienen una entrada (es decir, la fecha es una clave primaria natural). Estoy tratando de hacer una suma acumulada de la columna numérica junto con las fechas, pero la suma acumulada se restablece cuando el mes cambia (los datos se ordenan por fecha ascendente). He replicado lo que quiero obtener como resultado a continuación.

Date         score  monthly.running.sum
10/2/2019       7       7
10/9/2019       6       13
10/16/2019      12      25
10/23/2019      2       27
10/30/2019      13      40
11/6/2019       2       2
11/13/2019      4       6
11/20/2019      15      21
11/27/2019      16      37
12/4/2019       4       4
12/11/2019      24      28
12/18/2019      28      56
12/25/2019      8       64
1/1/2020        1       1
1/8/2020        15      16
1/15/2020       9       25
1/22/2020       8       33

Parece que el paquete "runner" posiblemente sea adecuado para esto, pero realmente no entiendo cómo instruirlo. Sé que podría usar una operación join más un group_by usando dplyr para hacer esto, pero el conjunto de datos es muy grande y hacerlo sería muy ineficiente. También podría iterar manualmente a través de la lista con un bucle, pero eso también parece poco elegante. La última opción que se me ocurre es seleccionar un vector único de objetos yearmon y luego cortar la lista original en muchas listas más cortas y ejecutar un simple cumsum en él, pero eso también se siente poco óptimo. Estoy seguro de que esta no es la primera vez que alguien tiene que hacer esto, y dada la cantidad de herramientas que hay en tidyverse para hacer las cosas, creo que solo necesito ayuda para encontrar la correcta. La razón por la que estoy buscando una herramienta en lugar de usar uno de los métodos que describí anteriormente (que tomaría menos tiempo que escribir esta publicación) es porque este código debe ser muy legible para una audiencia que se siente menos cómoda con el código.

2
Guillermo 28 abr. 2020 a las 03:24

4 respuestas

La mejor respuesta

Usando lubridate, puede extraer los valores de mes y año de la fecha, group_by esos valores y ellos realizan la suma acumulativa de la siguiente manera:

library(lubridate)
library(dplyr)

df %>% mutate(Month = month(mdy(Date)),
              Year = year(mdy(Date))) %>%
  group_by(Month, Year) %>%
  mutate(SUM = cumsum(score))

# A tibble: 17 x 6
# Groups:   Month, Year [4]
   Date       score monthly.running.sum Month  Year   SUM
   <chr>      <int>               <int> <int> <int> <int>
 1 10/2/2019      7                   7    10  2019     7
 2 10/9/2019      6                  13    10  2019    13
 3 10/16/2019    12                  25    10  2019    25
 4 10/23/2019     2                  27    10  2019    27
 5 10/30/2019    13                  40    10  2019    40
 6 11/6/2019      2                   2    11  2019     2
 7 11/13/2019     4                   6    11  2019     6
 8 11/20/2019    15                  21    11  2019    21
 9 11/27/2019    16                  37    11  2019    37
10 12/4/2019      4                   4    12  2019     4
11 12/11/2019    24                  28    12  2019    28
12 12/18/2019    28                  56    12  2019    56
13 12/25/2019     8                  64    12  2019    64
14 1/1/2020       1                   1     1  2020     1
15 1/8/2020      15                  16     1  2020    16
16 1/15/2020      9                  25     1  2020    25
17 1/22/2020      8                  33     1  2020    33

Una alternativa será utilizar la función floor_date para convertir cada fecha como el primer día de cada mes y calcular la suma acumulativa:

library(lubridate)
library(dplyr)

df %>% mutate(Floor = floor_date(mdy(Date), unit = "month")) %>%
  group_by(Floor) %>%
  mutate(SUM = cumsum(score))

# A tibble: 17 x 5
# Groups:   Floor [4]
   Date       score monthly.running.sum Floor        SUM
   <chr>      <int>               <int> <date>     <int>
 1 10/2/2019      7                   7 2019-10-01     7
 2 10/9/2019      6                  13 2019-10-01    13
 3 10/16/2019    12                  25 2019-10-01    25
 4 10/23/2019     2                  27 2019-10-01    27
 5 10/30/2019    13                  40 2019-10-01    40
 6 11/6/2019      2                   2 2019-11-01     2
 7 11/13/2019     4                   6 2019-11-01     6
 8 11/20/2019    15                  21 2019-11-01    21
 9 11/27/2019    16                  37 2019-11-01    37
10 12/4/2019      4                   4 2019-12-01     4
11 12/11/2019    24                  28 2019-12-01    28
12 12/18/2019    28                  56 2019-12-01    56
13 12/25/2019     8                  64 2019-12-01    64
14 1/1/2020       1                   1 2020-01-01     1
15 1/8/2020      15                  16 2020-01-01    16
16 1/15/2020      9                  25 2020-01-01    25
17 1/22/2020      8                  33 2020-01-01    33
2
dc37 28 abr. 2020 a las 00:31

La clase yearmon representa los objetos año / mes, así que simplemente convierta las fechas a yearmon y acumúlelos usando este unineador:

library(zoo)

transform(DF, run.sum = ave(score, as.yearmon(Date, "%m/%d/%Y"), FUN = cumsum))

Dando:

         Date score run.sum
1   10/2/2019     7       7
2   10/9/2019     6      13
3  10/16/2019    12      25
4  10/23/2019     2      27
5  10/30/2019    13      40
6   11/6/2019     2       2
7  11/13/2019     4       6
8  11/20/2019    15      21
9  11/27/2019    16      37
10  12/4/2019     4       4
11 12/11/2019    24      28
12 12/18/2019    28      56
13 12/25/2019     8      64
14   1/1/2020     1       1
15   1/8/2020    15      16
16  1/15/2020     9      25
17  1/22/2020     8      33
1
G. Grothendieck 28 abr. 2020 a las 02:05

Una alternativa base R:

df$Date <- as.Date(df$Date, "%m/%d/%Y")
df$monthly.running.sum <- with(df, ave(score, format(Date, "%Y-%m"),FUN = cumsum))
df

#         Date score monthly.running.sum
#1  2019-10-02     7                   7
#2  2019-10-09     6                  13
#3  2019-10-16    12                  25
#4  2019-10-23     2                  27
#5  2019-10-30    13                  40
#6  2019-11-06     2                   2
#7  2019-11-13     4                   6
#8  2019-11-20    15                  21
#9  2019-11-27    16                  37
#10 2019-12-04     4                   4
#11 2019-12-11    24                  28
#12 2019-12-18    28                  56
#13 2019-12-25     8                  64
#14 2020-01-01     1                   1
#15 2020-01-08    15                  16
#16 2020-01-15     9                  25
#17 2020-01-22     8                  33
2
Ronak Shah 28 abr. 2020 a las 00:40

También podemos usar data.table

library(data.table)
setDT(df)[, Date := as.IDate(Date, "%m/%d/%Y")
           ][, monthly.running.sum :=  cumsum(score),format(Date, "%Y-%m")][]
#          Date score monthly.running.sum
# 1: 2019-10-02     7                   7
# 2: 2019-10-09     6                  13
# 3: 2019-10-16    12                  25
# 4: 2019-10-23     2                  27
# 5: 2019-10-30    13                  40
# 6: 2019-11-06     2                   2
# 7: 2019-11-13     4                   6
# 8: 2019-11-20    15                  21
# 9: 2019-11-27    16                  37
#10: 2019-12-04     4                   4
#11: 2019-12-11    24                  28
#12: 2019-12-18    28                  56
#13: 2019-12-25     8                  64
#14: 2020-01-01     1                   1
#15: 2020-01-08    15                  16
#16: 2020-01-15     9                  25
#17: 2020-01-22     8                  33

Datos

df <- structure(list(Date = c("10/2/2019", "10/9/2019", "10/16/2019", 
"10/23/2019", "10/30/2019", "11/6/2019", "11/13/2019", "11/20/2019", 
"11/27/2019", "12/4/2019", "12/11/2019", "12/18/2019", "12/25/2019", 
"1/1/2020", "1/8/2020", "1/15/2020", "1/22/2020"), score = c(7L, 
6L, 12L, 2L, 13L, 2L, 4L, 15L, 16L, 4L, 24L, 28L, 8L, 1L, 15L, 
9L, 8L)), row.names = c(NA, -17L), class = "data.frame")
1
akrun 28 abr. 2020 a las 01:09