Вычислить разницу в весах от месяца к месяцу

#r #dplyr

#r #dplyr

Вопрос:

Я работаю над некоторыми данными портфеля, и я в тупике из-за этой манипуляции с данными. У меня есть этот образец данных

 df <- tibble(
  date = as.Date(c("2020-01-31", "2020-01-31", "2020-01-31", 
                   "2020-02-29", "2020-02-29", "2020-02-29",
                   "2020-03-31", "2020-03-31", "2020-03-31") ),
  id = c("KO", "AAPL", "MSFT",
         "KO", "AAPL", "GOOG", 
         "KO", "AAPL", "MSFT"),
  weight = c(0.3, 0.4, 0.3,
             0.5, 0.3, 0.2,
             0.6, 0.2, 0.2),
  
  `weight_change (desired column)` = c(NA, NA, NA,
                                       0.2, -0.1, 0.2,
                                       0.1, -0.1, 0.2)
) 
  

Это позиции в образце портфеля. Портфель получает новые веса каждый месяц. Я хочу рассчитать изменение веса для каждого элемента с точки зрения веса за предыдущие месяцы. В этом примере мы видим, что в конце февраля текущий вес KO равен 0,5, что на 0,2 больше, чем в предыдущем месяце. AAPL снизился на 0,1, в то время как GOOG заменяет MSFT, поэтому изменение по сравнению с предыдущим месяцем составляет весь его текущий вес: 0,2. Как я могу настроить mutate таким образом, чтобы он искал запас на предыдущую дату и вычислял разницу между весами?

Ответ №1:

Если данные ежемесячны для каждого «идентификатора», мы можем сделать a complete , чтобы учесть недостающие месяцы, а затем сгруппировать по diff

 library(dplyr)
library(tidyr)
library(zoo)    
df %>%
     mutate(yearmonth = as.Date(as.yearmon(date))) %>%
     group_by(id) %>% 
     complete(yearmonth = seq(first(yearmonth), last(yearmonth), by = '1 month')) %>%
     mutate(weight_change = if(n() == 1) weight else c(NA, diff(replace_na(weight, 0)))) %>%
     ungroup %>%
     select(names(df), weight_change) %>%
     filter(!is.na(date))
# A tibble: 9 x 5
#  date       id    weight `weight_change (desired column)` weight_change
#  <date>     <chr>  <dbl>                            <dbl>         <dbl>
#1 2020-01-31 AAPL     0.4                             NA          NA    
#2 2020-02-29 AAPL     0.3                             -0.1        -0.1  
#3 2020-03-31 AAPL     0.2                             -0.1        -0.100
#4 2020-02-29 GOOG     0.2                              0.2         0.2  
#5 2020-01-31 KO       0.3                             NA          NA    
#6 2020-02-29 KO       0.5                              0.2         0.2  
#7 2020-03-31 KO       0.6                              0.1         0.100
#8 2020-01-31 MSFT     0.3                             NA          NA    
#9 2020-03-31 MSFT     0.2                              0.2         0.2  
  

Ответ №2:

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

 library(tidyverse)
library(lubridate)

df <- tibble(
  date = c("2020-01-31", "2020-01-31", "2020-01-31", 
                   "2020-02-29", "2020-02-29", "2020-02-29",
                   "2020-03-31", "2020-03-31", "2020-03-31"),
  id = c("KO", "AAPL", "MSFT", "KO", "AAPL", "GOOG", "KO", "AAPL", "MSFT"),
  weight = c(0.3, 0.4, 0.3, 0.5, 0.3, 0.2, 0.6, 0.2, 0.2),
  `weight_change (desired_column)` = c(NA, NA, NA, 0.2, -0.1, 0.2, 0.1, -0.1, 0.2)
) %>% #new code starts here
  mutate(
    date = as_date(date),
    date_ym = floor_date(date,
                         unit = "month"))%>%
  group_by(id)%>%
  arrange(date)%>%
  mutate(id_n = row_number(),
         prev_exist = case_when(lag(date_ym) == date_ym - months(1) ~ "immediate month", #if there is an immediate month
                                id_n == 1 amp; date != min(df$date)~ "new month", #if this is a new month
                                TRUE ~ "no immediate month"),
         weight_change = case_when(prev_exist == "new month"~ weight,
                                   prev_exist == "no immediate month" amp; id_n > 1~ weight,
                                   TRUE ~ weight-lag(weight)),
         date_ym = NULL,
         id_n  = NULL,
         prev_exist = NULL)
  

Ответ №3:

Подход timetk :

 library(timetk)
df %>% 
   mutate(Month = lubridate::floor_date(date, "month")) %>%
   group_by(id) %>% 
   timetk::pad_by_time(.date_var = Month, .by="month") %>% 
   select(-Month) %>% 
   mutate(WC = if(n() == 1) weight else c(NA, diff(weight)))

A tibble: 10 x 5
Groups:   id [4]
   id    date       weight weight_change     WC
   <chr> <date>      <dbl>         <dbl>  <dbl>
 1 KO    2020-01-31    0.3          NA   NA    
 2 KO    2020-02-29    0.5           0.2  0.2  
 3 KO    2020-03-31    0.6           0.1  0.100
 4 AAPL  2020-01-31    0.4          NA   NA    
 5 AAPL  2020-02-29    0.3          -0.1 -0.1  
 6 AAPL  2020-03-31    0.2          -0.1 -0.100
 7 MSFT  2020-01-31    0.3          NA   NA    
 8 MSFT  NA           NA            NA   NA    
 9 MSFT  2020-03-31    0.2           0.2 NA    
10 GOOG  2020-02-29    0.2           0.2  0.2