# 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)
library(igraph)
## Warning: package 'igraph' was built under R version 4.3.3
##
## Attaching package: 'igraph'
## The following objects are masked from 'package:lubridate':
##
## %--%, union
## The following object is masked from 'package:tidyr':
##
## crossing
## The following objects are masked from 'package:dplyr':
##
## as_data_frame, groups, union
## The following objects are masked from 'package:stats':
##
## decompose, spectrum
## The following object is masked from 'package:base':
##
## union
# IMPORT DATA
data <- read_excel(
"C:/Users/ASUS/OneDrive/Documents/CRM_KELOMPOK 3/DATA PUAN COFEE GUNMAL_APRIL-SEPTEMBER 2025.xlsx",
sheet = "PEMASUKAN"
)
# RENAME KOLOM
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"
)
# CLEANING DATA
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))
# STATISTIK DESKRIPTIF
summary(data)
## Tanggal Cup_Dingin Cup_Panas Air_Mineral
## Min. :2025-03-29 Min. : 83.0 Min. : 0.000 Min. : 0.000
## 1st Qu.:2025-05-16 1st Qu.:131.0 1st Qu.: 1.000 1st Qu.: 4.000
## Median :2025-07-01 Median :147.0 Median : 2.000 Median : 6.000
## Mean :2025-06-30 Mean :149.6 Mean : 2.623 Mean : 6.956
## 3rd Qu.:2025-08-15 3rd Qu.:168.5 3rd Qu.: 4.000 3rd Qu.:10.000
## Max. :2025-09-30 Max. :255.0 Max. :13.000 Max. :21.000
##
## Espresso Kapiten Cookies Fudgy_Brownies
## Min. : 0.000 Min. :0.0000 Min. : 0.000 Min. :0.0000
## 1st Qu.: 0.000 1st Qu.:0.0000 1st Qu.: 1.000 1st Qu.:0.0000
## Median : 1.000 Median :0.0000 Median : 3.000 Median :0.0000
## Mean : 1.527 Mean :0.3661 Mean : 4.098 Mean :0.2131
## 3rd Qu.: 2.000 3rd Qu.:1.0000 3rd Qu.: 7.000 3rd Qu.:0.0000
## Max. :48.000 Max. :4.0000 Max. :30.000 Max. :6.0000
## NA's :1
## Fudgy_Brownies_Aya Cireng Singkong Kentang
## Min. :0.0000 Min. : 0.000 Min. :0.000 Min. : 0.000
## 1st Qu.:0.0000 1st Qu.: 4.000 1st Qu.:1.000 1st Qu.: 2.000
## Median :0.0000 Median : 6.000 Median :2.000 Median : 4.000
## Mean :0.9563 Mean : 6.104 Mean :1.869 Mean : 4.087
## 3rd Qu.:2.0000 3rd Qu.: 8.000 3rd Qu.:3.000 3rd Qu.: 6.000
## Max. :6.0000 Max. :18.000 Max. :7.000 Max. :11.000
##
## Dimsum Pentol Tahu_Bakso Mie_Goreng Mie_Kuah
## Min. :0.000 Min. :0.0000 Min. :0.00 Min. :0.00 Min. :0.000
## 1st Qu.:1.000 1st Qu.:0.0000 1st Qu.:0.00 1st Qu.:0.00 1st Qu.:0.000
## Median :1.000 Median :1.0000 Median :1.00 Median :1.00 Median :1.000
## Mean :1.486 Mean :0.7486 Mean :1.47 Mean :1.23 Mean :1.175
## 3rd Qu.:2.000 3rd Qu.:1.0000 3rd Qu.:2.00 3rd Qu.:2.00 3rd Qu.:2.000
## Max. :7.000 Max. :5.0000 Max. :8.00 Max. :8.00 Max. :9.000
##
## Nasi_Goreng
## Min. :0.000
## 1st Qu.:1.000
## Median :2.000
## Mean :1.743
## 3rd Qu.:3.000
## Max. :6.000
##
# VISUALISASI 1 - TOTAL PENJUALAN PER PRODUK
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))
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 (April–September 2025)",
subtitle = "Puan Kopi Gunung Malang",
x = NULL, y = "Jumlah Terjual (Unit)", fill = "Kategori") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", color = "#3E1C00"))

# VISUALISASI 2 - TREN PENJUALAN BULANAN
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 | April–September 2025",
x = NULL, y = "Jumlah Terjual", color = "Produk") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", color = "#3E1C00"))

# VISUALISASI 3 - HEATMAP PENJUALAN BULANAN
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 | April–September 2025",
x = NULL, y = NULL, fill = "Total\nTerjual") +
theme_minimal(base_size = 11) +
theme(plot.title = element_text(face = "bold", color = "#3E1C00"),
axis.text.x = element_text(angle = 30, hjust = 1))

# DATA BINER
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))
# PENENTUAN THRESHOLD
freq_item <- colSums(data_biner)
support_produk <- freq_item / n_trans
names(support_produk) <- prod_names
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
MIN_SUPPORT <- 0.50
MIN_CONFIDENCE <- 0.60
MIN_LIFT <- 1.0
# VISUALISASI 4 - ITEM FREQUENCY PLOT
freq_df <- data.frame(
Produk = prod_names,
Frekuensi = as.numeric(freq_item)
) %>% arrange(desc(Frekuensi)) %>% head(17)
ggplot(freq_df, aes(x = reorder(Produk, Frekuensi), y = Frekuensi)) +
geom_col(fill = "#A0522D", width = 0.7) +
geom_text(aes(label = Frekuensi), hjust = -0.2, size = 3.2, color = "#3E1C00") +
coord_flip() +
scale_y_continuous(expand = expansion(mult = c(0, 0.15))) +
labs(title = "Frekuensi Kemunculan Tiap Produk\nPuan Kopi Gunung Malang",
x = NULL, y = "Frekuensi (Hari)") +
theme_minimal(base_size = 11) +
theme(plot.title = element_text(face = "bold", color = "#3E1C00"))

# FREQUENT 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))
cat("\n=== FREQUENT 1-ITEMSETS ===\n")
##
## === FREQUENT 1-ITEMSETS ===
print(freq_1)
## Itemset Support Count
## Cup Dingin Cup Dingin 1.0000 183
## Cireng Cireng 0.9836 180
## Air Mineral Air Mineral 0.9727 178
## Kentang Kentang 0.9508 174
## Cup Panas Cup Panas 0.8525 156
## Singkong Singkong 0.8142 149
## Nasi Goreng Nasi Goreng 0.7869 144
## Cookies Cookies 0.7650 140
## Dimsum Dimsum 0.7650 140
## Tahu Bakso Tahu Bakso 0.7268 133
## Espresso Espresso 0.6721 123
## Mie Goreng Mie Goreng 0.6120 112
## Mie Kuah Mie Kuah 0.5355 98
## Pentol Pentol 0.5301 97
# APRIORI MANUAL - ASSOCIATION RULES
cols <- colnames(data_biner)
n_cols <- length(cols)
rules_list <- list()
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
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)
)
}
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()) %>%
select(No, everything())
cat("\n=== ASSOCIATION RULES (lift > 1) ===\n")
##
## === ASSOCIATION RULES (lift > 1) ===
print(rules_df)
## No Antecedent Consequent Support Confidence Lift Count
## 1 1 Mie Goreng Nasi Goreng 0.514 0.839 1.067 94
## 2 2 Nasi Goreng Mie Goreng 0.514 0.653 1.067 94
## 3 3 Espresso Cookies 0.546 0.813 1.063 100
## 4 4 Espresso Tahu Bakso 0.519 0.772 1.063 95
## 5 5 Cookies Espresso 0.546 0.714 1.063 100
## 6 6 Tahu Bakso Espresso 0.519 0.714 1.063 95
## 7 7 Tahu Bakso Dimsum 0.579 0.797 1.042 106
## 8 8 Dimsum Tahu Bakso 0.579 0.757 1.042 106
## 9 9 Espresso Singkong 0.568 0.846 1.038 104
## 10 10 Singkong Espresso 0.568 0.698 1.038 104
## 11 11 Mie Goreng Cup Panas 0.541 0.884 1.037 99
## 12 12 Cup Panas Mie Goreng 0.541 0.635 1.037 99
## 13 13 Tahu Bakso Singkong 0.612 0.842 1.034 112
## 14 14 Singkong Tahu Bakso 0.612 0.752 1.034 112
## 15 15 Espresso Nasi Goreng 0.546 0.813 1.033 100
## 16 16 Nasi Goreng Espresso 0.546 0.694 1.033 100
## 17 17 Nasi Goreng Singkong 0.661 0.840 1.032 121
## 18 18 Singkong Nasi Goreng 0.661 0.812 1.032 121
## 19 19 Pentol Kentang 0.519 0.979 1.030 95
## 20 20 Nasi Goreng Kentang 0.770 0.979 1.030 141
## 21 21 Kentang Nasi Goreng 0.770 0.810 1.030 141
## 22 22 Espresso Kentang 0.656 0.976 1.026 120
## 23 23 Kentang Espresso 0.656 0.690 1.026 120
## 24 24 Singkong Kentang 0.792 0.973 1.023 145
## 25 25 Kentang Singkong 0.792 0.833 1.023 145
## 26 26 Cookies Cup Panas 0.667 0.871 1.022 122
## 27 27 Cup Panas Cookies 0.667 0.782 1.022 122
## 28 28 Tahu Bakso Cookies 0.568 0.782 1.022 104
## 29 29 Cookies Tahu Bakso 0.568 0.743 1.022 104
## 30 30 Nasi Goreng Cup Panas 0.683 0.868 1.018 125
## 31 31 Cup Panas Nasi Goreng 0.683 0.801 1.018 125
## 32 32 Pentol Cireng 0.530 1.000 1.017 97
## 33 33 Tahu Bakso Cireng 0.727 1.000 1.017 133
## 34 34 Mie Goreng Cireng 0.612 1.000 1.017 112
## 35 35 Mie Kuah Cireng 0.536 1.000 1.017 98
## 36 36 Cireng Tahu Bakso 0.727 0.739 1.017 133
## 37 37 Cireng Mie Goreng 0.612 0.622 1.017 112
## 38 38 Dimsum Kentang 0.738 0.964 1.014 135
## 39 39 Mie Goreng Kentang 0.590 0.964 1.014 108
## 40 40 Kentang Dimsum 0.738 0.776 1.014 135
## 41 41 Kentang Mie Goreng 0.590 0.621 1.014 108
## 42 42 Dimsum Air Mineral 0.754 0.986 1.013 138
## 43 43 Air Mineral Dimsum 0.754 0.775 1.013 138
## 44 44 Tahu Bakso Kentang 0.699 0.962 1.012 128
## 45 45 Kentang Tahu Bakso 0.699 0.736 1.012 128
## 46 46 Nasi Goreng Cireng 0.781 0.993 1.010 143
## 47 47 Cireng Nasi Goreng 0.781 0.794 1.010 143
## 48 48 Dimsum Cireng 0.760 0.993 1.009 139
## 49 49 Mie Kuah Kentang 0.514 0.959 1.009 94
## 50 50 Cookies Singkong 0.628 0.821 1.009 115
## 51 51 Mie Goreng Singkong 0.503 0.821 1.009 92
## 52 52 Singkong Cookies 0.628 0.772 1.009 115
## 53 53 Cireng Dimsum 0.760 0.772 1.009 139
## 54 54 Singkong Mie Goreng 0.503 0.617 1.009 92
## 55 55 Espresso Cireng 0.667 0.992 1.008 122
## 56 56 Cookies Nasi Goreng 0.607 0.793 1.008 111
## 57 57 Nasi Goreng Cookies 0.607 0.771 1.008 111
## 58 58 Cireng Espresso 0.667 0.678 1.008 122
## 59 59 Singkong Air Mineral 0.798 0.980 1.007 146
## 60 60 Nasi Goreng Air Mineral 0.770 0.979 1.007 141
## 61 61 Air Mineral Singkong 0.798 0.820 1.007 146
## 62 62 Air Mineral Nasi Goreng 0.770 0.792 1.007 141
## 63 63 Cookies Air Mineral 0.749 0.979 1.006 137
## 64 64 Air Mineral Cookies 0.749 0.770 1.006 137
## 65 65 Air Mineral Cireng 0.962 0.989 1.005 176
## 66 66 Kentang Cireng 0.940 0.989 1.005 172
## 67 67 Cireng Air Mineral 0.962 0.978 1.005 176
## 68 68 Cireng Kentang 0.940 0.956 1.005 172
## 69 69 Tahu Bakso Cup Panas 0.623 0.857 1.005 114
## 70 70 Cup Panas Tahu Bakso 0.623 0.731 1.005 114
## 71 71 Kentang Air Mineral 0.929 0.977 1.004 170
## 72 72 Air Mineral Kentang 0.929 0.955 1.004 170
## 73 73 Singkong Cireng 0.803 0.987 1.003 147
## 74 74 Cireng Singkong 0.803 0.817 1.003 147
## 75 75 Cookies Cireng 0.754 0.986 1.002 138
## 76 76 Cup Panas Air Mineral 0.831 0.974 1.002 152
## 77 77 Air Mineral Cup Panas 0.831 0.854 1.002 152
## 78 78 Cireng Cookies 0.754 0.767 1.002 138
## 79 79 Dimsum Singkong 0.623 0.814 1.000 114
## 80 80 Singkong Dimsum 0.623 0.765 1.000 114
# VISUALISASI 5 - SCATTER PLOT 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 (warna = Lift)",
x = "Support", y = "Confidence",
color = "Lift", size = "Lift") +
theme_minimal(base_size = 11) +
theme(plot.title = element_text(face = "bold", color = "#3E1C00"))

# VISUALISASI 6 - NETWORK GRAPH
g <- graph_from_data_frame(
rules_df[, c("Antecedent", "Consequent", "Lift")],
directed = TRUE
)
E(g)$width <- E(g)$Lift * 0.8
E(g)$color <- colorRampPalette(c("#E8B97E", "#3E1C00"))(ecount(g))
V(g)$color <- "#C68642"
V(g)$frame.color <- "#6F3811"
V(g)$size <- 20
V(g)$label.cex <- 0.65
V(g)$label.color <- "white"
plot(g,
layout = layout_with_fr(g),
edge.arrow.size = 0.4,
main = "Network Graph Association Rules — Puan Kopi Gunung Malang")

# VISUALISASI 7 - HEATMAP RULES
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 Rules: Antecedent vs Consequent (nilai = Lift)",
x = "Consequent", y = "Antecedent", fill = "Lift") +
theme_minimal(base_size = 10) +
theme(plot.title = element_text(face = "bold", color = "#3E1C00"),
axis.text.x = element_text(angle = 45, hjust = 1))

# KORELASI ANTAR PRODUK
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 = "Korelasi Antar Produk — Puan Kopi Gunung Malang",
mar = c(0, 0, 2, 0))

# CO-OCCURRENCE MATRIX
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))
print(co_matrix)
## Cup Dingin Cup Panas Air Mineral Espresso Kapiten Cookies
## Cup Dingin 0 156 178 123 51 140
## Cup Panas 156 0 152 104 47 122
## Air Mineral 178 152 0 119 50 137
## Espresso 123 104 119 0 34 100
## Kapiten 51 47 50 34 0 41
## Cookies 140 122 137 100 41 0
## Fudgy Brownies 16 13 16 10 5 14
## Fudgy Brownies Aya 64 54 62 38 16 44
## Cireng 180 153 176 122 50 138
## Singkong 149 125 146 104 39 115
## Kentang 174 148 170 120 49 133
## Dimsum 140 118 138 89 40 103
## Pentol 97 86 94 70 25 73
## Tahu Bakso 133 114 129 95 38 104
## Mie Goreng 112 99 108 80 33 89
## Mie Kuah 98 83 95 69 29 77
## Nasi Goreng 144 125 141 100 44 111
## Fudgy Brownies Fudgy Brownies Aya Cireng Singkong Kentang
## Cup Dingin 16 64 180 149 174
## Cup Panas 13 54 153 125 148
## Air Mineral 16 62 176 146 170
## Espresso 10 38 122 104 120
## Kapiten 5 16 50 39 49
## Cookies 14 44 138 115 133
## Fudgy Brownies 0 0 16 14 16
## Fudgy Brownies Aya 0 0 64 56 60
## Cireng 16 64 0 147 172
## Singkong 14 56 147 0 145
## Kentang 16 60 172 145 0
## Dimsum 14 52 139 114 135
## Pentol 8 38 97 82 95
## Tahu Bakso 15 48 133 112 128
## Mie Goreng 12 41 112 92 108
## Mie Kuah 9 31 98 80 94
## Nasi Goreng 13 50 143 121 141
## Dimsum Pentol Tahu Bakso Mie Goreng Mie Kuah Nasi Goreng
## Cup Dingin 140 97 133 112 98 144
## Cup Panas 118 86 114 99 83 125
## Air Mineral 138 94 129 108 95 141
## Espresso 89 70 95 80 69 100
## Kapiten 40 25 38 33 29 44
## Cookies 103 73 104 89 77 111
## Fudgy Brownies 14 8 15 12 9 13
## Fudgy Brownies Aya 52 38 48 41 31 50
## Cireng 139 97 133 112 98 143
## Singkong 114 82 112 92 80 121
## Kentang 135 95 128 108 94 141
## Dimsum 0 78 106 85 77 110
## Pentol 78 0 73 70 58 83
## Tahu Bakso 106 73 0 80 74 103
## Mie Goreng 85 70 80 0 62 94
## Mie Kuah 77 58 74 62 0 86
## Nasi Goreng 110 83 103 94 86 0
# VISUALISASI 8 - TOP CO-OCCURRENCE
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 = CoOccurrence), hjust = -0.2, size = 3.2, color = "#3E1C00") +
coord_flip() +
scale_y_continuous(expand = expansion(mult = c(0, 0.15))) +
labs(title = "Top 20 Pasangan Produk Co-occurrence Tertinggi",
subtitle = "Puan Kopi Gunung Malang | April–September 2025",
x = NULL, y = "Jumlah Hari Terjual Bersamaan") +
theme_minimal(base_size = 11) +
theme(plot.title = element_text(face = "bold", color = "#3E1C00"))

# ANALISIS 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 | April–September 2025",
x = NULL, y = "Rata-rata Unit Terjual", fill = "Jenis Hari") +
theme_minimal(base_size = 11) +
theme(plot.title = element_text(face = "bold", color = "#3E1C00"),
legend.position = "bottom")

# REKOMENDASI PRODUCT BUNDLING
bundling <- data.frame(
Bundle = c("Bundle 1", "Bundle 2", "Bundle 3", "Bundle 4"),
Nama_Bundle = c("Paket Kopi Favorit", "Paket Ngopi Santai",
"Paket Makan Siang", "Paket Cemilan Grup"),
Produk = c(
"Cup Dingin + Cireng / Kentang",
"Cup Dingin + Cookies / Fudgy Brownies",
"Nasi Goreng / Mie Goreng + Cup Dingin",
"Cireng + Kentang + Dimsum + Air Mineral"
),
Dasar_Rules = c(
"Support & Lift tertinggi antara minuman + snack",
"Asosiasi kuat minuman kopi + kue",
"Co-occurrence tinggi makanan berat + minuman",
"Produk snack sering terjual bersamaan"
)
)
print(bundling)
## Bundle Nama_Bundle Produk
## 1 Bundle 1 Paket Kopi Favorit Cup Dingin + Cireng / Kentang
## 2 Bundle 2 Paket Ngopi Santai Cup Dingin + Cookies / Fudgy Brownies
## 3 Bundle 3 Paket Makan Siang Nasi Goreng / Mie Goreng + Cup Dingin
## 4 Bundle 4 Paket Cemilan Grup Cireng + Kentang + Dimsum + Air Mineral
## Dasar_Rules
## 1 Support & Lift tertinggi antara minuman + snack
## 2 Asosiasi kuat minuman kopi + kue
## 3 Co-occurrence tinggi makanan berat + minuman
## 4 Produk snack sering terjual bersamaan
# STRATEGI CRM
strategi_crm <- data.frame(
No = 1:6,
Strategi = c("Diskon Bundle", "Stamp Card",
"Promo Jam Makan Siang", "Weekend Special",
"Cross-Selling", "Member Reward"),
Deskripsi = c(
"Diskon 10-15% untuk pembelian paket bundle",
"Beli 5x bundle = 1 minuman gratis",
"Harga spesial jam 11.00-14.00 untuk Paket Makan Siang",
"Promo bundle eksklusif Sabtu-Minggu",
"Kasir menawarkan produk pelengkap berdasarkan rules",
"Pelanggan member poin 2x lipat saat beli bundle"
),
Target_Bundle = c("Semua Bundle", "Bundle 1 & 2", "Bundle 3",
"Bundle 4", "Semua Bundle", "Semua Bundle")
)
print(strategi_crm)
## No Strategi
## 1 1 Diskon Bundle
## 2 2 Stamp Card
## 3 3 Promo Jam Makan Siang
## 4 4 Weekend Special
## 5 5 Cross-Selling
## 6 6 Member Reward
## Deskripsi Target_Bundle
## 1 Diskon 10-15% untuk pembelian paket bundle Semua Bundle
## 2 Beli 5x bundle = 1 minuman gratis Bundle 1 & 2
## 3 Harga spesial jam 11.00-14.00 untuk Paket Makan Siang Bundle 3
## 4 Promo bundle eksklusif Sabtu-Minggu Bundle 4
## 5 Kasir menawarkan produk pelengkap berdasarkan rules Semua Bundle
## 6 Pelanggan member poin 2x lipat saat beli bundle Semua Bundle
# VISUALISASI 9 - PROPORSI KATEGORI PRODUK
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 Produk",
subtitle = "Puan Kopi Gunung Malang | April–September 2025") +
theme_void() +
theme(plot.title = element_text(face = "bold", hjust = 0.5, color = "#3E1C00"),
plot.subtitle = element_text(hjust = 0.5, color = "#6F3811"),
legend.position = "none")

# VISUALISASI 10 - SUPPORT VS CONFIDENCE + LABEL
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)) +
geom_text(aes(label = paste(Antecedent, "→", Consequent)),
size = 2.5, vjust = -1, check_overlap = TRUE, color = "#3E1C00") +
labs(title = "Association Rules: Support vs Confidence",
subtitle = "Ukuran & Warna Titik = Nilai Lift",
x = "Support", y = "Confidence",
color = "Lift", size = "Lift") +
theme_minimal(base_size = 11) +
theme(plot.title = element_text(face = "bold", color = "#3E1C00"))

# VISUALISASI 11 - 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 | April–September 2025",
x = NULL, y = "Jumlah Terjual", fill = "Kategori") +
theme_minimal(base_size = 11) +
theme(plot.title = element_text(face = "bold", color = "#3E1C00"),
axis.text.x = element_text(angle = 30, hjust = 1))

# VISUALISASI 12 - TOP 10 PRODUK TERLARIS
top10 <- total_produk %>% head(10)
ggplot(top10,
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.5) +
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 = "Top 10 Produk Terlaris",
subtitle = "Puan Kopi Gunung Malang | April–September 2025",
x = NULL, y = "Total Terjual", fill = "Kategori") +
theme_minimal(base_size = 11) +
theme(plot.title = element_text(face = "bold", color = "#3E1C00"))

# RINGKASAN AKHIR
cat("================================================\n")
## ================================================
cat(" RINGKASAN MARKET BASKET ANALYSIS - CRM\n")
## RINGKASAN MARKET BASKET ANALYSIS - CRM
cat(" Puan Kopi Gunung Malang\n")
## Puan Kopi Gunung Malang
cat("================================================\n")
## ================================================
cat("Periode data :", format(min(data$Tanggal)),
"s.d.", format(max(data$Tanggal)), "\n")
## Periode data : 2025-03-29 s.d. 2025-09-30
cat("Total hari data :", nrow(data), "hari\n")
## Total hari data : 183 hari
cat("Jumlah produk : 17 produk\n")
## Jumlah produk : 17 produk
cat("Frequent itemsets :", nrow(freq_1), "(1-itemset)\n")
## Frequent itemsets : 14 (1-itemset)
cat("Total rules :", nrow(rules_df), "\n")
## Total rules : 80
cat("Min Support :", MIN_SUPPORT, "\n")
## Min Support : 0.5
cat("Min Confidence :", MIN_CONFIDENCE, "\n")
## Min Confidence : 0.6
cat("Bundle diusulkan : 4 bundle produk\n")
## Bundle diusulkan : 4 bundle produk
cat("Strategi CRM : 6 strategi\n")
## Strategi CRM : 6 strategi
cat("================================================\n")
## ================================================