# ============================================================
# BAGIAN 1 — LOAD PACKAGE
# ============================================================
library(readxl)
## Warning: package 'readxl' was built under R version 4.3.3
library(ggplot2)
## Warning: package 'ggplot2' was built under R version 4.3.3
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.3.3
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(tidyr)
## Warning: package 'tidyr' was built under R version 4.3.3
library(lubridate)
## Warning: package 'lubridate' was built under R version 4.3.3
## 
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
## 
##     date, intersect, setdiff, union
library(RColorBrewer)
library(corrplot)
## Warning: package 'corrplot' was built under R version 4.3.3
## corrplot 0.95 loaded
library(scales)
# ============================================================
# BAGIAN 2 — IMPORT & BERSIHKAN DATA
# ============================================================
data <- read_excel(
  "C:/Users/ASUS/OneDrive/Documents/CRMK3_PUAN KOPI GUNMAL/DATA PUAN KOPI GUNMAL_APRIL-SEPTEMBER 2025.xlsx",
  sheet = "PEMASUKAN"
)

# Rename kolom agar lebih mudah diproses
colnames(data) <- c(
  "Tanggal",
  "Cup_Dingin", "Cup_Panas", "Air_Mineral",
  "Espresso", "Kapiten",
  "Cookies", "Fudgy_Brownies", "Fudgy_Brownies_Aya",
  "Cireng", "Singkong", "Kentang", "Dimsum",
  "Pentol", "Tahu_Bakso",
  "Mie_Goreng", "Mie_Kuah", "Nasi_Goreng"
)

data$Tanggal <- as.Date(data$Tanggal)
produk_cols  <- names(data)[-1]
for (col in produk_cols) {
  data[[col]] <- suppressWarnings(as.numeric(as.character(data[[col]])))
}
data <- data %>% filter(!is.na(Tanggal))

# ── Penanganan Outlier ──────────────────────────────────────
# Espresso tanggal 2025-06-28 tercatat 48 (kemungkinan error input).
# Nilai diganti dengan median Espresso hari-hari lain.
espresso_median <- median(data$Espresso[data$Espresso < 10], na.rm = TRUE)
data$Espresso[data$Espresso >= 10] <- espresso_median
cat("Outlier Espresso (>= 10) diganti dengan median:", espresso_median, "\n")
## Outlier Espresso (>= 10) diganti dengan median: 1
cat("\nData dimuat:", nrow(data), "hari |",
    format(min(data$Tanggal)), "s.d.", format(max(data$Tanggal)), "\n\n")
## 
## Data dimuat: 183 hari | 2025-03-29 s.d. 2025-09-30
# ============================================================
# BAGIAN 3 — HARGA SATUAN (Rp) — dari sheet MENU PUAN KOPI
# ============================================================
# Cup Dingin/Panas = harga Tuan (kopi susu dasar) = Rp 15.000
# Espresso         = Rp 5.000
# Kapiten          = Kopi Soda/Kapiten = Rp 30.000
# Air Mineral      = Rp 8.000
# Cookies          = Rp 10.000
# Fudgy Brownies   = Rp 22.000 | Fudgy Brownies Aya = Rp 20.000
# Cireng/Singkong  = Rp 20.000 | Kentang = Rp 23.000
# Dimsum/Pentol    = Rp 25.000 | Tahu Bakso = Rp 27.000
# Mie Goreng/Kuah  = Rp 23.000 | Nasi Goreng = Rp 25.000

harga_satuan <- c(
  Cup_Dingin         = 15000,
  Cup_Panas          = 15000,
  Air_Mineral        = 8000,
  Espresso           = 5000,
  Kapiten            = 30000,
  Cookies            = 10000,
  Fudgy_Brownies     = 22000,
  Fudgy_Brownies_Aya = 20000,
  Cireng             = 20000,
  Singkong           = 20000,
  Kentang            = 23000,
  Dimsum             = 25000,
  Pentol             = 25000,
  Tahu_Bakso         = 27000,
  Mie_Goreng         = 23000,
  Mie_Kuah           = 23000,
  Nasi_Goreng        = 25000
)
# ============================================================
# BAGIAN 4 — STATISTIK DESKRIPTIF
# ============================================================
stat_desc <- data %>%
  select(-Tanggal) %>%
  summarise(across(everything(), list(
    Mean   = ~round(mean(., na.rm = TRUE), 2),
    Median = ~median(., na.rm = TRUE),
    SD     = ~round(sd(., na.rm = TRUE), 2),
    Min    = ~min(., na.rm = TRUE),
    Max    = ~max(., na.rm = TRUE),
    Total  = ~sum(., na.rm = TRUE)
  ))) %>%
  pivot_longer(
    everything(),
    names_to  = c("Produk", ".value"),
    names_sep = "_(?=[^_]+$)"
  ) %>%
  mutate(Produk = gsub("_", " ", Produk))

cat("=== STATISTIK DESKRIPTIF ===\n")
## === STATISTIK DESKRIPTIF ===
print(stat_desc)
## # A tibble: 17 × 7
##    Produk               Mean Median    SD   Min   Max Total
##    <chr>               <dbl>  <dbl> <dbl> <dbl> <dbl> <dbl>
##  1 Cup Dingin         150.      147 29.5     83   255 27380
##  2 Cup Panas            2.62      2  2.23     0    13   480
##  3 Air Mineral          6.96      6  4.13     0    21  1273
##  4 Espresso             1.27      1  1.36     0     7   231
##  5 Kapiten              0.37      0  0.68     0     4    67
##  6 Cookies              4.1       3  4.04     0    30   750
##  7 Fudgy Brownies       0.21      0  0.77     0     6    39
##  8 Fudgy Brownies Aya   0.96      0  1.6      0     6   175
##  9 Cireng               6.1       6  3.36     0    18  1117
## 10 Singkong             1.87      2  1.44     0     7   342
## 11 Kentang              4.09      4  2.39     0    11   748
## 12 Dimsum               1.49      1  1.42     0     7   272
## 13 Pentol               0.75      1  0.87     0     5   137
## 14 Tahu Bakso           1.47      1  1.29     0     8   269
## 15 Mie Goreng           1.23      1  1.41     0     8   225
## 16 Mie Kuah             1.17      1  1.54     0     9   215
## 17 Nasi Goreng          1.74      2  1.38     0     6   319
# ============================================================
# BAGIAN 5 — TOTAL PENJUALAN PER PRODUK & KATEGORI
# ============================================================
total_produk <- data %>%
  select(-Tanggal) %>%
  summarise(across(everything(), ~sum(., na.rm = TRUE))) %>%
  pivot_longer(everything(), names_to = "Produk", values_to = "Total") %>%
  mutate(
    Produk   = gsub("_", " ", Produk),
    Kategori = case_when(
      Produk %in% c("Cup Dingin", "Cup Panas",
                    "Espresso", "Kapiten", "Air Mineral") ~ "Minuman",
      Produk %in% c("Cookies", "Fudgy Brownies",
                    "Fudgy Brownies Aya")                 ~ "Kue & Snack",
      TRUE                                                ~ "Makanan"
    )
  ) %>%
  arrange(desc(Total))
# ============================================================
# BAGIAN 6 — VISUALISASI PENJUALAN (VIZ 1–7)
# ============================================================

# VIZ 1: Total Penjualan per Produk
# Menampilkan semua 17 produk, sudah mencakup informasi "top produk"
# sehingga tidak perlu chart Top 10 terpisah.
ggplot(total_produk,
       aes(x = reorder(Produk, Total), y = Total, fill = Kategori)) +
  geom_col(width = 0.7) +
  geom_text(aes(label = comma(Total)), hjust = -0.1, size = 3.2) +
  coord_flip() +
  scale_fill_manual(values = c("Kue & Snack" = "#6F3811",
                               "Makanan"     = "#A0522D",
                               "Minuman"     = "#C68642")) +
  scale_y_continuous(labels = comma, expand = expansion(mult = c(0, 0.18))) +
  labs(title    = "Total Penjualan per Produk (Maret–September 2025)",
       subtitle = "Puan Kopi Gunung Malang",
       x = NULL, y = "Jumlah Terjual (Unit)", fill = "Kategori",
       caption  = "Gambar 1. Total penjualan tiap produk selama 183 hari pengamatan.") +
  theme_minimal(base_size = 12) +
  theme(plot.title   = element_text(face = "bold", color = "#3E1C00"),
        plot.caption = element_text(hjust = 0, color = "#6F3811", size = 9))

# VIZ 2: Tren Penjualan Harian
data_harian <- data %>%
  mutate(Total_Harian = rowSums(select(., -Tanggal), na.rm = TRUE))

ggplot(data_harian, aes(x = Tanggal, y = Total_Harian)) +
  geom_line(color = "#A0522D", linewidth = 0.8) +
  geom_smooth(method = "loess", color = "#3E1C00",
              se = TRUE, fill = "#E8B97E", alpha = 0.3) +
  scale_x_date(date_labels = "%b %Y", date_breaks = "1 month") +
  scale_y_continuous(labels = comma) +
  labs(title    = "Tren Penjualan Harian — Total Semua Produk",
       subtitle = "Puan Kopi Gunung Malang | Maret–September 2025",
       x = NULL, y = "Total Unit Terjual",
       caption  = "Gambar 2. Garis tipis = penjualan harian; garis tebal = tren umum (LOESS).") +
  theme_minimal(base_size = 11) +
  theme(plot.title   = element_text(face = "bold", color = "#3E1C00"),
        plot.caption = element_text(hjust = 0, color = "#6F3811", size = 9))
## `geom_smooth()` using formula = 'y ~ x'

# VIZ 3: Tren Penjualan Minuman per Bulan
tren_bulanan <- data %>%
  mutate(Bulan = floor_date(Tanggal, "month")) %>%
  group_by(Bulan) %>%
  summarise(across(Cup_Dingin:Kapiten, ~sum(., na.rm = TRUE)), .groups = "drop") %>%
  pivot_longer(-Bulan, names_to = "Produk", values_to = "Total") %>%
  mutate(Produk = gsub("_", " ", Produk))

ggplot(tren_bulanan,
       aes(x = Bulan, y = Total, color = Produk, group = Produk)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 2.5) +
  scale_color_manual(values = c("#3E1C00", "#7B3F00", "#C68642",
                                "#E8B97E", "#A0522D")) +
  scale_x_date(date_labels = "%b %Y", date_breaks = "1 month") +
  scale_y_continuous(labels = comma) +
  labs(title    = "Tren Penjualan Minuman per Bulan",
       subtitle = "Puan Kopi Gunung Malang | Maret–September 2025",
       x = NULL, y = "Jumlah Terjual", color = "Produk",
       caption  = "Gambar 3. Tren penjualan bulanan tiap varian minuman.") +
  theme_minimal(base_size = 12) +
  theme(plot.title   = element_text(face = "bold", color = "#3E1C00"),
        plot.caption = element_text(hjust = 0, color = "#6F3811", size = 9))

# VIZ 4: Heatmap Penjualan Bulanan per Produk
# Menampilkan pola bulan × produk sekaligus; lebih informatif dari bar chart
# kategori bulanan (VIZ 6 asli tetap dipertahankan karena perspektif berbeda).
heatmap_data <- data %>%
  mutate(Bulan = format(Tanggal, "%b %Y")) %>%
  group_by(Bulan) %>%
  summarise(across(-Tanggal, ~sum(., na.rm = TRUE)), .groups = "drop") %>%
  pivot_longer(-Bulan, names_to = "Produk", values_to = "Total") %>%
  mutate(
    Produk = gsub("_", " ", Produk),
    Bulan  = factor(Bulan, levels = c("Mar 2025", "Apr 2025", "May 2025",
                                      "Jun 2025", "Jul 2025", "Aug 2025",
                                      "Sep 2025"))
  )

ggplot(heatmap_data, aes(x = Bulan, y = Produk, fill = Total)) +
  geom_tile(color = "white", linewidth = 0.5) +
  geom_text(aes(label = comma(Total)), size = 2.8, color = "white") +
  scale_fill_gradient(low = "#FBE9D0", high = "#3E1C00", labels = comma) +
  labs(title    = "Heatmap Penjualan Bulanan per Produk",
       subtitle = "Puan Kopi Gunung Malang | Maret–September 2025",
       x = NULL, y = NULL, fill = "Total\nTerjual",
       caption  = "Gambar 4. Warna lebih gelap = penjualan lebih tinggi.") +
  theme_minimal(base_size = 11) +
  theme(plot.title   = element_text(face = "bold", color = "#3E1C00"),
        axis.text.x  = element_text(angle = 30, hjust = 1),
        plot.caption = element_text(hjust = 0, color = "#6F3811", size = 9))

# VIZ 5: Proporsi Penjualan per Kategori
total_kategori <- total_produk %>%
  group_by(Kategori) %>%
  summarise(Total = sum(Total), .groups = "drop") %>%
  mutate(
    Persen = round(Total / sum(Total) * 100, 1),
    Label  = paste0(Kategori, "\n", Persen, "%")
  )

ggplot(total_kategori, aes(x = "", y = Total, fill = Kategori)) +
  geom_col(width = 1) +
  coord_polar(theta = "y") +
  geom_text(aes(label = Label),
            position = position_stack(vjust = 0.5),
            size = 4, fontface = "bold", color = "white") +
  scale_fill_manual(values = c("Kue & Snack" = "#6F3811",
                               "Makanan"     = "#A0522D",
                               "Minuman"     = "#C68642")) +
  labs(title    = "Proporsi Penjualan per Kategori",
       subtitle = "Puan Kopi Gunung Malang | Maret–September 2025",
       caption  = "Gambar 5. Kontribusi tiap kategori terhadap total penjualan.") +
  theme_void() +
  theme(plot.title      = element_text(face = "bold", hjust = 0.5, color = "#3E1C00"),
        plot.subtitle   = element_text(hjust = 0.5, color = "#6F3811"),
        plot.caption    = element_text(hjust = 0.5, color = "#6F3811", size = 9),
        legend.position = "none")

# VIZ 6: Penjualan per Kategori per Bulan
bulanan_kategori <- data %>%
  mutate(Bulan = format(Tanggal, "%b %Y")) %>%
  group_by(Bulan) %>%
  summarise(
    Minuman   = sum(Cup_Dingin + Cup_Panas + Espresso +
                      Kapiten + Air_Mineral, na.rm = TRUE),
    Kue_Snack = sum(Cookies + Fudgy_Brownies +
                      Fudgy_Brownies_Aya, na.rm = TRUE),
    Makanan   = sum(Cireng + Singkong + Kentang + Dimsum +
                      Pentol + Tahu_Bakso + Mie_Goreng +
                      Mie_Kuah + Nasi_Goreng, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  pivot_longer(-Bulan, names_to = "Kategori", values_to = "Total") %>%
  mutate(Bulan = factor(Bulan, levels = c("Mar 2025", "Apr 2025", "May 2025",
                                          "Jun 2025", "Jul 2025", "Aug 2025",
                                          "Sep 2025")))

ggplot(bulanan_kategori,
       aes(x = Bulan, y = Total, fill = Kategori)) +
  geom_col(position = "dodge", width = 0.7) +
  scale_fill_manual(values = c("Kue_Snack" = "#6F3811",
                               "Makanan"   = "#A0522D",
                               "Minuman"   = "#C68642")) +
  scale_y_continuous(labels = comma) +
  labs(title    = "Total Penjualan per Kategori per Bulan",
       subtitle = "Puan Kopi Gunung Malang | Maret–September 2025",
       x = NULL, y = "Jumlah Terjual", fill = "Kategori",
       caption  = "Gambar 6. Perbandingan penjualan tiga kategori setiap bulan.") +
  theme_minimal(base_size = 11) +
  theme(plot.title   = element_text(face = "bold", color = "#3E1C00"),
        axis.text.x  = element_text(angle = 30, hjust = 1),
        plot.caption = element_text(hjust = 0, color = "#6F3811", size = 9))

# VIZ 7: Weekday vs Weekend
data_hari <- data %>%
  mutate(
    Hari       = weekdays(Tanggal),
    Jenis_Hari = ifelse(Hari %in% c("Saturday", "Sunday", "Sabtu", "Minggu"),
                        "Weekend", "Weekday")
  ) %>%
  group_by(Jenis_Hari) %>%
  summarise(across(-c(Tanggal, Hari), ~round(mean(., na.rm = TRUE), 2)),
            .groups = "drop") %>%
  pivot_longer(-Jenis_Hari, names_to = "Produk", values_to = "Rata2") %>%
  mutate(Produk = gsub("_", " ", Produk))

ggplot(data_hari,
       aes(x = reorder(Produk, Rata2), y = Rata2, fill = Jenis_Hari)) +
  geom_col(position = "dodge", width = 0.7) +
  coord_flip() +
  scale_fill_manual(values = c("Weekday" = "#A0522D", "Weekend" = "#E8B97E")) +
  scale_y_continuous(labels = comma) +
  labs(title    = "Rata-rata Penjualan Harian: Weekday vs Weekend",
       subtitle = "Puan Kopi Gunung Malang | Maret–September 2025",
       x = NULL, y = "Rata-rata Unit Terjual", fill = "Jenis Hari",
       caption  = "Gambar 7. Produk dengan nilai Weekend > Weekday cocok dipromosikan di akhir pekan.") +
  theme_minimal(base_size = 11) +
  theme(plot.title      = element_text(face = "bold", color = "#3E1C00"),
        legend.position = "bottom",
        plot.caption    = element_text(hjust = 0, color = "#6F3811", size = 9))

# ============================================================
# BAGIAN 7 — DATA BINER (FONDASI APRIORI)
# ============================================================
data_biner  <- data %>%
  select(-Tanggal) %>%
  mutate(across(everything(), ~ifelse(is.na(.) | . == 0, 0L, 1L)))

data_matrix <- as.matrix(data_biner)
n_trans     <- nrow(data_biner)
prod_names  <- gsub("_", " ", colnames(data_biner))
# ============================================================
# BAGIAN 8 — THRESHOLD & SUPPORT TIAP PRODUK
# ============================================================
# Catatan support aktual dari data:
#   Kapiten         = 0.279  →  TIDAK lolos (jarang dipesan)
#   Fudgy Brownies  = 0.087  →  TIDAK lolos
#   Fudgy Brownies Aya = 0.350 → TIDAK lolos
#   Semua produk lain >= 0.530 → lolos

freq_item      <- colSums(data_biner)
support_produk <- freq_item / n_trans
names(support_produk) <- prod_names

MIN_SUPPORT    <- 0.50
MIN_CONFIDENCE <- 0.60
MIN_LIFT       <- 1.0

cat("=== SUPPORT TIAP PRODUK ===\n")
## === SUPPORT TIAP PRODUK ===
print(round(sort(support_produk, decreasing = TRUE), 4))
##         Cup Dingin             Cireng        Air Mineral            Kentang 
##             1.0000             0.9836             0.9727             0.9508 
##          Cup Panas           Singkong        Nasi Goreng            Cookies 
##             0.8525             0.8142             0.7869             0.7650 
##             Dimsum         Tahu Bakso           Espresso         Mie Goreng 
##             0.7650             0.7268             0.6721             0.6120 
##           Mie Kuah             Pentol Fudgy Brownies Aya            Kapiten 
##             0.5355             0.5301             0.3497             0.2787 
##     Fudgy Brownies 
##             0.0874
cat("\nParameter Apriori: Support >=", MIN_SUPPORT,
    "| Confidence >=", MIN_CONFIDENCE, "| Lift >", MIN_LIFT, "\n")
## 
## Parameter Apriori: Support >= 0.5 | Confidence >= 0.6 | Lift > 1
cat("Catatan: Kapiten (0.279), Fudgy Brownies (0.087), Fudgy Brownies Aya (0.350)",
    "tidak memenuhi minimum support.\n\n")
## Catatan: Kapiten (0.279), Fudgy Brownies (0.087), Fudgy Brownies Aya (0.350) tidak memenuhi minimum support.
# ============================================================
# BAGIAN 9 — VIZ 8: FREKUENSI PRODUK
# ============================================================
# (Nomor mundur 1 karena VIZ 8 Top-10 asli dihapus — sudah tercakup VIZ 1)
freq_df <- data.frame(
  Produk    = prod_names,
  Frekuensi = as.numeric(freq_item),
  Support   = round(support_produk, 3)
) %>%
  arrange(desc(Frekuensi)) %>%
  mutate(
    Warna         = ifelse(Support >= MIN_SUPPORT, "#A0522D", "#D2A679"),
    Label_Support = paste0(Frekuensi, " hari (", round(Support * 100), "%)")
  )

ggplot(freq_df, aes(x = reorder(Produk, Frekuensi),
                    y = Frekuensi, fill = Warna)) +
  geom_col(width = 0.7) +
  geom_text(aes(label = Label_Support), hjust = -0.05, size = 3, color = "#3E1C00") +
  coord_flip() +
  scale_fill_identity() +
  scale_y_continuous(expand = expansion(mult = c(0, 0.22))) +
  geom_hline(yintercept = round(MIN_SUPPORT * n_trans),
             linetype = "dashed", color = "#6F3811", linewidth = 0.6) +
  annotate("text", x = 1.5, y = round(MIN_SUPPORT * n_trans) + 2,
           label = paste0("Min. support (50% = ", round(MIN_SUPPORT * n_trans), " hari)"),
           color = "#6F3811", size = 2.8, hjust = 0) +
  labs(title    = "Frekuensi Kemunculan Tiap Produk",
       subtitle = "Warna gelap = memenuhi minimum support (≥50% hari)",
       x = NULL, y = "Frekuensi (Hari)",
       caption  = "Gambar 8. Produk di atas garis putus-putus masuk sebagai frequent item dalam Apriori.") +
  theme_minimal(base_size = 11) +
  theme(plot.title   = element_text(face = "bold", color = "#3E1C00"),
        plot.caption = element_text(hjust = 0, color = "#6F3811", size = 9))

# ============================================================
# BAGIAN 10 — FREQUENT 1-ITEMSETS
# ============================================================
freq_1 <- data.frame(
  Itemset = prod_names,
  Support = round(support_produk, 4),
  Count   = as.numeric(freq_item)
) %>%
  filter(Support >= MIN_SUPPORT) %>%
  arrange(desc(Support)) %>%
  mutate(
    No           = row_number(),
    Support_Pct  = paste0(round(Support * 100, 1), "%"),
    Interpretasi = paste0("Terjual ", Count, " dari ", n_trans, " hari")
  ) %>%
  select(No, Itemset, Count, Support_Pct, Interpretasi)

cat("=== FREQUENT 1-ITEMSETS ===\n")
## === FREQUENT 1-ITEMSETS ===
print(freq_1)
##             No     Itemset Count Support_Pct              Interpretasi
## Cup Dingin   1  Cup Dingin   183        100% Terjual 183 dari 183 hari
## Cireng       2      Cireng   180       98.4% Terjual 180 dari 183 hari
## Air Mineral  3 Air Mineral   178       97.3% Terjual 178 dari 183 hari
## Kentang      4     Kentang   174       95.1% Terjual 174 dari 183 hari
## Cup Panas    5   Cup Panas   156       85.2% Terjual 156 dari 183 hari
## Singkong     6    Singkong   149       81.4% Terjual 149 dari 183 hari
## Nasi Goreng  7 Nasi Goreng   144       78.7% Terjual 144 dari 183 hari
## Cookies      8     Cookies   140       76.5% Terjual 140 dari 183 hari
## Dimsum       9      Dimsum   140       76.5% Terjual 140 dari 183 hari
## Tahu Bakso  10  Tahu Bakso   133       72.7% Terjual 133 dari 183 hari
## Espresso    11    Espresso   123       67.2% Terjual 123 dari 183 hari
## Mie Goreng  12  Mie Goreng   112       61.2% Terjual 112 dari 183 hari
## Mie Kuah    13    Mie Kuah    98       53.5%  Terjual 98 dari 183 hari
## Pentol      14      Pentol    97         53%  Terjual 97 dari 183 hari
cat("Total:", nrow(freq_1), "produk memenuhi minimum support\n\n")
## Total: 14 produk memenuhi minimum support
# ============================================================
# BAGIAN 11 — ALGORITMA APRIORI
# ============================================================
cols       <- colnames(data_biner)
n_cols     <- length(cols)
rules_list <- list()

cat("Menjalankan Apriori...\n")
## Menjalankan Apriori...
for (i in 1:(n_cols - 1)) {
  for (j in (i + 1):n_cols) {
    sup_ij <- sum(data_biner[[i]] == 1 & data_biner[[j]] == 1) / n_trans
    if (sup_ij < MIN_SUPPORT) next
    
    sup_i <- sum(data_biner[[i]]) / n_trans
    sup_j <- sum(data_biner[[j]]) / n_trans
    
    # i -> j
    conf_ij <- sup_ij / sup_i
    lift_ij <- conf_ij / sup_j
    if (conf_ij >= MIN_CONFIDENCE && lift_ij > MIN_LIFT) {
      rules_list[[length(rules_list) + 1]] <- data.frame(
        Antecedent = gsub("_", " ", cols[i]),
        Consequent = gsub("_", " ", cols[j]),
        Support    = round(sup_ij,  3),
        Confidence = round(conf_ij, 3),
        Lift       = round(lift_ij, 3),
        Count      = round(sup_ij * n_trans)
      )
    }
    
    # j -> i
    conf_ji <- sup_ij / sup_j
    lift_ji <- conf_ji / sup_i
    if (conf_ji >= MIN_CONFIDENCE && lift_ji > MIN_LIFT) {
      rules_list[[length(rules_list) + 1]] <- data.frame(
        Antecedent = gsub("_", " ", cols[j]),
        Consequent = gsub("_", " ", cols[i]),
        Support    = round(sup_ij,  3),
        Confidence = round(conf_ji, 3),
        Lift       = round(lift_ji, 3),
        Count      = round(sup_ij * n_trans)
      )
    }
  }
}

rules_df <- bind_rows(rules_list) %>%
  arrange(desc(Lift), desc(Confidence)) %>%
  mutate(
    No       = row_number(),
    Rule_Str = paste0("{", Antecedent, "} -> {", Consequent, "}"),
    Kekuatan = case_when(
      Lift >= 1.5 & Confidence >= 0.80 ~ "Sangat Kuat",
      Lift >= 1.2 & Confidence >= 0.70 ~ "Kuat",
      TRUE                             ~ "Cukup"
    )
  ) %>%
  select(No, Rule_Str, Antecedent, Consequent,
         Support, Confidence, Lift, Count, Kekuatan)

cat("Selesai.", nrow(rules_df), "rules ditemukan.\n\n")
## Selesai. 80 rules ditemukan.
cat("Support    = proporsi hari kedua produk terjual bersamaan\n")
## Support    = proporsi hari kedua produk terjual bersamaan
cat("Confidence = peluang beli Consequent jika sudah beli Antecedent\n")
## Confidence = peluang beli Consequent jika sudah beli Antecedent
cat("Lift       = kekuatan asosiasi; >1 = hubungan positif\n\n")
## Lift       = kekuatan asosiasi; >1 = hubungan positif
print(rules_df)
##    No                       Rule_Str  Antecedent  Consequent Support Confidence
## 1   1  {Mie Goreng} -> {Nasi Goreng}  Mie Goreng Nasi Goreng   0.514      0.839
## 2   2  {Nasi Goreng} -> {Mie Goreng} Nasi Goreng  Mie Goreng   0.514      0.653
## 3   3        {Espresso} -> {Cookies}    Espresso     Cookies   0.546      0.813
## 4   4     {Espresso} -> {Tahu Bakso}    Espresso  Tahu Bakso   0.519      0.772
## 5   5        {Cookies} -> {Espresso}     Cookies    Espresso   0.546      0.714
## 6   6     {Tahu Bakso} -> {Espresso}  Tahu Bakso    Espresso   0.519      0.714
## 7   7       {Tahu Bakso} -> {Dimsum}  Tahu Bakso      Dimsum   0.579      0.797
## 8   8       {Dimsum} -> {Tahu Bakso}      Dimsum  Tahu Bakso   0.579      0.757
## 9   9       {Espresso} -> {Singkong}    Espresso    Singkong   0.568      0.846
## 10 10       {Singkong} -> {Espresso}    Singkong    Espresso   0.568      0.698
## 11 11    {Mie Goreng} -> {Cup Panas}  Mie Goreng   Cup Panas   0.541      0.884
## 12 12    {Cup Panas} -> {Mie Goreng}   Cup Panas  Mie Goreng   0.541      0.635
## 13 13     {Tahu Bakso} -> {Singkong}  Tahu Bakso    Singkong   0.612      0.842
## 14 14     {Singkong} -> {Tahu Bakso}    Singkong  Tahu Bakso   0.612      0.752
## 15 15    {Espresso} -> {Nasi Goreng}    Espresso Nasi Goreng   0.546      0.813
## 16 16    {Nasi Goreng} -> {Espresso} Nasi Goreng    Espresso   0.546      0.694
## 17 17    {Nasi Goreng} -> {Singkong} Nasi Goreng    Singkong   0.661      0.840
## 18 18    {Singkong} -> {Nasi Goreng}    Singkong Nasi Goreng   0.661      0.812
## 19 19          {Pentol} -> {Kentang}      Pentol     Kentang   0.519      0.979
## 20 20     {Nasi Goreng} -> {Kentang} Nasi Goreng     Kentang   0.770      0.979
## 21 21     {Kentang} -> {Nasi Goreng}     Kentang Nasi Goreng   0.770      0.810
## 22 22        {Espresso} -> {Kentang}    Espresso     Kentang   0.656      0.976
## 23 23        {Kentang} -> {Espresso}     Kentang    Espresso   0.656      0.690
## 24 24        {Singkong} -> {Kentang}    Singkong     Kentang   0.792      0.973
## 25 25        {Kentang} -> {Singkong}     Kentang    Singkong   0.792      0.833
## 26 26       {Cookies} -> {Cup Panas}     Cookies   Cup Panas   0.667      0.871
## 27 27       {Cup Panas} -> {Cookies}   Cup Panas     Cookies   0.667      0.782
## 28 28      {Tahu Bakso} -> {Cookies}  Tahu Bakso     Cookies   0.568      0.782
## 29 29      {Cookies} -> {Tahu Bakso}     Cookies  Tahu Bakso   0.568      0.743
## 30 30   {Nasi Goreng} -> {Cup Panas} Nasi Goreng   Cup Panas   0.683      0.868
## 31 31   {Cup Panas} -> {Nasi Goreng}   Cup Panas Nasi Goreng   0.683      0.801
## 32 32           {Pentol} -> {Cireng}      Pentol      Cireng   0.530      1.000
## 33 33       {Tahu Bakso} -> {Cireng}  Tahu Bakso      Cireng   0.727      1.000
## 34 34       {Mie Goreng} -> {Cireng}  Mie Goreng      Cireng   0.612      1.000
## 35 35         {Mie Kuah} -> {Cireng}    Mie Kuah      Cireng   0.536      1.000
## 36 36       {Cireng} -> {Tahu Bakso}      Cireng  Tahu Bakso   0.727      0.739
## 37 37       {Cireng} -> {Mie Goreng}      Cireng  Mie Goreng   0.612      0.622
## 38 38          {Dimsum} -> {Kentang}      Dimsum     Kentang   0.738      0.964
## 39 39      {Mie Goreng} -> {Kentang}  Mie Goreng     Kentang   0.590      0.964
## 40 40          {Kentang} -> {Dimsum}     Kentang      Dimsum   0.738      0.776
## 41 41      {Kentang} -> {Mie Goreng}     Kentang  Mie Goreng   0.590      0.621
## 42 42      {Dimsum} -> {Air Mineral}      Dimsum Air Mineral   0.754      0.986
## 43 43      {Air Mineral} -> {Dimsum} Air Mineral      Dimsum   0.754      0.775
## 44 44      {Tahu Bakso} -> {Kentang}  Tahu Bakso     Kentang   0.699      0.962
## 45 45      {Kentang} -> {Tahu Bakso}     Kentang  Tahu Bakso   0.699      0.736
## 46 46      {Nasi Goreng} -> {Cireng} Nasi Goreng      Cireng   0.781      0.993
## 47 47      {Cireng} -> {Nasi Goreng}      Cireng Nasi Goreng   0.781      0.794
## 48 48           {Dimsum} -> {Cireng}      Dimsum      Cireng   0.760      0.993
## 49 49        {Mie Kuah} -> {Kentang}    Mie Kuah     Kentang   0.514      0.959
## 50 50        {Cookies} -> {Singkong}     Cookies    Singkong   0.628      0.821
## 51 51     {Mie Goreng} -> {Singkong}  Mie Goreng    Singkong   0.503      0.821
## 52 52        {Singkong} -> {Cookies}    Singkong     Cookies   0.628      0.772
## 53 53           {Cireng} -> {Dimsum}      Cireng      Dimsum   0.760      0.772
## 54 54     {Singkong} -> {Mie Goreng}    Singkong  Mie Goreng   0.503      0.617
## 55 55         {Espresso} -> {Cireng}    Espresso      Cireng   0.667      0.992
## 56 56     {Cookies} -> {Nasi Goreng}     Cookies Nasi Goreng   0.607      0.793
## 57 57     {Nasi Goreng} -> {Cookies} Nasi Goreng     Cookies   0.607      0.771
## 58 58         {Cireng} -> {Espresso}      Cireng    Espresso   0.667      0.678
## 59 59    {Singkong} -> {Air Mineral}    Singkong Air Mineral   0.798      0.980
## 60 60 {Nasi Goreng} -> {Air Mineral} Nasi Goreng Air Mineral   0.770      0.979
## 61 61    {Air Mineral} -> {Singkong} Air Mineral    Singkong   0.798      0.820
## 62 62 {Air Mineral} -> {Nasi Goreng} Air Mineral Nasi Goreng   0.770      0.792
## 63 63     {Cookies} -> {Air Mineral}     Cookies Air Mineral   0.749      0.979
## 64 64     {Air Mineral} -> {Cookies} Air Mineral     Cookies   0.749      0.770
## 65 65      {Air Mineral} -> {Cireng} Air Mineral      Cireng   0.962      0.989
## 66 66          {Kentang} -> {Cireng}     Kentang      Cireng   0.940      0.989
## 67 67      {Cireng} -> {Air Mineral}      Cireng Air Mineral   0.962      0.978
## 68 68          {Cireng} -> {Kentang}      Cireng     Kentang   0.940      0.956
## 69 69    {Tahu Bakso} -> {Cup Panas}  Tahu Bakso   Cup Panas   0.623      0.857
## 70 70    {Cup Panas} -> {Tahu Bakso}   Cup Panas  Tahu Bakso   0.623      0.731
## 71 71     {Kentang} -> {Air Mineral}     Kentang Air Mineral   0.929      0.977
## 72 72     {Air Mineral} -> {Kentang} Air Mineral     Kentang   0.929      0.955
## 73 73         {Singkong} -> {Cireng}    Singkong      Cireng   0.803      0.987
## 74 74         {Cireng} -> {Singkong}      Cireng    Singkong   0.803      0.817
## 75 75          {Cookies} -> {Cireng}     Cookies      Cireng   0.754      0.986
## 76 76   {Cup Panas} -> {Air Mineral}   Cup Panas Air Mineral   0.831      0.974
## 77 77   {Air Mineral} -> {Cup Panas} Air Mineral   Cup Panas   0.831      0.854
## 78 78          {Cireng} -> {Cookies}      Cireng     Cookies   0.754      0.767
## 79 79         {Dimsum} -> {Singkong}      Dimsum    Singkong   0.623      0.814
## 80 80         {Singkong} -> {Dimsum}    Singkong      Dimsum   0.623      0.765
##     Lift Count Kekuatan
## 1  1.067    94    Cukup
## 2  1.067    94    Cukup
## 3  1.063   100    Cukup
## 4  1.063    95    Cukup
## 5  1.063   100    Cukup
## 6  1.063    95    Cukup
## 7  1.042   106    Cukup
## 8  1.042   106    Cukup
## 9  1.038   104    Cukup
## 10 1.038   104    Cukup
## 11 1.037    99    Cukup
## 12 1.037    99    Cukup
## 13 1.034   112    Cukup
## 14 1.034   112    Cukup
## 15 1.033   100    Cukup
## 16 1.033   100    Cukup
## 17 1.032   121    Cukup
## 18 1.032   121    Cukup
## 19 1.030    95    Cukup
## 20 1.030   141    Cukup
## 21 1.030   141    Cukup
## 22 1.026   120    Cukup
## 23 1.026   120    Cukup
## 24 1.023   145    Cukup
## 25 1.023   145    Cukup
## 26 1.022   122    Cukup
## 27 1.022   122    Cukup
## 28 1.022   104    Cukup
## 29 1.022   104    Cukup
## 30 1.018   125    Cukup
## 31 1.018   125    Cukup
## 32 1.017    97    Cukup
## 33 1.017   133    Cukup
## 34 1.017   112    Cukup
## 35 1.017    98    Cukup
## 36 1.017   133    Cukup
## 37 1.017   112    Cukup
## 38 1.014   135    Cukup
## 39 1.014   108    Cukup
## 40 1.014   135    Cukup
## 41 1.014   108    Cukup
## 42 1.013   138    Cukup
## 43 1.013   138    Cukup
## 44 1.012   128    Cukup
## 45 1.012   128    Cukup
## 46 1.010   143    Cukup
## 47 1.010   143    Cukup
## 48 1.009   139    Cukup
## 49 1.009    94    Cukup
## 50 1.009   115    Cukup
## 51 1.009    92    Cukup
## 52 1.009   115    Cukup
## 53 1.009   139    Cukup
## 54 1.009    92    Cukup
## 55 1.008   122    Cukup
## 56 1.008   111    Cukup
## 57 1.008   111    Cukup
## 58 1.008   122    Cukup
## 59 1.007   146    Cukup
## 60 1.007   141    Cukup
## 61 1.007   146    Cukup
## 62 1.007   141    Cukup
## 63 1.006   137    Cukup
## 64 1.006   137    Cukup
## 65 1.005   176    Cukup
## 66 1.005   172    Cukup
## 67 1.005   176    Cukup
## 68 1.005   172    Cukup
## 69 1.005   114    Cukup
## 70 1.005   114    Cukup
## 71 1.004   170    Cukup
## 72 1.004   170    Cukup
## 73 1.003   147    Cukup
## 74 1.003   147    Cukup
## 75 1.002   138    Cukup
## 76 1.002   152    Cukup
## 77 1.002   152    Cukup
## 78 1.002   138    Cukup
## 79 1.000   114    Cukup
## 80 1.000   114    Cukup
# ============================================================
# BAGIAN 12 — VISUALISASI ASSOCIATION RULES (VIZ 9–12)
# ============================================================

# VIZ 9: Scatter Plot Support vs Confidence
# (VIZ 11 asli dihapus — scatter berlabel sering overlap & tidak menambah info baru
#  di luar yang sudah ada di VIZ 9 + bar chart Top Rules)
ggplot(rules_df, aes(x = Support, y = Confidence, size = Lift, color = Lift)) +
  geom_point(alpha = 0.8) +
  scale_color_gradient(low = "#F2D0A4", high = "#3E1C00") +
  scale_size_continuous(range = c(3, 10)) +
  labs(title    = "Association Rules: Support vs Confidence",
       subtitle = "Ukuran & warna titik = nilai Lift",
       x = "Support", y = "Confidence",
       color = "Lift", size = "Lift",
       caption  = "Gambar 9. Titik kanan atas = rules terbaik (sering terjadi & asosiasi kuat).") +
  theme_minimal(base_size = 11) +
  theme(plot.title   = element_text(face = "bold", color = "#3E1C00"),
        plot.caption = element_text(hjust = 0, color = "#6F3811", size = 9))

# VIZ 10: Heatmap Lift Antar Produk
ggplot(rules_df, aes(x = Consequent, y = Antecedent, fill = Lift)) +
  geom_tile(color = "white") +
  geom_text(aes(label = round(Lift, 2)), size = 2.8, color = "white") +
  scale_fill_gradient(low = "#FBE9D0", high = "#3E1C00") +
  labs(title    = "Heatmap Kekuatan Asosiasi (Lift)",
       subtitle = "Baris = produk pemicu | Kolom = produk yang cenderung dibeli juga",
       x = "Consequent", y = "Antecedent", fill = "Lift",
       caption  = "Gambar 10. Kotak gelap = asosiasi sangat kuat antar dua produk.") +
  theme_minimal(base_size = 10) +
  theme(plot.title   = element_text(face = "bold", color = "#3E1C00"),
        axis.text.x  = element_text(angle = 45, hjust = 1),
        plot.caption = element_text(hjust = 0, color = "#6F3811", size = 9))

# VIZ 11: Bar Chart Top Rules by Lift
ggplot(rules_df %>% head(15),
       aes(x = reorder(Rule_Str, Lift), y = Lift, fill = Kekuatan)) +
  geom_col(width = 0.7) +
  geom_text(aes(label = paste0("Lift=", Lift, " | Conf=", Confidence)),
            hjust = -0.05, size = 2.8, color = "#3E1C00") +
  coord_flip() +
  scale_fill_manual(values = c("Sangat Kuat" = "#3E1C00",
                               "Kuat"        = "#A0522D",
                               "Cukup"       = "#C68642")) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.3))) +
  labs(title    = "Top 15 Association Rules Berdasarkan Lift",
       subtitle = "Dasar kuantitatif rekomendasi product bundling",
       x = NULL, y = "Lift", fill = "Kekuatan Rules",
       caption  = "Gambar 11. {A} -> {B}: jika pelanggan beli A, rekomendasikan B.") +
  theme_minimal(base_size = 10) +
  theme(plot.title   = element_text(face = "bold", color = "#3E1C00"),
        plot.caption = element_text(hjust = 0, color = "#6F3811", size = 9))

# ── Buat co_matrix lebih awal (dibutuhkan VIZ 12) ──
co_matrix       <- t(data_matrix) %*% data_matrix
diag(co_matrix) <- 0
colnames(co_matrix) <- gsub("_", " ", colnames(co_matrix))
rownames(co_matrix) <- gsub("_", " ", rownames(co_matrix))

# VIZ 12: Heatmap Co-occurrence Antar Produk 
# Lebih mudah dibaca: baris × kolom = pasangan produk,
# warna = seberapa sering keduanya terjual di hari yang sama.

co_heat <- as.data.frame(as.table(co_matrix)) %>%
  rename(Produk1 = Var1, Produk2 = Var2, CoOccurrence = Freq)

ggplot(co_heat, aes(x = Produk2, y = Produk1, fill = CoOccurrence)) +
  geom_tile(color = "white", linewidth = 0.4) +
  geom_text(aes(label = ifelse(CoOccurrence > 0, CoOccurrence, "")),
            size = 2.5, color = "white") +
  scale_fill_gradient(low = "#FBE9D0", high = "#3E1C00", labels = comma) +
  labs(title    = "Heatmap Co-occurrence Antar Produk",
       subtitle = "Angka = jumlah hari kedua produk terjual bersamaan",
       x = NULL, y = NULL, fill = "Hari\nBersamaan",
       caption  = "Gambar 12. Warna gelap = pasangan produk yang paling sering muncul di hari yang sama.") +
  theme_minimal(base_size = 10) +
  theme(plot.title   = element_text(face = "bold", color = "#3E1C00"),
        axis.text.x  = element_text(angle = 45, hjust = 1),
        plot.caption = element_text(hjust = 0, color = "#6F3811", size = 9))

# ============================================================
# BAGIAN 13 — CO-OCCURRENCE MATRIX & VIZ 13
# ============================================================
co_df <- as.data.frame(as.table(co_matrix)) %>%
  rename(Produk1 = Var1, Produk2 = Var2, CoOccurrence = Freq) %>%
  filter(as.character(Produk1) < as.character(Produk2), CoOccurrence > 0) %>%
  arrange(desc(CoOccurrence)) %>%
  head(20) %>%
  mutate(Pasangan = paste(Produk1, "-", Produk2))

ggplot(co_df,
       aes(x = reorder(Pasangan, CoOccurrence), y = CoOccurrence)) +
  geom_col(fill = "#A0522D", alpha = 0.9, width = 0.7) +
  geom_text(aes(label = paste0(CoOccurrence, " hari")),
            hjust = -0.1, size = 3.2, color = "#3E1C00") +
  coord_flip() +
  scale_y_continuous(expand = expansion(mult = c(0, 0.18))) +
  labs(title    = "Top 20 Pasangan Produk dengan Co-occurrence Tertinggi",
       subtitle = "Puan Kopi Gunung Malang | Maret–September 2025",
       x = NULL, y = "Jumlah Hari Terjual Bersamaan",
       caption  = "Gambar 13. Pasangan produk yang paling sering muncul di hari yang sama.") +
  theme_minimal(base_size = 11) +
  theme(plot.title   = element_text(face = "bold", color = "#3E1C00"),
        plot.caption = element_text(hjust = 0, color = "#6F3811", size = 9))

# ============================================================
# BAGIAN 14 — KORELASI ANTAR PRODUK (VIZ 14)
# ============================================================
data_biner_cor <- data_biner %>% select(where(~ sd(., na.rm = TRUE) > 0))
cor_matrix     <- cor(data_biner_cor, use = "pairwise.complete.obs")
cor_matrix[is.nan(cor_matrix)] <- 0
cor_matrix[is.na(cor_matrix)]  <- 0
colnames(cor_matrix) <- gsub("_", " ", colnames(cor_matrix))
rownames(cor_matrix) <- gsub("_", " ", rownames(cor_matrix))

corrplot(
  cor_matrix,
  method      = "color",
  type        = "upper",
  order       = "hclust",
  addCoef.col = "#3E1C00",
  number.cex  = 0.65,
  tl.cex      = 0.8,
  tl.col      = "#3E1C00",
  col         = colorRampPalette(c("#FBE9D0", "#C68642", "#3E1C00"))(200),
  title       = "Gambar 14: Korelasi Antar Produk — Puan Kopi Gunung Malang",
  mar         = c(0, 0, 3, 0)
)
mtext("Kotak gelap = korelasi positif kuat (sering dibeli bersamaan).",
      side = 1, line = 1, cex = 0.72, col = "#6F3811", adj = 0)

# ============================================================
# BAGIAN 15 — REKOMENDASI PRODUCT BUNDLING + HARGA AKTUAL
# ============================================================
bundle_dari_rules <- rules_df %>%
  filter(Lift > MIN_LIFT, Confidence >= MIN_CONFIDENCE) %>%
  arrange(desc(Lift), desc(Confidence)) %>%
  mutate(
    Rekomendasi_Bundle = paste0(Antecedent, " + ", Consequent),
    Harga_A      = harga_satuan[gsub(" ", "_", Antecedent)],
    Harga_B      = harga_satuan[gsub(" ", "_", Consequent)],
    Harga_Normal = Harga_A + Harga_B,
    Diskon_Pct   = case_when(
      Kekuatan == "Sangat Kuat" ~ 0.15,
      Kekuatan == "Kuat"        ~ 0.10,
      TRUE                      ~ 0.05
    ),
    Harga_Bundle = round(Harga_Normal * (1 - Diskon_Pct) / 500) * 500,
    Hemat        = Harga_Normal - Harga_Bundle,
    Dasar        = paste0(Count, " hari | Conf ",
                          round(Confidence * 100), "% | Lift ", Lift)
  ) %>%
  select(Rekomendasi_Bundle, Harga_Normal, Harga_Bundle, Hemat,
         Support, Confidence, Lift, Count, Kekuatan, Diskon_Pct, Dasar)

top_bundle <- bundle_dari_rules %>%
  distinct(Rekomendasi_Bundle, .keep_all = TRUE) %>%
  head(4) %>%
  mutate(
    No            = row_number(),
    Nama_Bundle   = c("Nongkrong Kit",
                      "Work From Cafe Pack",
                      "Healing Combo",
                      "Makan Bareng Bundle")[1:4],
    Target_Segmen = c(
      "Gen Z & milenial yang nongkrong sore/malam",
      "Mahasiswa dan pekerja yang bawa laptop",
      "Pelanggan yang butuh me-time atau istirahat",
      "Pelanggan yang datang untuk makan"
    )[1:4]
  )
# ============================================================
# BAGIAN 16 — VISUALISASI BUNDLE (VIZ 15–16)
# ============================================================

# VIZ 15: Kekuatan Asosiasi per Bundle
# (VIZ 18 asli dihapus — metrik Support/Confidence/Lift sudah tertera
#  sebagai label di chart ini, sehingga bar chart 3 metrik terpisah redundan)
ggplot(bundle_dari_rules %>%
         distinct(Rekomendasi_Bundle, .keep_all = TRUE) %>%
         head(12),
       aes(x = reorder(Rekomendasi_Bundle, Lift), y = Lift, fill = Kekuatan)) +
  geom_col(width = 0.7) +
  geom_text(
    aes(label = paste0("Lift=", Lift, "  Conf=", round(Confidence * 100), "%",
                       "  (", Count, " hari)")),
    hjust = -0.04, size = 2.7, color = "#3E1C00"
  ) +
  coord_flip() +
  scale_fill_manual(values = c("Sangat Kuat" = "#3E1C00",
                               "Kuat"        = "#A0522D",
                               "Cukup"       = "#C68642")) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.35))) +
  labs(title    = "Rekomendasi Product Bundling — Kekuatan Asosiasi",
       subtitle = "Setiap pasangan diturunkan langsung dari hasil MBA",
       x = NULL, y = "Lift", fill = "Kekuatan",
       caption  = "Gambar 15. Warna gelap = prioritas utama implementasi bundling.") +
  theme_minimal(base_size = 10) +
  theme(plot.title      = element_text(face = "bold", color = "#3E1C00", size = 11),
        plot.caption    = element_text(hjust = 0, color = "#6F3811", size = 9),
        legend.position = "right")

# VIZ 16: Perbandingan Harga Satuan vs Harga Bundle (Top 8)
harga_viz <- bundle_dari_rules %>%
  distinct(Rekomendasi_Bundle, .keep_all = TRUE) %>%
  head(8) %>%
  select(Rekomendasi_Bundle, Harga_Normal, Harga_Bundle) %>%
  pivot_longer(-Rekomendasi_Bundle, names_to = "Tipe", values_to = "Harga") %>%
  mutate(Tipe = recode(Tipe,
                       "Harga_Normal" = "Harga Satuan",
                       "Harga_Bundle" = "Harga Bundle"))

ggplot(harga_viz,
       aes(x = reorder(Rekomendasi_Bundle, Harga), y = Harga, fill = Tipe)) +
  geom_col(position = "dodge", width = 0.7) +
  geom_text(aes(label = paste0("Rp ", format(Harga, big.mark = "."))),
            position = position_dodge(width = 0.7),
            hjust = -0.05, size = 2.6, color = "#3E1C00") +
  coord_flip() +
  scale_fill_manual(values = c("Harga Satuan" = "#D2A679",
                               "Harga Bundle" = "#3E1C00")) +
  scale_y_continuous(
    labels = function(x) paste0("Rp ", format(x, big.mark = ".")),
    expand = expansion(mult = c(0, 0.4))
  ) +
  labs(title    = "Perbandingan Harga Satuan vs Harga Bundle",
       subtitle = "Selisih harga = nilai penghematan yang ditawarkan kepada pelanggan",
       x = NULL, y = "Harga (Rp)", fill = NULL,
       caption  = "Gambar 16. Batang gelap = harga bundle (lebih murah dari beli satuan). Harga dari MENU PUAN KOPI.") +
  theme_minimal(base_size = 10) +
  theme(plot.title      = element_text(face = "bold", color = "#3E1C00"),
        plot.caption    = element_text(hjust = 0, color = "#6F3811", size = 9),
        legend.position = "top")
## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' and 'decimal.mark' are both '.', which could be confusing
## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' and 'decimal.mark' are both '.', which could be confusing

# ============================================================
# BAGIAN 17 — STRATEGI CRM
# ============================================================
strategi_dari_rules <- top_bundle %>%
  mutate(
    Strategi_CRM = case_when(
      Kekuatan == "Sangat Kuat" ~ paste0(
        "Tampilkan '", Nama_Bundle, "' di menu utama. Diskon ",
        Diskon_Pct * 100, "%. Kasir aktif menawarkan."
      ),
      Kekuatan == "Kuat" ~ paste0(
        "Diskon ", Diskon_Pct * 100, "% untuk '", Nama_Bundle,
        "'. Promosi via Instagram Story & WhatsApp broadcast."
      ),
      TRUE ~ paste0(
        "Cross-selling: saat pelanggan pesan ", Rekomendasi_Bundle,
        " sebagai pelengkap."
      )
    ),
    Dasar_MBA = paste0("Lift=", Lift, " | Conf=", round(Confidence * 100),
                       "% | ", Count, " hari bersamaan")
  ) %>%
  select(No, Nama_Bundle, Rekomendasi_Bundle, Target_Segmen,
         Harga_Normal, Harga_Bundle, Hemat,
         Kekuatan, Strategi_CRM, Dasar_MBA)

cat("\n=== STRATEGI CRM PER BUNDLE ===\n\n")
## 
## === STRATEGI CRM PER BUNDLE ===
print(strategi_dari_rules)
##   No         Nama_Bundle       Rekomendasi_Bundle
## 1  1       Nongkrong Kit Mie Goreng + Nasi Goreng
## 2  2 Work From Cafe Pack Nasi Goreng + Mie Goreng
## 3  3       Healing Combo       Espresso + Cookies
## 4  4 Makan Bareng Bundle    Espresso + Tahu Bakso
##                                 Target_Segmen Harga_Normal Harga_Bundle Hemat
## 1  Gen Z & milenial yang nongkrong sore/malam        48000        45500  2500
## 2      Mahasiswa dan pekerja yang bawa laptop        48000        45500  2500
## 3 Pelanggan yang butuh me-time atau istirahat        15000        14000  1000
## 4           Pelanggan yang datang untuk makan        32000        30500  1500
##   Kekuatan
## 1    Cukup
## 2    Cukup
## 3    Cukup
## 4    Cukup
##                                                                      Strategi_CRM
## 1 Cross-selling: saat pelanggan pesan Mie Goreng + Nasi Goreng sebagai pelengkap.
## 2 Cross-selling: saat pelanggan pesan Nasi Goreng + Mie Goreng sebagai pelengkap.
## 3       Cross-selling: saat pelanggan pesan Espresso + Cookies sebagai pelengkap.
## 4    Cross-selling: saat pelanggan pesan Espresso + Tahu Bakso sebagai pelengkap.
##                                    Dasar_MBA
## 1  Lift=1.067 | Conf=84% | 94 hari bersamaan
## 2  Lift=1.067 | Conf=65% | 94 hari bersamaan
## 3 Lift=1.063 | Conf=81% | 100 hari bersamaan
## 4  Lift=1.063 | Conf=77% | 95 hari bersamaan
strategi_crm_lengkap <- data.frame(
  No       = 1:6,
  Strategi = c("Bundle Pricing", "Loyalty Stamp Card", "Promo Jam Sibuk",
               "Weekend Vibes Bundle", "Smart Cross-Selling", "Member Poin Ganda"),
  Deskripsi = c(
    paste0("Harga bundle lebih murah dari beli satuan (diskon 5–15%). Ditampilkan ",
           "sebagai 'rekomendasi kasir' di papan menu."),
    "Setiap pembelian bundle mendapat 1 stamp. Kumpulkan 5 stamp = 1 minuman gratis.",
    "Harga spesial jam 11.00–14.00 untuk bundle makanan. Menyasar pekerja milenial.",
    "Bundle eksklusif Sabtu–Minggu dengan nama & packaging khusus.",
    paste0("Kasir dibekali daftar ", nrow(rules_df),
           " association rules. Contoh: pelanggan pesan kopi → kasir tawarkan menu pelengkap terkuat."),
    "Member dapat poin 2× saat beli bundle. Mendorong loyalitas & meningkatkan nilai transaksi."
  ),
  Target_Segmen = c(
    "Gen Z dan milenial, semua bundle",
    "Pelanggan reguler Gen Z",
    "Milenial pekerja, bundle makanan",
    "Gen Z dan milenial, akhir pekan",
    "Semua pelanggan",
    "Member terdaftar"
  ),
  Dasar_Keputusan = c(
    "Lift & Confidence tertinggi dari hasil MBA",
    "Pola kunjungan berulang dari data harian",
    "Co-occurrence makanan dan minuman tinggi",
    "Hasil analisis Weekday vs Weekend",
    paste0(nrow(rules_df), " association rules dari data"),
    "Strategi retensi berbasis data transaksi"
  )
)

cat("\n=== TABEL STRATEGI CRM LENGKAP ===\n\n")
## 
## === TABEL STRATEGI CRM LENGKAP ===
print(strategi_crm_lengkap)
##   No             Strategi
## 1  1       Bundle Pricing
## 2  2   Loyalty Stamp Card
## 3  3      Promo Jam Sibuk
## 4  4 Weekend Vibes Bundle
## 5  5  Smart Cross-Selling
## 6  6    Member Poin Ganda
##                                                                                                           Deskripsi
## 1  Harga bundle lebih murah dari beli satuan (diskon 5–15%). Ditampilkan sebagai 'rekomendasi kasir' di papan menu.
## 2                                   Setiap pembelian bundle mendapat 1 stamp. Kumpulkan 5 stamp = 1 minuman gratis.
## 3                                    Harga spesial jam 11.00–14.00 untuk bundle makanan. Menyasar pekerja milenial.
## 4                                                     Bundle eksklusif Sabtu–Minggu dengan nama & packaging khusus.
## 5 Kasir dibekali daftar 80 association rules. Contoh: pelanggan pesan kopi → kasir tawarkan menu pelengkap terkuat.
## 6                        Member dapat poin 2× saat beli bundle. Mendorong loyalitas & meningkatkan nilai transaksi.
##                      Target_Segmen                            Dasar_Keputusan
## 1 Gen Z dan milenial, semua bundle Lift & Confidence tertinggi dari hasil MBA
## 2          Pelanggan reguler Gen Z   Pola kunjungan berulang dari data harian
## 3 Milenial pekerja, bundle makanan   Co-occurrence makanan dan minuman tinggi
## 4  Gen Z dan milenial, akhir pekan          Hasil analisis Weekday vs Weekend
## 5                  Semua pelanggan             80 association rules dari data
## 6                 Member terdaftar   Strategi retensi berbasis data transaksi
# ============================================================
# BAGIAN 18 — EXPORT CSV
# ============================================================
write.csv(
  rules_df %>% select(No, Rule_Str, Support, Confidence, Lift, Count, Kekuatan),
  "01_association_rules.csv", row.names = FALSE
)
write.csv(freq_1,               "02_frequent_itemsets.csv",      row.names = FALSE)
write.csv(bundle_dari_rules,    "03_rekomendasi_bundling.csv",   row.names = FALSE)
write.csv(strategi_crm_lengkap, "04_strategi_crm.csv",           row.names = FALSE)
write.csv(total_produk,         "05_total_penjualan_produk.csv", row.names = FALSE)
write.csv(co_df,                "06_top_cooccurrence.csv",       row.names = FALSE)

cat("\nFile CSV tersimpan: 01_association_rules.csv sampai 06_top_cooccurrence.csv\n\n")
## 
## File CSV tersimpan: 01_association_rules.csv sampai 06_top_cooccurrence.csv
# ============================================================
# BAGIAN 19 — RINGKASAN AKHIR
# ============================================================
cat("============================================================\n")
## ============================================================
cat("  RINGKASAN AKHIR — Puan Kopi Gunung Malang\n")
##   RINGKASAN AKHIR — Puan Kopi Gunung Malang
cat("============================================================\n")
## ============================================================
cat("Periode    :", format(min(data$Tanggal)), "s.d.", format(max(data$Tanggal)), "\n")
## Periode    : 2025-03-29 s.d. 2025-09-30
cat("Total hari :", nrow(data), "| Jumlah produk: 17\n")
## Total hari : 183 | Jumlah produk: 17
cat("Catatan    : Outlier Espresso (48 unit, 28 Jun) diganti dengan median\n")
## Catatan    : Outlier Espresso (48 unit, 28 Jun) diganti dengan median
cat("------------------------------------------------------------\n")
## ------------------------------------------------------------
cat("Hasil MBA:\n")
## Hasil MBA:
cat("  Frequent items   :", nrow(freq_1),
    "produk (support >= 50%) — Kapiten, Fudgy Brownies, Fudgy Brownies Aya tidak lolos\n")
##   Frequent items   : 14 produk (support >= 50%) — Kapiten, Fudgy Brownies, Fudgy Brownies Aya tidak lolos
cat("  Association rules:", nrow(rules_df), "rules\n")
##   Association rules: 80 rules
cat("  Sangat Kuat      :", nrow(rules_df %>% filter(Kekuatan == "Sangat Kuat")), "\n")
##   Sangat Kuat      : 0
cat("  Kuat             :", nrow(rules_df %>% filter(Kekuatan == "Kuat")), "\n")
##   Kuat             : 0
cat("------------------------------------------------------------\n")
## ------------------------------------------------------------
cat("Top 4 Bundle (harga dari MENU PUAN KOPI):\n")
## Top 4 Bundle (harga dari MENU PUAN KOPI):
for (i in 1:nrow(top_bundle)) {
  cat(" ", top_bundle$Nama_Bundle[i], "-", top_bundle$Rekomendasi_Bundle[i], "\n")
  cat("   Normal: Rp", format(top_bundle$Harga_Normal[i], big.mark = "."),
      "| Bundle: Rp", format(top_bundle$Harga_Bundle[i], big.mark = "."),
      "| Hemat: Rp", format(top_bundle$Hemat[i], big.mark = "."),
      paste0("(", top_bundle$Diskon_Pct[i]*100, "%) | Lift=", top_bundle$Lift[i], ")\n"))
}
##   Nongkrong Kit - Mie Goreng + Nasi Goreng
## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' and 'decimal.mark' are both '.', which could be confusing
## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' and 'decimal.mark' are both '.', which could be confusing
## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' and 'decimal.mark' are both '.', which could be confusing
##    Normal: Rp 48.000 | Bundle: Rp 45.500 | Hemat: Rp 2.500 (5%) | Lift=1.067)
##   Work From Cafe Pack - Nasi Goreng + Mie Goreng
## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' and 'decimal.mark' are both '.', which could be confusing
## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' and 'decimal.mark' are both '.', which could be confusing
## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' and 'decimal.mark' are both '.', which could be confusing
##    Normal: Rp 48.000 | Bundle: Rp 45.500 | Hemat: Rp 2.500 (5%) | Lift=1.067)
##   Healing Combo - Espresso + Cookies
## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' and 'decimal.mark' are both '.', which could be confusing
## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' and 'decimal.mark' are both '.', which could be confusing
## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' and 'decimal.mark' are both '.', which could be confusing
##    Normal: Rp 15.000 | Bundle: Rp 14.000 | Hemat: Rp 1.000 (5%) | Lift=1.063)
##   Makan Bareng Bundle - Espresso + Tahu Bakso
## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' and 'decimal.mark' are both '.', which could be confusing
## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' and 'decimal.mark' are both '.', which could be confusing
## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' and 'decimal.mark' are both '.', which could be confusing
##    Normal: Rp 32.000 | Bundle: Rp 30.500 | Hemat: Rp 1.500 (5%) | Lift=1.063)
cat("------------------------------------------------------------\n")
## ------------------------------------------------------------
cat("Strategi CRM : 6 strategi | Visualisasi: 16 grafik | CSV: 6 file\n")
## Strategi CRM : 6 strategi | Visualisasi: 16 grafik | CSV: 6 file
cat("============================================================\n")
## ============================================================
# ============================================================
# END OF SCRIPT
# ============================================================