Thực hành trong R

Việt Nam, 2024

Author

Cao Xuân Lộc

Published

October 6, 2024

1 Chuẩn bị:

Code
#Call packages:
pacman::p_load(rio,
               here,
               janitor,
               tidyverse,
               dplyr,
               magrittr,
               lubridate,
               stringr
               )

Dưới đây là tệp dữ liệu về nhu cầu của khách hàng ở 3 nhà kho khác nhau. Bạn có thể nhấn vào nút dưới đây để tải về.

2 Lập kế hoạch MRP:

2.1 Dự đoán nhu cầu:

Sau khi đã có dữ liệu, chúng ta sẽ bắt đầu dự đoán nhu cầu của khách hàng dựa vào dữ liệu trong quá khứ bằng thư viện modeltime. Mình đã từng viết 1 bài hướng dẫn cách sử dụng thư viện này ở bài viết Time series model 2, bạn có thể xem qua.

Code
library(timetk)
demand<-df %>% 
    pivot_longer(cols = c(WHA,WHB,WHC), 
               names_to = "WH", 
               values_to = "Sale")
  
p<-demand %>% 
  group_by(WH) %>% 
  plot_time_series(Date, 
                   Sale, 
                   .interactive = T,
                   .color_var = month(Date))

#| warning: false
#| message: false
library(parsnip)
library(rsample)
# Devide the dataset to 7:3
splits <- initial_time_split(demand, prop = 0.7)
# ---- AUTO ARIMA ----

library(modeltime)
# Model Spec
model_spec <- arima_reg() %>%
    set_engine("auto_arima")

# Fit model:
model_fit <- model_spec %>%
    fit(Sale ~ Date, 
        data = training(splits))

# Evaluate the model:
invisible(capture.output({
modeltime_tbl <- modeltime_table(
    model_fit
) 
k<-modeltime_tbl %>%
    modeltime_calibrate(testing(splits)) %>%
    modeltime_forecast(
      new_data    = testing(splits),
      actual_data = demand,
      keep_data   = TRUE
    )
}))

Mô hình đưa ra có vẻ khá tốt vì sai số trung bình chỉ ở mức 5% đối với nhà kho A và C, nhà kho B thì tệ hơn một chút với mức 10%.

Bảng 2: Dự đoán dữ liệu bằng package modeltime

Summary Statistics

Source: package gt in R

WH Observation Mean error (%) Median error (%) Total pass Total fail
WHA 27 0.0400 0.04 15 12
WHB 27 0.0922 0.09 7 20
WHC 28 0.0586 0.06 9 19

Bảng 3: Đánh giá mức độ dự đoán của mô hình

Bảng 3: Đánh giá mức độ dự đoán của mô hình

Vậy chúng ta đã có dữ liệu đầu vào là dự đoán nhu cầu. Tiếp theo, ta sẽ xây dựng kế hoạch cung ứng hàng hóa dựa trên kết quả trên. Kế hoạch cung ứng nghĩa nhằm đảm bảo rằng hàng hóa và dịch vụ được cung cấp đúng thời điểm, đúng số lượng và với chi phí hợp lý.

Thông thường, kế hoạch cung ứng chỉ gồm 4 thông tin đơn giản là:

  • Địa điểm bốc hàng.
  • Địa điểm giao hàng.
  • Loại hàng hóa.
  • Số lượng hàng.

Và kế hoạch có thể theo ngày đối với các cửa hàng bách hóa, siêu thị hoặc theo tuần đối với các nhà kho. Ngoài ra, còn tùy vào mặt hàng là hàng hóa gì, nhu cầu của hàng hóa như thể nào cũng ảnh hưởng đến thời gian giao hàng như: hàng bán chạy thì cần cung ứng hằng ngày, hàng ế ẩm thì có khi cả tháng mới nhập hoặc xuất một lần.

Như vậy, ta cần xác định khi nào hàng sẽ có nguy cơ bị outstock để lập kế hoạch cung ứng cho khoảng thời gian là 1 tháng tiếp theo.

2.2 Các thông số của nhà kho:

Mỗi nhà kho sẽ có các thông số đánh giá khác nhau. Đối với nhà kho thuộc dạng public warehouse thì họ quan tâm về mức độ lấp đầy chỗ (giống như mua vé xem phim vậy, càng ít chỗ trống nghĩa là doanh thu cao), private warehouse thì thường sẽ quan tâm đến các dịch vụ cao cấp hơn như: kho lạnh, phòng cháy chữa cháy, dạng cross-docking thì đặc biệt quan tâm đến tốc độ chu chuyển hàng hóa trong kho,… Nhưng về tổng quan, để quản lí hàng hóa trong kho sẽ cần các thông số như:

  • Mean of demand: nhu cầu trung bình theo ngày/tháng/năm của sản phẩm.

  • Standard deviation of demand: phương sai của sản phẩm.

  • Safety stockLead time.

Đầu tiên, ta sẽ tính các giá trị Lead timeSafety stock cho từng nhà kho.

Code
library(dplyr)
library(knitr)

safety_stock_tbl <- k %>%
  group_by(WH) %>%
  summarise(Mean = round(mean(.value),0),
            SD = sd(.value)) %>%
  mutate(Leadtime = c("2 days", "1.5 days", "2.5 days"),
         Safety_stock = round(1.64 * SD * sqrt(as.numeric(sub(" days", "", Leadtime))),0),
         ROP = Mean * as.numeric(sub(" days", "", Leadtime)) + Safety_stock)

safety_stock_tbl %>%
  gt() %>%
  tab_header(
    title = md("**Safety Stock by Warehouse**"),
    subtitle = md("*Source: package gt in R*")
  ) %>%
  cols_label(
    WH = "Warehouse",
    Mean = "Mean",
    SD = "Standard Deviation",
    Leadtime = "Lead Time",
    Safety_stock = "Safety Stock",
    ROP = "Reorder Point"
  ) %>% tab_style(
    style = list(
      cell_text(align = "center")
    ),
    locations = cells_body(columns = everything())
  ) %>%
   cols_align(
    align = "center",
    columns = everything()
  ) %>% 
  gt_theme_pff() %>% 
  tab_options(
    table.width = "80%"
  )

Safety Stock by Warehouse

Source: package gt in R

Warehouse Mean Standard Deviation Lead Time Safety Stock Reorder Point
WHA 166 7.506268 2 days 17 349.0
WHB 147 10.361962 1.5 days 21 241.5
WHC 192 11.803806 2.5 days 31 511.0

Bảng 4: Các thông số đánh giá của từng nhà kho

Sau khi đã có các thông số cần thiết, chúng ta sẽ bắt đầu bước simulation - giả lập cho số lượng hàng tồn kho trong gần 1 tháng tiếp theo. Ở đây mình sẽ xây dựng kế hoạch cho nhà kho B. Trong đó, hàm dưới đây mình viết giúp bạn có thể tính toán cho nhiều trường hợp với 4 đối số:

  • Mã nhà kho: trong bộ dữ liệu này sẽ có 3 nhà kho với các mã: “WHA”, “WHB”, “WHC”. Ở đây, mình đặt warehouse = "WHB".

  • Batch order: là số lượng hàng đặt từ supplier. Tùy vào đặc tính của sản phẩm, số lượng đặt hàng có thể chênh lệch so với số lượng mà bạn cần. Ví dụ, bạn chỉ cần 450 tấn là vừa đủ nhưng 1 lô hàng mà nhà cung cấp sản xuất sẽ là 500 tấn (quy mô thấp hơn thì chi phí/bao sẽ cao hơn) nên bạn chỉ có thể đặt 500 tấn chứ đặt 450 thì 2 bên sẽ không thỏa thuận được. Ở đây mình đặt quantity = 600.

  • Leadtime: khái niệm này mình cũng đề cập ở trên nhưng ở đây bạn có thể cân nhắc việc cộng thêm 1 khoảng safety leadtime để nâng cao hiệu quả công việc. Ví dụ, leadtime của nhà kho C là 2.5 ngày thì mình có thể đặt hàng sớm trước 1 ngày so với ngày dự kiến ROP.

  • Starting inventory: lượng hàng tồn kho đầu kì. Ở đây mình đặt inv_start = 500.

Code
long_df<-df %>% 
  pivot_longer(cols = contains("WH"),
               names_to = "Warehouse",
               values_to = "Demand")

f<-function(warehouse,quantity,leadtime,inv_start){

## Inpur:
batch_order <- quantity
safetystock <- safety_stock_tbl %>%
  filter(WH == warehouse) %>%
  select(ROP) %>%
  pull()

mrp<-long_df %>% 
  filter(Date >= as.Date("2024-06-02") & Warehouse == warehouse) %>%
  select(c(Date, Demand)) %>% 
  mutate(Inv_start= NA,
         Inv_end = NA,
         ROP = NA,
         Stockout = NA)

mrp$Inv_start[1]<-inv_start
mrp$Inv_end[1] <-mrp$Inv_start[1]-mrp$Demand[1]

## Processing:
reorder_goods <- function(data, 
                          safety_stock = safetystock, 
                          lead_time = leadtime, 
                          batch_order = quantity) {
  data$ROP <- 0
  last_reorder_day <- -(leadtime + 1)  # Initialize to a value that allows immediate reorder

  for (day in 1:nrow(data)) {
    # Check if we can place a new order (ROP = 1)
    if (!is.na(data$Inv_end[day]) && 
        data$Inv_end[day] < safety_stock && 
        (day - last_reorder_day > leadtime)) {  # Ensure at least 2 days since last reorder
      data$ROP[day] <- 1
      last_reorder_day <- day  # Update the last reorder day

      reorder_day <- day + lead_time

      if (reorder_day <= nrow(data)) {
        data$Inv_end[reorder_day] <- ifelse(is.na(data$Inv_end[reorder_day]), 0, data$Inv_end[reorder_day]) + batch_order
        
        for (i in (reorder_day+1):nrow(data)) {
          data$Inv_start[i] <- data$Inv_end[i - 1]
          data$Inv_end[i] <- data$Inv_start[i] - data$Demand[i]
        }
      }
    }
    
   # Assign Stockout status

  }
  
  return(data)
}


# Fill in Inv_end for the rest of the days
for (i in 2:nrow(mrp)) {
  mrp$Inv_start[i] <- mrp$Inv_end[i - 1]
  mrp$Inv_end[i] <- mrp$Inv_start[i] - mrp$Demand[i]
}

# Apply the reorder function
result <- reorder_goods(mrp)

for (i in 1:nrow(result)){
  result$Stockout[i] <- ifelse(result$Inv_start[i] < result$Demand[i], 1, 0)
}
return(result)
}

result<-f("WHB",600,3,500)

Kết quả giả lập được trình bày như sau, trong đó:

  • Starting inv: là tồn kho đầu ngày (hoặc đầu kì cho tháng/năm). Lượng hàng đầu ngày sẽ bằng lượng hàng cuối ngày hôm trước.
  • Ending inv: là tồn kho cuối ngày, được tính bằng hiệu số của lượng hàng đầu ngày và nhu cầu của khách hàng.
  • Status: ám chỉ trạng thái của nhà kho, ở đây có 3 dạng trạng thái: Oke là khi nhà kho không bị outstock và không cần đặt thêm hàng, Reorder khi đặt đơn hàng và Outstock khi lượng hàng tồn kho không đủ để phục vụ nhu cầu của khách hàng vào ngày đó.
  • Supply: lượng hàng sẽ nhận từ đơn đặt hàng.
Code
library(reactable)
library(reactablefmtr)
library(htmltools)

# create a function status.PI.Index
status_PI.Index <- function(color = "#aaa", width = "0.55rem", height = width) {
  span(style = list(
    display = "inline-block",
    marginRight = "0.5rem",
    width = width,
    height = height,
    backgroundColor = color,
    borderRadius = "50%"
  ))
}
# Reactable:
table<-result %>% 
            mutate(Date = as.Date(Date),
                   Status = ifelse(ROP == 0 & Stockout == 0, "Oke",
                                   ifelse(ROP == 1  & Stockout == 0, "Reorder","Overstock")),
                   Supply = ROP*600) %>% 
                     select(-c(ROP,Stockout))

reactable(table,
  columns = list(
    Date = colDef(name = "Date", 
                   sortable = TRUE, 
                   align = "center", 
                   headerStyle = list(background = "#b0b0b0")),  
    Demand = colDef(name = "Demand", 
                    align = "center", 
                    headerStyle = list(background = "#b0b0b0"),
                    cell = data_bars(table, 
                               fill_color = "#3fc1c5",
                               text_position = "outside-end")
            ),  
    Inv_start = colDef(name = "Starting Inv", 
                       align = "center", 
                       headerStyle = list(background = "#b0b0b0"),
                       style = function(value) {
                         color <- ifelse(value <= 0, "#e00000", "#008000")  
                         list(color = color, fontWeight = "bold")
                       }),
    Supply = colDef(name = "Supply (units)",
                    align = "center",
                    headerStyle = list(background = "#b0b0b0"),
                    cell = data_bars(table, 
                               fill_color = "#3CB371",
                               text_position = "outside-end")
            ),
    Inv_end = colDef(name = "Ending Inv", 
                     align = "center", 
                     headerStyle = list(background = "#b0b0b0"),
                     style = function(value) {
                       color <- ifelse(value <= 0, "#e00000", "#008000")  
                       list(color = color, fontWeight = "bold")
                     }),
    Status = colDef(
              name = "Status",
                     headerStyle = list(background = "#b0b0b0"),
              cell = function(value) {
                color <- switch(value,
                                Overstock = "hsl(3, 69%, 50%)",
                                Oke = "hsl(154, 64%, 50%)",
                                Reorder = "hsl(214, 45%, 50%)")
                Status <- status_PI.Index(color = color)
                tagList(Status, value)
              }
            )),
  defaultPageSize = 10,
  highlight = TRUE,
  striped = TRUE,
  bordered = TRUE,
  resizable = TRUE
)

Bảng 5: Kết quả giả lập của kế hoạch MRP

2.3 Thêm safety leadtime:

Bây giờ chúng ta đã có kế hoạch dự kiến trong tháng tiếp theo giống ta đã biết trước được khi nào thì nhà kho sẽ bị outstock và dựa vào đó, ta có thể đưa ra phương án để tránh việc này.

Như trong trường hợp này, nhà kho B bị outstock tới tận 16 lận, một con số khá tệ. Là người quản lí kho, ta sẽ đưa ra phương án là đặt hàng trước 1 ngày dự kiến mà lượng hàng tồn kho nhỏ hơn safety stock.

2.3.1 So sánh giữa 2 cách:

Kết quả: Qua biểu đồ dưới đây, ta cũng dễ dàng thấy là việc đặt hàng trước 1 ngày hàng tồn kho nhỏ hơn safety stock giảm tình trạng outstock đáng kể (từ 16 lần xuống còn 6 lần trong tháng).

Ngoài ra, một điểm đặc biệt là không xảy ra tình trạng nợ hàng, nghĩa là cửa hàng của bạn chỉ bị thiếu hàng và chưa đủ thỏa mãn hết nhu cầu trong ngày của khách hàng chớ không bị âm hàng như cách cũ.

Bảng 6: So sánh giữa phương pháp truyền thống và thêm safety leadtime

2.3.2 Đánh giá bằng line chart:

Để so sánh sự quan trọng của safety leadtime, mình sẽ sử dụng line chart để đánh giá mức độ dịch vụ của nhà kho dựa trên tiêu chí high service: càng ít outstock thì càng tốt. Ngoài ra, mình còn thêm vào các đường nét đứt biểu diễn thời điểm ROP

Code
# Create time series objects with daily frequency
library(xts)
demand_ts <-xts(result$Demand, 
                order.by = result$Date)
inv_start_ts <- xts(result$Inv_start, 
                   order.by = result$Date)
inv_start_safety_ts <- xts(result$Inv_start_safety,
                           order.by = result$Date)

event<-result %>% 
  filter(ROP == 1) %>% 
  select(Date) %>% 
  mutate(Date = as.Date(Date)) %>% 
  pull()

# Create line plot:
library(dygraphs)
combine <- cbind(demand_ts,
                 inv_start_ts,
                 inv_start_safety_ts)

dygraph(combine) %>%
  dySeries("demand_ts", label = "Demand") %>%
  dySeries("inv_start_ts", label = "Starting inv") %>% 
  dySeries("inv_start_safety_ts", label = "Starting safety inv") %>% 
  dyOptions(fillGraph = TRUE, fillAlpha = 0.4) %>% 
  dyEvent(event[1], "ROP", labelLoc = "bottom") %>%
  dyEvent(event[2], "ROP", labelLoc = "bottom") %>%
  dyEvent(event[3], "ROP", labelLoc = "bottom") %>%
  dyEvent(event[4], "ROP", labelLoc = "bottom") %>%
  dyEvent(event[5], "ROP", labelLoc = "bottom") %>%
  dyEvent(event[6], "ROP", labelLoc = "bottom")

Bảng 7: So sánh level of service của hai phương pháp

Sau khi đã có lịch đặt hàng dự kiến của finished product, ta sẽ xây dựng tiếp các kế hoạch đặt hàng cho raw material hoặc components. Công thức tính toán ngày sẽ là:

\[ \text{Reorder day of material} = \text{Reorder day of finished product} - \text{Lead time of material} \]

Tip

Leadtime của mỗi material sẽ được thể hiện trong bảng BOM mà mình đã trình bày ở trang trước.

Ví dụ ta cần 2 material với leadtime là 3 ngày để tạo ra 1 component và cuối cùng tốn thêm 2 ngày nữa để tạo ra finished product và mình sẽ sử dụng package ggweekly của gadenbuie để xây dựng thời khoa biểu cho lịch đặt hàng. Dựa vào lịch trình này, ta sẽ biết được khi nào cần đặt hàng và sẽ có thể thông báo qua điện thoại nếu bạn sử dụng package googlecalendar của benjcunningham với API của Google.

Code
# Step 1: Create calendar:
library(ggweekly)
calendar<-result |> 
  select(c(Date, ROP_safety)) |> 
  filter(ROP_safety == 1) |> 
   rename(Finished_product = ROP_safety) |> 
  mutate(Finished_product = 600) 
 
component1<-calendar |> 
  rename(Component = Finished_product) |> 
  mutate(Date = Date - days(2),
         Component = 600)

material1<-component1 |> 
  rename(Material_1 = Component) |>
  mutate(Date = Date - days(2),
         Material_1 = 600*2)

material2<-component1 |> 
  rename(Material_2 = Component) |>
  mutate(Date = Date - days(3),
         Material_2 = 600*3)


# Step 2: Create a full sequence of dates
calendar$Date <- as.Date(calendar$Date)
material1$Date <- as.Date(material1$Date)

full_date_sequence <- seq.Date(from = min(material1$Date), to = max(calendar$Date), 
                               by = "day")

full_dates <- data.frame(Date = full_date_sequence)

# Step 3: Join the full sequence with your data
final_result <- full_dates |>
  left_join(calendar, by = "Date") |>
  left_join(component1, by = "Date") |>
  left_join(material1, by = "Date") |>
  left_join(material2, by = "Date") |>
  arrange(desc(Date)) 

final_result <- final_result %>%
  mutate(across(c(Finished_product,Component, Material_1, Material_2), ~replace(., is.na(.), 0)))
Code
# Step 4: Create table to present the result:
final_result |>
  mutate(Date = as.Date(Date)) |> 
  reactable(
    columns = list(
      Finished_product = colDef(
        name = "Finished product",
        cell = data_bars(final_result, 
                         fill_color = c("#ffffff","#4CAF50"),
                         text_position = "inside-end")
      ),
      Component = colDef(
        name = "Component",
        cell = data_bars(final_result, 
                         fill_color = c("#ffffff","#0dbaee"),
                         text_position = "inside-end")
      ),
      Material_1 = colDef(
        name = "Material 1",
        cell = data_bars(final_result, 
                         fill_color = c("#ffffff","#f87d1a"),
                         text_position = "inside-end")
      ),
      Material_2 = colDef(
        name = "Material 2",
        cell = data_bars(final_result, 
                         fill_color = c("#ffffff","#0e4cc5"),
                         text_position = "inside-end")
      )
    ),
    defaultPageSize = 10,
    highlight = TRUE,
    striped = TRUE,
    bordered = TRUE,
    resizable = TRUE
  )

Bảng 8: Bảng kết quả MRP

Code
library(dplyr)

# Create the table with label, color, and fill columns
project_days <- final_result %>%
  mutate(
    label = paste(
      ifelse(Finished_product != 0, paste("Finished_product:", Finished_product), ""),
      ifelse(Component != 0, paste("Component:", Component), ""),
      ifelse(Material_1 != 0, paste("Material_1:", Material_1), ""),
      ifelse(Material_2 != 0, paste("Material_2:", Material_2), ""),
      sep = "\n"  # This will insert a newline between each label part
    ) %>% 
    # Trim leading/trailing spaces from label
    str_trim(),
    
    color = case_when(
      !is.na(Finished_product) & Finished_product != 0 ~ "#4CAF50", 
      !is.na(Component) & Component != 0 ~ "#0dbaee",         
      !is.na(Material_1) & Material_1 != 0 ~ "#f87d1a",        
      !is.na(Material_2) & Material_2 != 0 ~ "#0e4cc5",        
      TRUE ~ "#ffffff"
    ),
    fill = color
  ) %>%
  select(day = Date, label, color, fill) %>%
  mutate(day = as.character(day))  # Convert day to character

library(ggweekly)

ggweek_planner(
  start_day = min(final_result$Date), 
  end_day = max(final_result$Date), 
  highlight_days = project_days,
  show_month_boundaries = FALSE, 
  show_month_start_day = FALSE,
  week_start = "epiweek",
  week_start_label = "week",
  weekend_fill = "#FFFFFF"
) + 
  ggplot2::ggtitle("The MRP calendar")  

Bảng 9: Thời khóa biểu cho kế hoạch MRP

Tuy nhiên nếu xét về tiêu chí tối ưu chi phí (saving cost) thì ta cần tính toán thêm chi phí của việc outstock và chi phí tồn kho (inventory cost).

Giả sử chi phí của inventory cost là $40/sản phẩm và outstock cost là $15/sản phẩm thì ta sẽ có bảng so sánh như bảng dưới đây. Kết quả cho thấy phương pháp truyền thống giúp chi phí ở mức thấp hơn.

Code
library(gt)
library(gtExtras)
gt(result %>% 
  summarise(`Normal` = sum(Stockout),
            `Safety Leadtime` = sum(Stock_out_safety)) %>% 
  pivot_longer(cols = everything(),
               names_to = "Approach",
               values_to = "Outstock cost") %>% 
  mutate(`Outstock cost` = `Outstock cost` * 30,
         `Inventory cost` = c(result$Inv_end[nrow(result)],
                              result$Inv_end_safety[nrow(result)])
  ) %>% 
  mutate(`Inventory cost` = ifelse(`Inventory cost` < 0,0,`Inventory cost`*15),
         Total = `Outstock cost` + `Inventory cost`))  %>%
  tab_header(
    title = "Inventory and Stockout Costs"
  ) %>%
  cols_label(
    Approach = "Approach",
    `Outstock cost` = "Outstock Cost ($)",
    `Inventory cost` = "Inventory Cost ($)"
  ) %>%
  fmt_currency(
    columns = c(`Outstock cost`, `Inventory cost`, Total),
    currency = "USD"
  ) %>% 
  gt_highlight_rows(
     rows = 2,
     fill = "orange",
     bold_target_only = TRUE,
     target_col = Total
   )  %>%
   cols_align(
    align = "center",
    columns = everything()
  ) %>% 
  gt_theme_pff() %>% 
  tab_options(
    table.width = "80%"
  )
Inventory and Stockout Costs
Approach Outstock Cost ($) Inventory Cost ($) Total
Normal $480.00 $0.00 $480.00
Safety Leadtime $180.00 $450.00 $630.00

Bảng 10: So sánh tổng chi phí giữa hai phương pháp

3 Kết luận:

Như vậy, ở bài post này chúng ta đã học được cách sử dụng R trong việc xây dựng kế hoạch MRP dựa vào dữ liệu quá khứ cũng như so sánh kết quả giữa 2 tiêu chí là: high servicelow cost.

Tiếp theo, ta lại tiếp tục quy trình trên và làm kế hoạch cho các tháng sau. Chúc bạn may mắn hoàn thành task của mình !!!.

Code
## Gộp dữ liệu từ training set và testing set thành một:
data_prepared_tbl <- bind_rows(training(splits), 
                               testing(splits))


## Tạo thêm các hàng cho dữ liệu sắp tới. Ví dụ ta cần trong 6 tháng thì hàm sẽ tạo thêm 365*4 = 1460 hàng:
future_tbl <- data_prepared_tbl %>%
    group_by(WH) %>%
    future_frame(.length_out = "1 months") %>%
    ungroup()

## Dự đoán nhu cầu cho 3 tháng tiếp theo:
refit_tbl <- modeltime_tbl %>%
    modeltime_refit(data_prepared_tbl)

invisible(capture.output({
refit_tbl<-refit_tbl %>%
    modeltime_forecast(
        new_data    = future_tbl,
        actual_data = data_prepared_tbl,
        keep_data   = TRUE) 
}))

refit_tbl %>% 
  group_by(WH) %>% 
  plot_modeltime_forecast(
         .interactive = TRUE) %>% 
  layout(
    legend = list(
      x = 0.5,  # Centered horizontally
      y = -0.2,  # Position below the plot area
      xanchor = "center",  # Anchor to the center
      yanchor = "top"      # Anchor the top of the legend to the specified Y position
    )
  )

Bảng 9: Kết quả dự đoán nhu cầu trong 1 tháng tiếp theo

Như vậy, chúng ta đã được học về thuật toán Genetic và mô hình MILP cũng như cách thực hiện trong Rstudio.

Nếu bạn có câu hỏi hay thắc mắc nào, đừng ngần ngại liên hệ với mình qua Gmail. Bên cạnh đó, nếu bạn muốn xem lại các bài viết trước đây của mình, hãy nhấn vào hai nút dưới đây để truy cập trang Rpubs hoặc mã nguồn trên Github. Rất vui được đồng hành cùng bạn, hẹn gặp lại! 😄😄😄

Contact Me

Contact Me