Analisis Time Series pada data Online Ride Sharing (Scotty) di Turki Tujuan dari analisis ini adalah memprediksi permintaan atau order (demand) selama 7 hari ke depan untuk setiap jam dan sub-area (sxk9e,sxk9s,sxk97). Saya akan menggunakan beberapa algoritma untuk memprediksi, lalu akan dipilih model/algoritma yang paling tepat untuk melakukan prediksi.

Analisis ini menggunakan referensi dari https://algotech.netlify.com/blog/purrr-operly-fitting-multiple-time-series-model/ dalam alur dan pengerjaannya.

library(tidyverse)
library(ggplot2)
library(dplyr)
library(forecast)
library(purrr)
library(yardstick)
library(lubridate)
library(recipes)
library(magrittr)
library(plotly)
library(MLmetrics)

1 Preparasi Data

# Memasukkan data
scot <- read_csv("data_input/scotty-ts-auto/data/data-train.csv")
## Parsed with column specification:
## cols(
##   id = col_character(),
##   trip_id = col_character(),
##   driver_id = col_character(),
##   rider_id = col_character(),
##   start_time = col_datetime(format = ""),
##   src_lat = col_double(),
##   src_lon = col_double(),
##   src_area = col_character(),
##   src_sub_area = col_character(),
##   dest_lat = col_double(),
##   dest_lon = col_double(),
##   dest_area = col_character(),
##   dest_sub_area = col_character(),
##   distance = col_double(),
##   status = col_character(),
##   confirmed_time_sec = col_double()
## )
summary(scot)
##       id              trip_id           driver_id        
##  Length:90113       Length:90113       Length:90113      
##  Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character  
##                                                          
##                                                          
##                                                          
##    rider_id           start_time                     src_lat     
##  Length:90113       Min.   :2017-10-01 00:00:17   Min.   :41.00  
##  Class :character   1st Qu.:2017-10-19 19:25:25   1st Qu.:41.04  
##  Mode  :character   Median :2017-11-07 14:39:07   Median :41.06  
##                     Mean   :2017-11-04 16:37:48   Mean   :41.06  
##                     3rd Qu.:2017-11-18 22:40:43   3rd Qu.:41.07  
##                     Max.   :2017-12-02 23:59:20   Max.   :41.09  
##     src_lon        src_area         src_sub_area          dest_lat    
##  Min.   :28.96   Length:90113       Length:90113       Min.   :11.13  
##  1st Qu.:28.98   Class :character   Class :character   1st Qu.:41.04  
##  Median :28.99   Mode  :character   Mode  :character   Median :41.05  
##  Mean   :29.00                                         Mean   :41.05  
##  3rd Qu.:29.01                                         3rd Qu.:41.07  
##  Max.   :29.05                                         Max.   :52.37  
##     dest_lon       dest_area         dest_sub_area         distance       
##  Min.   : 2.407   Length:90113       Length:90113       Min.   :   0.000  
##  1st Qu.:28.974   Class :character   Class :character   1st Qu.:   2.313  
##  Median :28.994   Mode  :character   Mode  :character   Median :   3.839  
##  Mean   :28.993                                         Mean   :   5.695  
##  3rd Qu.:29.017                                         3rd Qu.:   6.453  
##  Max.   :80.192                                         Max.   :5860.002  
##     status          confirmed_time_sec
##  Length:90113       Min.   :  0.00    
##  Class :character   1st Qu.: 12.00    
##  Mode  :character   Median : 27.00    
##                     Mean   : 47.04    
##                     3rd Qu.: 67.00    
##                     Max.   :495.00

Melihat summary data di atas, kolom yang akan digunakan dalam analisis adalah kolom start_time (waktu order) dan count dari row yang akan dihitung sebagai data permintaan. Data permintaan tersebut akan di grup untuk setiap jamnya menjadi data permintaan setiap jam.

Selanjutnya, melengkapi data jam agar tersedia untuk setiap jamnya walaupun tidak ada permintaan pada jam tersebut. Hal ini dilakukan agar saat modeling, model akan akurat dalam memprediksi kondisi waktu tanpa permintaan tersebut.

# Membulatkan satuan data waktu ke jam
scot %<>% 
  mutate(datetime = floor_date(start_time,"hour")) %>% 
  group_by(src_sub_area,datetime) %>% 
  summarise(demand = length(status))

# Melengkapi data observasi jam yang kosong
scot %<>% 
  complete(datetime = seq(min(scot$datetime), max(scot$datetime), by = "hour")) %>% 
  mutate(demand = ifelse(is.na(demand), 0, demand))

head(scot)
## # A tibble: 6 x 3
## # Groups:   src_sub_area [1]
##   src_sub_area datetime            demand
##   <chr>        <dttm>               <dbl>
## 1 sxk97        2017-10-01 00:00:00      6
## 2 sxk97        2017-10-01 01:00:00      4
## 3 sxk97        2017-10-01 02:00:00      9
## 4 sxk97        2017-10-01 03:00:00      2
## 5 sxk97        2017-10-01 04:00:00      5
## 6 sxk97        2017-10-01 05:00:00      4

Visualisasi data permintaan per sub area dapat dilihat pada plot di bawah.

ggplotly(ggplot(scot,aes(x = datetime, y = demand)) +
           geom_line(aes(col = src_sub_area)) +
           labs(x = "", y = "Permintaan (order)",title = "PERMINTAAN UNTUK SETIAP SUB-AREA") +
           facet_wrap(~ src_sub_area, scale = "free_y", ncol = 1) + 
           scale_color_brewer(palette = "Pastel2") +
           theme_test(base_family = "Bebas Neue") +
           theme(legend.position = "none"))

Membuat visualisasi data dari salah satu sub-area untuk mengidentifikasi pola musiman yang muncul. Saya menggunakan skala kuadrat agar dapat memperjelas pola yang terjadi.

sxk97 <- scot %>% filter(src_sub_area == "sxk97") %>% .$demand

ggseasonplot(ts(sxk97,frequency = 24),polar = T) +
  theme_test(base_family = "Bebas Neue") +
  theme(legend.position = "none") +
  labs(title = "Pola Harian",x = "Jam") +
  scale_y_sqrt()

ggseasonplot(ts(sxk97,frequency = 24*7),polar = T) +
  theme_test(base_family = "Bebas Neue") +
  theme(legend.position = "none") +
  labs(title = "Pola Mingguan",x =NULL) +
  scale_y_sqrt() 

  # scale_x_time(limits = c(0, 360),breaks = seq(1, 359.99, by = 51.42), labels=c("Minggu","Senin","Selasa","Rabu","Kamis","Jumat","Sabtu")) 
  
ggseasonplot(ts(sxk97,frequency = 24*7*4),polar = T) +
  theme_test(base_family = "Bebas Neue") +
  theme(legend.position = "none") +
  labs(title = "Pola Bulanan",x = "") +
  scale_y_sqrt()

  # scale_x_time(limits = c(0, 360),breaks = seq(1, 359.99, by = 90), labels=c("Minggu 1","Minggu 2","Minggu 3","Minggu 4")) 

Pada plot pola harian, terlihat permintaan tertinggi ada pada jam 17-19 malam. Sedangkan permintaan terendah ada di jam 5-6 pagi. Pada plot pola mingguan (dimulai dari hari minggu pagi di arah jam 12), dapat kita lihat bahwa permintaan tertinggi muncul di hari jumat atau sabtu. Pada plot pola bulanan (dimulai tanggal 1 Oktober di arah jam 12), dapat dilihat bahwa permintaan tinggi di minggu awal bulan dan akhir bulan. Setelah melihat plot dari pola harian, mingguan, dan bulanan, saya tentukan untuk menggunakan pola harian dan mingguan saja dalam pemodelan ini. Hal ini dikarenakan pola bulanan tidak terlalu terlihat karena observasi pada dataset hanya selama 2 bulan.

Setelah menentukan pola yang ingin digunakan, saya membuat visualisasi data setelah di dekomposisi oleh pola harian dan mingguan.

dcm <- scot %>% filter(src_sub_area == "sxk97") %>% .$demand %>% msts(.,seasonal.periods = c(24,24*7))
autoplot(mstl(dcm)) + theme_test(base_family = "Bebas Neue") + labs(title = "Dekomposisi data oleh pola mingguan dan pola harian")

Trend sudah terlihat halus, hal ini mengindikasikan pola musiman yang digunakan sudah cukup tepat.

Selanjutnya, melakukan Cross Validation dengan cara membagi data menjadi data Train (untuk melatih model) dan data Test (untuk mengevaluasi model). Data Test diperoleh dari observasi selama 1 minggu terakhir dan sisanya dimasukkan ke data Train.

# Besaran waktu data test
test_size <- 24 * 7

# Menentukan awal dan akhir dari data train dan test
test_end <- max(scot$datetime)
test_start <- test_end - hours(test_size) + hours(1)

train_end <- test_start - hours(1)
train_start <- min(scot$datetime)

intrain <- interval(train_start, train_end)
intest <- interval(test_start, test_end)

# Aplikasikan interval ke dataset
scot %<>%
  mutate(sample = case_when(
    datetime %within% intrain ~ "train",
    datetime %within% intest ~ "test"
  )) %>% 
  drop_na()

head(scot)
## # A tibble: 6 x 4
## # Groups:   src_sub_area [1]
##   src_sub_area datetime            demand sample
##   <chr>        <dttm>               <dbl> <chr> 
## 1 sxk97        2017-10-01 00:00:00      6 train 
## 2 sxk97        2017-10-01 01:00:00      4 train 
## 3 sxk97        2017-10-01 02:00:00      9 train 
## 4 sxk97        2017-10-01 03:00:00      2 train 
## 5 sxk97        2017-10-01 04:00:00      5 train 
## 6 sxk97        2017-10-01 05:00:00      4 train

Melakukan scaling data sebelum memulai pemodelan. Hal ini dilakukan agar pemodelan menjadi lebih robust dan lebih tidak sensitif pada data outlier.

# Mengubah data menjadi format lebar 
scot %<>%
  spread(src_sub_area, demand)

# Jenis scaling yang dilakukan adalah square root, center, scale
rec <- recipe(~ ., filter(scot)) %>%
  step_sqrt(all_numeric()) %>%
  step_center(all_numeric()) %>%
  step_scale(all_numeric()) %>%
  prep()

# Menjalankan fungsi scaling
scot <- bake(rec, scot)

# Mengubah kembali data menjadi format tinggi
scot %<>%
  gather(src_sub_area, demand, -datetime,-sample)

head(scot)
## # A tibble: 6 x 4
##   datetime            sample src_sub_area demand
##   <dttm>              <fct>  <chr>         <dbl>
## 1 2017-10-01 00:00:00 train  sxk97        -0.612
## 2 2017-10-01 01:00:00 train  sxk97        -0.855
## 3 2017-10-01 02:00:00 train  sxk97        -0.313
## 4 2017-10-01 03:00:00 train  sxk97        -1.17 
## 5 2017-10-01 04:00:00 train  sxk97        -0.727
## 6 2017-10-01 05:00:00 train  sxk97        -0.855

Setelah melakukan scaling, tidak lupa juga membuat fungsi untuk mengembalikan data menjadi nilai dengan skala sebenarnya.

# Fungsi untuk mengembalikan data menjadi ukuran skala sebenarnya
rec_revert <- function(vector, rec, varname) {
  rec_center <- rec$steps[[2]]$means[varname]
  rec_scale <- rec$steps[[3]]$sds[varname]
  results <- (vector * rec_scale + rec_center) ^ 2
  results <- round(results)
  results
}

Selanjutnya, melakukan nest kepada data Training dan data Test agar mempermudah proses pemilihan model.

scot %<>%
  group_by(src_sub_area, sample) %>%
  nest(.key = "data") %>%
  spread(sample, data)

scot
## # A tibble: 3 x 3
##   src_sub_area test               train               
##   <chr>        <list>             <list>              
## 1 sxk97        <tibble [168 × 2]> <tibble [1,344 × 2]>
## 2 sxk9e        <tibble [168 × 2]> <tibble [1,344 × 2]>
## 3 sxk9s        <tibble [168 × 2]> <tibble [1,344 × 2]>

Membuat object time series dan multiple seasonality time series. Saya akan mencoba kedua jenis objek time series pada pemodelan dan melihat objek mana yang memberikan error terkecil saat model melakukan prediksi.

# Membuat list dari fungsi objek
data_funs <- list(
  ts = function(x) ts(x$demand, frequency = 24),
  msts = function(x) msts(x$demand, seasonal.periods = c(24, 24 * 7))
)

# Mengubah bentuk fungsi kedalam data frame yang siap disatukan dengan dataset
data_funs %<>%
  rep(length(unique(scot$src_sub_area))) %>%
  enframe("data_fun_name", "data_fun") %>%
  mutate(src_sub_area =
    sort(rep(unique(scot$src_sub_area), length(unique(.$data_fun_name))))
  )

# Penggabungan dengan dataset
scot %<>% left_join(data_funs)
## Joining, by = "src_sub_area"
head(scot)
## # A tibble: 6 x 5
##   src_sub_area test               train              data_fun_name data_fun
##   <chr>        <list>             <list>             <chr>         <list>  
## 1 sxk97        <tibble [168 × 2]> <tibble [1,344 × … ts            <fn>    
## 2 sxk97        <tibble [168 × 2]> <tibble [1,344 × … msts          <fn>    
## 3 sxk9e        <tibble [168 × 2]> <tibble [1,344 × … ts            <fn>    
## 4 sxk9e        <tibble [168 × 2]> <tibble [1,344 × … msts          <fn>    
## 5 sxk9s        <tibble [168 × 2]> <tibble [1,344 × … ts            <fn>    
## 6 sxk9s        <tibble [168 × 2]> <tibble [1,344 × … msts          <fn>

2 Model

Selanjutnya, membuat daftar algoritma model yang ingin diaplikasikan pada dataset. Saya menggunakan model:
1. Exponential Smoothing State Space Model (ets)
2. Seasonal and Trend decomposition using Loess (stlm)
3. Trigonometric seasonality, Box-Cox transformation, ARMA errors, Trend and Seasonal components (tbats)
4. Autoregressive integrated moving average (arima)
5. Holt Winter

Saya akan mencoba semua model tersebut, lalu melihat model mana yang memberikan error terkecil saat model melakukan prediksi. Khusus untuk model ets dan arima, tidak diaplikasikan pada objek multiple seasonal time series karena model tersebut tidak kompatibel dengan objek tersebut.

# Membuat list dari model 
models <- list(
  ets = function(x) ets(x),
  stlm = function(x) stlm(x),
  tbats = function(x) tbats(x),
  auto.arima = function(x) auto.arima(x),
  holt.winter = function(x) HoltWinters(x,seasonal = "additive")
)

# Mengubah bentuk fungsi kedalam data frame yang siap disatukan dengan dataset
models %<>%
  rep(length(unique(scot$src_sub_area))) %>%
  enframe("model_name", "model") %>%
  mutate(src_sub_area =
    sort(rep(unique(scot$src_sub_area), length(unique(.$model_name))))
  )
models
## # A tibble: 15 x 3
##    model_name  model  src_sub_area
##    <chr>       <list> <chr>       
##  1 ets         <fn>   sxk97       
##  2 stlm        <fn>   sxk97       
##  3 tbats       <fn>   sxk97       
##  4 auto.arima  <fn>   sxk97       
##  5 holt.winter <fn>   sxk97       
##  6 ets         <fn>   sxk9e       
##  7 stlm        <fn>   sxk9e       
##  8 tbats       <fn>   sxk9e       
##  9 auto.arima  <fn>   sxk9e       
## 10 holt.winter <fn>   sxk9e       
## 11 ets         <fn>   sxk9s       
## 12 stlm        <fn>   sxk9s       
## 13 tbats       <fn>   sxk9s       
## 14 auto.arima  <fn>   sxk9s       
## 15 holt.winter <fn>   sxk9s
# Penggabungan dengan dataset dan seleksi model ets dan arima pada objek multiple seasonal ts
scot %<>% 
  left_join(models) %>% 
  filter(!(model_name == "ets" & data_fun_name == "msts"),
         !(model_name == "auto.arima" & data_fun_name == "msts"))
## Joining, by = "src_sub_area"
head(scot)
## # A tibble: 6 x 7
##   src_sub_area test      train      data_fun_name data_fun model_name model
##   <chr>        <list>    <list>     <chr>         <list>   <chr>      <lis>
## 1 sxk97        <tibble … <tibble [… ts            <fn>     ets        <fn> 
## 2 sxk97        <tibble … <tibble [… ts            <fn>     stlm       <fn> 
## 3 sxk97        <tibble … <tibble [… ts            <fn>     tbats      <fn> 
## 4 sxk97        <tibble … <tibble [… ts            <fn>     auto.arima <fn> 
## 5 sxk97        <tibble … <tibble [… ts            <fn>     holt.wint… <fn> 
## 6 sxk97        <tibble … <tibble [… msts          <fn>     stlm       <fn>

Mengaplikasikan fungsi pembuatan objek time series dan algoritma pemodelan pada dataset.

scot %<>%
  mutate(
    params = map(train, ~ list(x = .x)),
    data = invoke_map(data_fun, params),
    params = map(data, ~ list(x = .x)),
    fitted = invoke_map(model, params)
  ) %>%
  select(-data, -params)
## Warning in HoltWinters(x, seasonal = "additive"): optimization
## difficulties: ERROR: ABNORMAL_TERMINATION_IN_LNSRCH
scot
## # A tibble: 24 x 8
##    src_sub_area test   train data_fun_name data_fun model_name model fitted
##    <chr>        <list> <lis> <chr>         <list>   <chr>      <lis> <list>
##  1 sxk97        <tibb… <tib… ts            <fn>     ets        <fn>  <ets> 
##  2 sxk97        <tibb… <tib… ts            <fn>     stlm       <fn>  <stlm>
##  3 sxk97        <tibb… <tib… ts            <fn>     tbats      <fn>  <tbat…
##  4 sxk97        <tibb… <tib… ts            <fn>     auto.arima <fn>  <ARIM…
##  5 sxk97        <tibb… <tib… ts            <fn>     holt.wint… <fn>  <HltW…
##  6 sxk97        <tibb… <tib… msts          <fn>     stlm       <fn>  <stlm>
##  7 sxk97        <tibb… <tib… msts          <fn>     tbats      <fn>  <tbat…
##  8 sxk97        <tibb… <tib… msts          <fn>     holt.wint… <fn>  <HltW…
##  9 sxk9e        <tibb… <tib… ts            <fn>     ets        <fn>  <ets> 
## 10 sxk9e        <tibb… <tib… ts            <fn>     stlm       <fn>  <stlm>
## # … with 14 more rows

3 Evaluasi

Menghitung error pada prediksi yang dilakukan setiap model terhadap data Test.

scot %<>%
  mutate(error =
    map(fitted, ~ forecast(.x, h = 24 * 7)) %>%
    map2_dbl(test, ~ mae_vec(truth = rec_revert(.y$demand,rec,src_sub_area), estimate = rec_revert(.x$mean,rec,src_sub_area)))) %>% 
  arrange(src_sub_area, error) 
  

scot %>%
  select(src_sub_area, ends_with("_name"), error)
## # A tibble: 24 x 4
##    src_sub_area data_fun_name model_name  error
##    <chr>        <chr>         <chr>       <dbl>
##  1 sxk97        msts          tbats       10.1 
##  2 sxk97        msts          stlm        11.2 
##  3 sxk97        msts          holt.winter 11.3 
##  4 sxk97        ts            ets         11.8 
##  5 sxk97        ts            tbats       11.9 
##  6 sxk97        ts            holt.winter 12.1 
##  7 sxk97        ts            stlm        12.4 
##  8 sxk97        ts            auto.arima  15.2 
##  9 sxk9e        msts          tbats        7.57
## 10 sxk9e        msts          holt.winter  7.98
## # … with 14 more rows

Selanjutnya, membuat visualisasi yang memperlihatkan bagaimana perbedaan hasil prediksi dari setiap model (warna hijau) jika dibandingan dengan data permintaan riil (warna hitam).

# Melakukan forecast pada data test
scot_test <- scot %>%
  mutate(
    forecast =
      map(fitted, ~ forecast(.x, h = 24 * 7)) %>%
      map2(test, ~ tibble(
        datetime = .y$datetime,
        demand = as.vector(.x$mean)
      )),
    key = paste(data_fun_name, model_name, sep = "-")
  )

# Membentuk data untuk visualisasi 
scot_test %<>%
  select(src_sub_area, key, actual = test, forecast) %>%
  spread(key, forecast) %>%
  gather(key, value, -src_sub_area) %>% 
  unnest(value) %>% 
  mutate(demand = rec_revert(demand,rec,src_sub_area))

# Visualisasi
color <- "forestgreen"

ggplotly(ggplot(scot_test,aes(x = datetime, y = demand)) +
           geom_line(data = scot_test %>% filter(key == "actual"),aes(y = demand),alpha = 0.2,size = 0.8)+
           geom_line(data = scot_test %>% filter(key != "actual"),aes(frame = key,col = key)) +
           labs(x = "", y = "Permintaan (order)",title = "PERBANDINGAN HASIL PREDIKSI MODEL", frame = "Models") +
           facet_wrap(~ src_sub_area, scale = "free_y", ncol = 1) +
           scale_color_manual(values = c(color,color,color,color,color,color,color,color,color)) +
           theme_test(base_family = "Bebas Neue") +
           theme(legend.position = "none"))
## Warning: Ignoring unknown aesthetics: frame
## Warning in p$x$data[firstFrame] <- p$x$frames[[1]]$data: number of items to
## replace is not a multiple of replacement length

Memilih model yang memiliki error prediksi terkecil untuk setiap sub-area.

# Seleksi error terkecil
scot %<>%
  select(-fitted) %>% # remove unused
  group_by(src_sub_area) %>%
  filter(error == min(error)) %>%
  ungroup()

# Menggabungkan data test dan train
scot %<>%
  mutate(fulldata = map2(train, test, ~ bind_rows(.x, .y))) %>%
  select(src_sub_area, fulldata, everything(), -train, -test)

scot
## # A tibble: 3 x 7
##   src_sub_area fulldata       data_fun_name data_fun model_name model error
##   <chr>        <list>         <chr>         <list>   <chr>      <lis> <dbl>
## 1 sxk97        <tibble [1,51… msts          <fn>     tbats      <fn>  10.1 
## 2 sxk9e        <tibble [1,51… msts          <fn>     tbats      <fn>   7.57
## 3 sxk9s        <tibble [1,51… msts          <fn>     tbats      <fn>   8.28

Model tbats memiliki error prediksi terkecil untuk setiap sub-area. Selanjutnya menggabungkan data Test dan data Train untuk membuat model final.

Melakukan pemodelan dengan model tbats terhadap dataset yang sudah di gabung, lalu melakukan prediksi permintaan selama 7 hari kedepan.

# Menjalankan model
scot %<>%
  mutate(
    params = map(fulldata, ~ list(x = .x)),
    data = invoke_map(data_fun, params),
    params = map(data, ~ list(x = .x)),
    fitted = invoke_map(model, params)
  ) %>%
  select(-data, -params)

# Melakukan prediksi
scot %<>%
  mutate(forecast =
    map(fitted, ~ forecast(.x, h = 24 * 7)) %>%
    map2(fulldata, ~ tibble(
      datetime = timetk::tk_make_future_timeseries(.y$datetime, 24 * 7),
      demand = as.vector(.x$mean)
    ))
  )

scot
## # A tibble: 3 x 9
##   src_sub_area fulldata data_fun_name data_fun model_name model error
##   <chr>        <list>   <chr>         <list>   <chr>      <lis> <dbl>
## 1 sxk97        <tibble… msts          <fn>     tbats      <fn>  10.1 
## 2 sxk9e        <tibble… msts          <fn>     tbats      <fn>   7.57
## 3 sxk9s        <tibble… msts          <fn>     tbats      <fn>   8.28
## # … with 2 more variables: fitted <list>, forecast <list>

Membuka nest data dan membuat visualisasi dari hasil prediksi.

# Membuka nest data
scot %<>%
  select(src_sub_area, actual = fulldata, forecast) %>%
  gather(key, value, -src_sub_area) %>%
  unnest(value) %>% 
  mutate(demand = rec_revert(demand,rec,src_sub_area))

# Visualisasi
ggplotly(ggplot(scot,aes(x = datetime, y = demand, colour = key)) +
           geom_line() +
           labs(y = "Permintaan (Order)", x = NULL, title = "HASIL PREDIKSI MODEL") +
           facet_wrap(~ src_sub_area, scale = "free_y", ncol = 1) +
           scale_color_brewer(palette = "Pastel2") +
           theme_test(base_family = "Bebas Neue") +
           theme(legend.position = "none"))
# predict <- scot %>% 
#   filter(key == "forecast") %>% 
#   select(-key)
# 
# head(predict)
# 
# write_csv(predict,"data_input/scotty-ts-auto/data/submission-0001HB.csv")