Pendahuluan

Latar Belakang

Perencanaan anggaran (budgeting) adalah salah satu fungsi inti manajemen. Namun akurasi perencanaan sering tidak dievaluasi secara sistematis, apakah realisasi memang sesuai target, atau ada pola penyimpangan yang konsisten?

Dataset Coffee Chain mencatat data operasional dua tahun (2012–2013) mencakup realisasi aktual (Sales, Profit, COGS, Margin) beserta anggarannya (Budget Sales, Budget Profit, dst). Ini memungkinkan evaluasi kuantitatif terhadap kualitas perencanaan.

Tujuan Penelitian

  1. Mengevaluasi kesesuaian antara realisasi kinerja keuangan dan anggaran yang telah ditetapkan pada Coffee Chain
  2. Mengidentifikasi segmen mana, baik berdasarkan market maupun jenis produk, yang mencatatkan gap terbesar dan paling signifikan secara statistik
  3. Mengidentifikasi pola penyimpangan apakah berbeda secara bermakna antar segmen
  4. Mengidentifikasi pola kontradiktif yang memerlukan penjelasan lebih lanjut?

Hipotesis

\[H_0: \mu_{gap} = 0\]

\[H_1: \mu_{gap} \neq 0\]

Deskripsi Dataset

Gambaran Umum

Dataset ini merupakan data operasional dan penjualan sebuah jaringan kedai kopi (Coffee Chain) di Amerika Serikat yang mencakup periode Januari 2012 hingga Desember 2013. Satu baris bukan mewakili satu transaksi pelanggan, melainkan satu catatan operasional untuk kombinasi tertentu produk tertentu, di area tertentu, pada bulan tertentu. Kombinasi yang sama (misalnya: Coffee di Illinois) bisa muncul puluhan kali dengan nilai berbeda di setiap bulannya. ## Variabel yang Dipilih Variabel yang dipilih terbagi menjadi dua kelompok. Pertama, empat pasang variabel budget dan aktual yaitu Sales, Margin, Profit, dan COGS yang menghasilkan variabel gap baru seperti Sales_Gap, COGS_Gap, Margin_Gap, dan Profit_Gap. Keempat variabel ini dipilih karena bersama-sama membentuk rantai nilai finansial dari penjualan hingga keuntungan bersih, sehingga analisis gap-nya dapat menceritakan di mana penyimpangan terjadi dalam alur keuangan tersebut. Profit_Gap menjadi variabel utama karena profit adalah outcome akhir yang paling relevan secara manajerial. Kedua, dua variabel segmentasi Market dan Product Type. Market dipilih karena anggaran kemungkinan disusun berbeda per region, sehingga pola penyimpangannya pun bisa berbeda. Product Type dipilih sebagai dimensi produk karena levelnya paling sesuai, lebih informatif dari Product Line yang hanya memiliki 2 kategori. Selain itu, variabel State digunakan khusus untuk visualisasi.

Persiapan

Load Library

library(tidyverse)
library(readxl)
library(car)
library(rstatix)
library(ggpubr)
library(scales)
library(knitr)
library(kableExtra)
library(DT)
library(e1071)
library(lubridate)
library(gridExtra)

Load Data

#Data Loading
data <- read_excel("D:/SEMESTER_4/SIM/1. Tugas SIM 2025B - Coffee Chain Datasets.xlsx")

datatable(
  data,
  options = list(pageLength = 5, scrollX = TRUE),
  caption = "Coffee Chain Dataset"
)

#EDA

str(data)
## tibble [4,248 × 20] (S3: tbl_df/tbl/data.frame)
##  $ Area Code     : num [1:4248] 719 970 970 303 303 720 970 719 970 719 ...
##  $ Date          : POSIXct[1:4248], format: "2012-01-01" "2012-01-01" ...
##  $ Market        : chr [1:4248] "Central" "Central" "Central" "Central" ...
##  $ Market Size   : chr [1:4248] "Major Market" "Major Market" "Major Market" "Major Market" ...
##  $ Product       : chr [1:4248] "Amaretto" "Colombian" "Decaf Irish Cream" "Green Tea" ...
##  $ Product Line  : chr [1:4248] "Beans" "Beans" "Beans" "Leaves" ...
##  $ Product Type  : chr [1:4248] "Coffee" "Coffee" "Coffee" "Tea" ...
##  $ State         : chr [1:4248] "Colorado" "Colorado" "Colorado" "Colorado" ...
##  $ Type          : chr [1:4248] "Regular" "Regular" "Decaf" "Regular" ...
##  $ Budget COGS   : num [1:4248] 90 80 100 30 60 80 140 50 50 40 ...
##  $ Budget Margin : num [1:4248] 130 110 140 50 90 130 160 80 70 70 ...
##  $ Budget Profit : num [1:4248] 100 80 110 30 70 80 110 20 40 20 ...
##  $ Budget Sales  : num [1:4248] 220 190 240 80 150 210 300 130 120 110 ...
##  $ COGS          : num [1:4248] 89 83 95 44 54 72 170 63 60 58 ...
##  $ Inventory     : num [1:4248] 777 623 821 623 456 ...
##  $ Margin        : num [1:4248] 130 107 139 56 80 108 171 87 80 72 ...
##  $ Marketing     : num [1:4248] 24 27 26 14 15 23 47 57 19 22 ...
##  $ Profit        : num [1:4248] 94 68 101 30 54 53 99 0 33 17 ...
##  $ Sales         : num [1:4248] 219 190 234 100 134 180 341 150 140 130 ...
##  $ Total Expenses: num [1:4248] 36 39 38 26 26 55 72 87 47 55 ...
# Cek Missing Value
colSums(is.na(data))
##      Area Code           Date         Market    Market Size        Product 
##              0              0              0              0              0 
##   Product Line   Product Type          State           Type    Budget COGS 
##              0              0              0              0              0 
##  Budget Margin  Budget Profit   Budget Sales           COGS      Inventory 
##              0              0              0              0              0 
##         Margin      Marketing         Profit          Sales Total Expenses 
##              0              0              0              0              0
# Cek Duplikat
sum(duplicated(data))
## [1] 0
data[duplicated(data), ]
## # A tibble: 0 × 20
## # ℹ 20 variables: Area Code <dbl>, Date <dttm>, Market <chr>,
## #   Market Size <chr>, Product <chr>, Product Line <chr>, Product Type <chr>,
## #   State <chr>, Type <chr>, Budget COGS <dbl>, Budget Margin <dbl>,
## #   Budget Profit <dbl>, Budget Sales <dbl>, COGS <dbl>, Inventory <dbl>,
## #   Margin <dbl>, Marketing <dbl>, Profit <dbl>, Sales <dbl>,
## #   Total Expenses <dbl>
df <- data %>%
  select(
    Sales, `Budget Sales`,
    Profit, `Budget Profit`,
    COGS, `Budget COGS`,
    Margin, `Budget Margin`,
    Market, `Product Type`,
    State, Date
  )

Rekayasa Variabel (Feature Engineering)

Variabel gap dihitung sebagai Gap = Aktual - Anggaran

  • Nilai positif realisasi melampaui anggaran
  • Nilai negatif realisasi di bawah anggaran
df_clean <- df %>%
  mutate(
    Profit_Gap = Profit - `Budget Profit`,
    Sales_Gap  = Sales  - `Budget Sales`,
    COGS_Gap   = COGS   - `Budget COGS`,
    Margin_Gap = Margin - `Budget Margin`,
    YearMonth  = floor_date(Date, "month")
  )
df_clean <- df_clean %>%
 rename(`Product_Type` = `Product Type`)
cat("Total observasi    :", nrow(df_clean), "\n")
## Total observasi    : 4248
cat("Periode            :", format(min(df_clean$Date), "%b %Y"),
    "–", format(max(df_clean$Date), "%b %Y"), "\n")
## Periode            : Jan 2012 – Des 2013
cat("Jumlah Market      :", n_distinct(df_clean$Market), "\n")
## Jumlah Market      : 4
cat("Jumlah Product Type:", n_distinct(df_clean$`Product Type`), "\n")
## Jumlah Product Type: 0

Statistik Deskriptif

df_clean %>%
  select(Sales, `Budget Sales`, Profit, `Budget Profit`,
         COGS, `Budget COGS`, Margin, `Budget Margin`) %>%
  summary() %>%
  kable(caption = "Statistik Deskriptif Variabel Finansial") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Statistik Deskriptif Variabel Finansial
Sales Budget Sales Profit Budget Profit COGS Budget COGS Margin Budget Margin
Min. : 17 Min. : 0.0 Min. :-638.0 Min. :-320.00 Min. : 0.00 Min. : 0.00 Min. :-302.00 Min. :-210.0
1st Qu.:100 1st Qu.: 80.0 1st Qu.: 17.0 1st Qu.: 20.00 1st Qu.: 43.00 1st Qu.: 30.00 1st Qu.: 52.75 1st Qu.: 50.0
Median :138 Median : 130.0 Median : 40.0 Median : 40.00 Median : 60.00 Median : 50.00 Median : 76.00 Median : 70.0
Mean :193 Mean : 175.6 Mean : 61.1 Mean : 60.91 Mean : 84.43 Mean : 74.83 Mean : 104.29 Mean : 100.8
3rd Qu.:230 3rd Qu.: 210.0 3rd Qu.: 92.0 3rd Qu.: 80.00 3rd Qu.:100.00 3rd Qu.: 90.00 3rd Qu.: 132.00 3rd Qu.: 130.0
Max. :912 Max. :1140.0 Max. : 778.0 Max. : 560.00 Max. :364.00 Max. :450.00 Max. : 613.00 Max. : 690.0

##Histogram Distribusi Gap

gap_vars <- c("Profit_Gap", "Sales_Gap", "COGS_Gap", "Margin_Gap")
df_clean %>%
  select(Profit_Gap, Sales_Gap, COGS_Gap, Margin_Gap) %>%
  pivot_longer(everything(), names_to = "Variabel", values_to = "Gap") %>%
  mutate(Variabel = dplyr::recode(Variabel,
    "Profit_Gap" = "Profit",
    "Sales_Gap"  = "Sales",
    "COGS_Gap"   = "COGS",
    "Margin_Gap" = "Margin"
  )) %>%
  ggplot(aes(x = Gap, fill = Variabel)) +
  geom_histogram(bins = 50, color = "white", alpha = 0.85) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "black", linewidth = 0.7) +
  facet_wrap(~Variabel, scales = "free", ncol = 2) +
  scale_fill_brewer(palette = "Set2") +
  labs(
    title    = "Distribusi Gap (Aktual − Anggaran) per Variabel Finansial",
    subtitle = "Garis putus-putus = titik nol (realisasi = anggaran)",
    x = "Gap", y = "Frekuensi"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "none", strip.text = element_text(face = "bold"))

data.frame(
  Variabel = gap_vars,
  Skewness = sapply(df_clean[gap_vars], skewness, na.rm = TRUE)
)
##              Variabel    Skewness
## Profit_Gap Profit_Gap -0.31958880
## Sales_Gap   Sales_Gap  0.07141022
## COGS_Gap     COGS_Gap  0.43617170
## Margin_Gap Margin_Gap -0.83144255

Interpretasi Tiga dari empat variabel gap memiliki skewness di bawah 0.5 (simetris). Margin_Gap paling condong (-0.831) namun masih di bawah ambang 1.

Cek Outlier

df_clean %>%
  select(all_of(gap_vars)) %>%
  pivot_longer(everything(), names_to = "Variabel", values_to = "Gap") %>%
  ggplot(aes(x = Variabel, y = Gap, fill = Variabel)) +
  geom_boxplot() +
  theme_minimal()

sapply(gap_vars, function(var) {
  x       <- df_clean[[var]]
  Q1      <- quantile(x, 0.25, na.rm = TRUE)
  Q3      <- quantile(x, 0.75, na.rm = TRUE)
  IQR_val <- IQR(x, na.rm = TRUE)
  sum(x < (Q1 - 1.5 * IQR_val) | x > (Q3 + 1.5 * IQR_val), na.rm = TRUE)
})
## Profit_Gap  Sales_Gap   COGS_Gap Margin_Gap 
##        517        323        366        382
# Outlier detail Profit_Gap
x       <- df_clean$Profit_Gap
Q1      <- quantile(x, .25)
Q3      <- quantile(x, .75)
IQR_val <- IQR(x)
x[x < (Q1 - 1.5 * IQR_val) | x > (Q3 + 1.5 * IQR_val)]
##   [1]  -57  -61 -108   53 -114  -51  -59  -76 -115   52 -122  -49  -52  -74 -111
##  [16]   62 -100  -54  -60  -51   47  -69 -124   59 -115  -53  -70   52  -87 -115
##  [31]   61   44 -130  -59  -78 -143   51   45 -119  -66  -81  -52   78  -54  -84
##  [46] -160   68 -128  -69  -78  -53   49  -74  -90 -152   72 -144  -66  -60  -55
##  [61]  -58  -83 -137   51   46 -150  -74  -91 -151 -196   53  -54  -52  -98  -58
##  [76]  -72   59  -70  -57  -66 -155  -54  -54  -58  -86   54 -223   94   69 -193
##  [91]  -55  -69  -51  -54  -83  -71  -94  -63   64  -56  -75   56   48  -60 -104
## [106]  -83   46  -53   99  -52  -79   65   57  -88   44   46   47   48   46   57
## [121]   44   56  115   44   80  113   46   44   49   46  212   73 -106  -98   73
## [136]  148   58   59   89   44   66  116  -62   68   53   44  166  120 -312   46
## [151]   54   54   59   59   47   46   47  108   69  101   50  175   84  -98 -111
## [166]  112  114   57   48   83   67   59  100  -64   61   51  150  111 -288   45
## [181]   47   60   58   45   44   51   45   53  105   80  100  183   79  -97 -119
## [196]   78  114   45   50   75   55   55  103  -60   71   57  165  110 -265   50
## [211]   51   52   47   46   62  116   51   80  116   54   49   45  189  167  -86
## [226]  130 -114   99   55   47   70   62   53   70  106  -59   72   61  165  111
## [241] -292   75   47   99   78   92  169  148  -91  107  -89   48   73   53   48
## [256]   97  -56   61   47  149  106 -274   59   69   44   47  103   48   76   92
## [271]   47   55   45  225  143  -97  143 -116   99   45   47   46   47   44   75
## [286]   76   55   48  101  -56   59   58  143  108 -259   68   65  -57   47   57
## [301]  116   57   80  102   44   67   48  218  226 -116 -127   61  141   67   45
## [316]   55   83   55  -67   60   97  166  102 -272   87   57   64   88  -57   44
## [331]   57   45  112   58   80  106  217  136 -190 -132   57  138   64   84   55
## [346]   57  104  -49   60  112  172  100 -298   55   57   64   71  -53   59   57
## [361]   47   45   51   46  116   51   90  121  -55   59  230   47 -173   47 -112
## [376]   92  143   49   79   99   55   46   91  -58   72  106  158  122 -358   51
## [391]   51   69   60  -53   57   52  -88  -79  122  111  142  -49  -53  -52  -49
## [406]  186  121 -152 -130  196  137  -49  -65   97  -70  119   67   51  106   94
## [421]  -50 -103  -74   61  202  141 -369   58   45   63  -57  -59   45  -75   59
## [436]   44   60   45   78   45   66   47  111   59   81  127   62   51  181  163
## [451] -112   58 -110  175  120   46   72   52   48   88   80   48   45   98  102
## [466]  -58   51  151  110 -225   75   59   62   61   49   69   67   51   98   51
## [481]  121   63  112  151   90   46   44   71   49  219  249 -130   47 -122  143
## [496]  152   44   90   47   87   44   89   52  109   73  111   52  -71  169  133
## [511] -285  118   61   48   80   49   46

Insight Outlier terlihat jelas di semua variabel, terutama Sales_Gap yang punya titik-titik ekstrem di atas 200 dan di bawah -400.

Uji Asumsi

QQ-Plot Semua Variabel Gap

par(mfrow = c(2, 2))
for (var in gap_vars) {
  qqnorm(df_clean[[var]],
         main = paste("QQ-Plot:", str_remove(var, "_Gap"), "Gap"),
         pch = 16, col = alpha("steelblue", 0.3), cex = 0.5)
  qqline(df_clean[[var]], col = "red", lwd = 2)
}

##Uji Normalitas Kolmogorov-Smirnov

ks_results <- purrr::map_dfr(gap_vars, function(var) {

  hasil <- ks.test(
    df_clean[[var]],
    "pnorm",
    mean = mean(df_clean[[var]], na.rm = TRUE),
    sd   = sd(df_clean[[var]], na.rm = TRUE)
  )

  tibble(
    Variabel  = str_remove(var, "_Gap"),
    n         = nrow(df_clean),
    D         = round(hasil$statistic, 4),
    p_value   = hasil$p.value,
    Skewness  = round(skewness(df_clean[[var]]), 3),
    Kurtosis  = round(kurtosis(df_clean[[var]]), 3),
    Keputusan = ifelse(
      hasil$p.value < 0.05,
      "Tidak normal",
      "Normal"
    )
  )

})

kable(
  ks_results,
  caption = "Kolmogorov-Smirnov Test — Semua Variabel Gap"
) %>%

kable_styling(
  bootstrap_options = c("striped","hover"),
  full_width = FALSE
) %>%

row_spec(
  which(ks_results$Keputusan=="Tidak normal"),
  background="#fff3cd"
) %>%

footnote(
 general = paste0(
   "Seluruh variabel tidak normal (p<0.05). ",
   "Namun skewness <1 untuk semua variabel dan n=4248 ",
   "mendukung robustness via ",
   "Central Limit Theorem."
 )
)
Kolmogorov-Smirnov Test — Semua Variabel Gap
Variabel n D p_value Skewness Kurtosis Keputusan
Profit 4248 0.1642 0 -0.320 15.442 Tidak normal
Sales 4248 0.1165 0 0.071 9.278 Tidak normal
COGS 4248 0.1317 0 0.436 7.916 Tidak normal
Margin 4248 0.1369 0 -0.831 9.931 Tidak normal
Note:
Seluruh variabel tidak normal (p<0.05). Namun skewness <1 untuk semua variabel dan n=4248 mendukung robustness via Central Limit Theorem.

Homogenitas Variansi — Levene’s Test

# Per Product Type
do.call(rbind, lapply(gap_vars, function(v) {
  test <- leveneTest(as.formula(paste(v, "~ Product_Type")), data = df_clean)
  data.frame(
    Variabel   = v,
    F_value    = round(test$`F value`[1], 4),
    p_value    = test$`Pr(>F)`[1],
    Kesimpulan = ifelse(test$`Pr(>F)`[1] < 0.05,
                        "Varians tidak homogen", "Varians homogen")
  )
})) %>%
  kable(caption = "Hasil Uji Levene (Product Type)") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Hasil Uji Levene (Product Type)
Variabel F_value p_value Kesimpulan
Profit_Gap 12.7635 0 Varians tidak homogen
Sales_Gap 36.4486 0 Varians tidak homogen
COGS_Gap 46.1002 0 Varians tidak homogen
Margin_Gap 49.5553 0 Varians tidak homogen
# Per Market
do.call(rbind, lapply(gap_vars, function(v) {
  test <- leveneTest(as.formula(paste(v, "~ Market")), data = df_clean)
  data.frame(
    Variabel   = v,
    F_value    = round(test$`F value`[1], 4),
    p_value    = test$`Pr(>F)`[1],
    Kesimpulan = ifelse(test$`Pr(>F)`[1] < 0.05,
                        "Varians tidak homogen", "Varians homogen")
  )
})) %>%
  kable(caption = "Hasil Uji Levene (Market)") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Hasil Uji Levene (Market)
Variabel F_value p_value Kesimpulan
Profit_Gap 25.8650 0 Varians tidak homogen
Sales_Gap 37.9369 0 Varians tidak homogen
COGS_Gap 54.6904 0 Varians tidak homogen
Margin_Gap 42.8392 0 Varians tidak homogen

Keputusan metodologi Meskipun Kolmogorov-Smirnov menolak normalitas untuk semua variabel gap (p < 0.001), nilai skewness seluruh variabel berada di bawah 1 (range: -0.831 hingga +0.436), mengindikasikan distribusi yang relatif simetris. Dikombinasikan dengan n = 4248, Central Limit Theorem menjamin robustness uji parametrik yang digunakan. Homogenitas variansi dilanggar untuk kedua faktor (p < 0.001) sehingga Welch’s ANOVA + Games-Howell digunakan.

Analisis Statistik

Analisis Gap Keseluruhan

gap_summary <- df_clean %>%
  summarise(
    Sales  = mean(Sales_Gap),
    Profit = mean(Profit_Gap),
    COGS   = mean(COGS_Gap),
    Margin = mean(Margin_Gap)
  ) %>%
  pivot_longer(everything(), names_to = "Metric", values_to = "Mean_Gap") %>%
  mutate(
    Status = case_when(
      Metric == "COGS" & Mean_Gap <= 0 ~ "Overperform",
      Metric == "COGS" & Mean_Gap >  0 ~ "Underperform",
      Metric != "COGS" & Mean_Gap >= 0 ~ "Overperform",
      TRUE                             ~ "Underperform"
    ),
    Metric = factor(Metric, levels = c("Sales", "Profit", "COGS", "Margin"))
  )
ggplot(gap_summary, aes(x = Metric, y = Mean_Gap, fill = Status)) +
  geom_col(width = 0.55) +
  geom_hline(yintercept = 0, linewidth = 0.4, linetype = "dashed", color = "grey40") +
  geom_text(aes(label = sprintf("%+.2f", Mean_Gap),
                vjust = ifelse(Mean_Gap >= 0, -0.4, 1.3)),
            size = 3.5, fontface = "bold", color = "grey20") +
  scale_fill_manual(values = c("Overperform" = "#1D9E75", "Underperform" = "#D85A30"),
                    name = NULL) +
  labs(
    title    = "Rata-rata Gap Aktual vs Budget (Keseluruhan)",
    subtitle = "Hijau = overperform | Merah = underperform\nCOGS: negatif = lebih hemat dari budget (overperform)",
    x = NULL, y = "Rata-rata Gap (Aktual − Budget)",
    caption  = "Sumber: Coffee Chain Dataset 2012–2013"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title         = element_text(face = "bold", size = 14),
    plot.subtitle      = element_text(size = 10, color = "grey40", margin = margin(b = 10)),
    legend.position    = "bottom",
    panel.grid.major.x = element_blank(),
    panel.grid.minor   = element_blank()
  )

Interpretasi Realisasi penjualan, profit, dan margin melampaui anggaran secara positif (overperform).Realisasi COGS (Cost of Goods Sold) melampaui anggraran yang berarti underperform. Untuk mengonfirmasi apakah penyimpangan tersebut signifikan secara statistik dan bukan sekadar fluktuasi acak, dilakukan uji one-sample t-test terhadap masing-masing variabel gap dengan hipotesis nol bahwa rata-rata gap sama dengan nol.

# One-sample t-test: apakah mean gap berbeda dari nol?
map_dfr(gap_vars, function(var) {
  res <- t.test(df_clean[[var]], mu = 0)
  tibble(
    Variabel = str_remove(var, "_Gap"),
    N        = nrow(df_clean),
    Mean_Gap = round(res$estimate, 3),
    CI_Low   = round(res$conf.int[1], 3),
    CI_High  = round(res$conf.int[2], 3),
    t_stat   = round(res$statistic, 3),
    p_value  = res$p.value,
    Sig      = case_when(
      res$p.value < 0.001 ~ "***",
      res$p.value < 0.01  ~ "**",
      res$p.value < 0.05  ~ "*",
     TRUE                ~ "ns"
    )
  )
}) %>%
  kable(caption = "Paired T-test: Seluruh Data (H₀: mean gap = 0)",
        col.names = c("Variabel", "N", "Mean Gap", "CI Low", "CI High", "t", "p-value", "Sig")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
  footnote(general = "*** p<0.001 | ** p<0.01 | * p<0.05 | ns tidak signifikan")
Paired T-test: Seluruh Data (H₀: mean gap = 0)
Variabel N Mean Gap CI Low CI High t p-value Sig
Profit 4248 0.184 -0.981 1.350 0.310 0.7564943 ns
Sales 4248 17.338 16.006 18.670 25.518 0.0000000 ***
COGS 4248 9.603 8.953 10.252 28.980 0.0000000 ***
Margin 4248 3.474 2.712 4.236 8.940 0.0000000 ***
Note:
*** p<0.001 | ** p<0.01 | * p<0.05 | ns tidak signifikan

Temuan 1 Sales, COGS, dan Margin menunjukkan hasil signifikan. Sedangkan, Profit tidak signifikan.Secara keseluruhan, hasil uji t menunjukkan kinerja perusahaan mengalami overperformance pada sisi penjualan, tetapi tidak menghasilkan overperformance pada profit. - Sales meningkat signifikan di atas anggaran, menandakan target penjualan terlampaui. - COGS juga meningkat signifikan, menunjukkan kenaikan penjualan diiringi pembengkakan biaya. - Margin memang naik signifikan, tetapi peningkatannya relatif kecil dibanding kenaikan sales. - Profit tidak berbeda signifikan dari anggaran, yang berarti perusahaan pada akhirnya hanya mencapai target profit, bukan melampauinya. Inti temuan: pertumbuhan revenue tidak dikonversi secara efisien menjadi pertumbuhan profit, karena tambahan penjualan terserap oleh kenaikan biaya. Dengan demikian, masalah utama bukan pada kemampuan menghasilkan sales, melainkan pada efisiensi biaya dan konversi sales menjadi profitabilitas.

Segmen dengan Gap Paling Besar

Breakdown per Market

#Profit Gap
df_clean %>%
  group_by(Market) %>%
  summarise(Mean_Gap = mean(Profit_Gap, na.rm = TRUE), .groups = "drop") %>%
  mutate(Status = ifelse(Mean_Gap >= 0, "Overperform", "Underperform")) %>%
  ggplot(aes(x = Market, y = Mean_Gap, fill = Status)) +
  geom_col(width = 0.65) +
  geom_hline(yintercept = 0, linetype = "dashed") +
  geom_text(aes(label = sprintf("%+.2f", Mean_Gap),
                vjust = ifelse(Mean_Gap >= 0, -0.4, 1.3)),
            size = 4, fontface = "bold") +
  scale_fill_manual(values = c("Overperform" = "darkgreen", "Underperform" = "red"),
                    name = NULL) +
  labs(title = "Rata-rata Profit Gap per Market",
       subtitle = "Positif = overperform | Negatif = underperform",
       x = NULL, y = "Profit Gap (Aktual − Budget)") +
  theme_minimal() +
  theme(legend.position = "bottom", axis.text.x = element_text(angle = 30, hjust = 1))

Interpretasi Tiga market berkisar di sekitar nol (±1 atau kurang), sementara South menjadi satu-satunya yang benar-benar menyimpang jauh ke bawah. Ini mengindikasikan masalah South bersifat lokal dan spesifik, bukan tren perusahaan secara keseluruhan.Temuan ini perlu dikonfirmasi dengan uji t-test per market untuk memastikan apakah gap masing-masing signifikan secara statistik, mengingat secara visual East dan South terlihat paling menyimpang dari nol.

p2_market <- df_clean %>%
  group_by(Market) %>%
  summarise(
    N        = n(),
    Mean_Gap = mean(Profit_Gap),
    SD       = sd(Profit_Gap),
    t_stat   = t.test(Profit_Gap, mu = 0)$statistic,
    p_value  = t.test(Profit_Gap, mu = 0)$p.value,
    CI_Low   = t.test(Profit_Gap, mu = 0)$conf.int[1],
    CI_High  = t.test(Profit_Gap, mu = 0)$conf.int[2],
    Sig      = case_when(
      p_value < 0.001 ~ "***",
      p_value < 0.01  ~ "**",
      p_value < 0.05  ~ "*",
      TRUE            ~ "ns"
    )
  ) %>%
  arrange(Mean_Gap)

p2_market %>%
  select(-CI_Low, -CI_High) %>%
  kable(digits = 3, caption = "Paired T-test Profit Gap per Market",
        col.names = c("Market", "N", "Mean Gap", "SD", "t", "p-value", "Sig")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
  row_spec(which(p2_market$Sig != "ns"), bold = TRUE) %>%
  footnote(general = "*** p<0.001 | ** p<0.01 | * p<0.05 | ns tidak signifikan")
Paired T-test Profit Gap per Market
Market N Mean Gap SD t p-value Sig
South 672 -3.812 22.909 -4.314 0.000 ***
West 1344 -0.271 47.923 -0.207 0.836 ns
Central 1344 0.946 29.627 1.171 0.242 ns
East 888 2.744 44.301 1.846 0.065 ns
Note:
*** p<0.001 | ** p<0.01 | * p<0.05 | ns tidak signifikan

Breakdown per Product Type

#Profit Gap
df_clean %>%
  group_by(Product_Type) %>%
  summarise(Mean_Gap = mean(Profit_Gap, na.rm = TRUE), .groups = "drop") %>%
  mutate(Status = ifelse(Mean_Gap >= 0, "Overperform", "Underperform")) %>%
  ggplot(aes(x = Product_Type, y = Mean_Gap, fill = Status)) +
  geom_col(width = 0.65) +
  geom_hline(yintercept = 0, linetype = "dashed") +
  geom_text(aes(label = sprintf("%+.2f", Mean_Gap),
                vjust = ifelse(Mean_Gap >= 0, -0.4, 1.3)),
            size = 4, fontface = "bold") +
  scale_fill_manual(values = c("Overperform" = "darkgreen", "Underperform" = "red"),
                    name = NULL) +
  labs(title = "Rata-rata Profit Gap per Jenis Produk",
       subtitle = "Positif = overperform | Negatif = underperform",
       x = NULL, y = "Profit Gap (Aktual − Budget)") +
  theme_minimal() +
  theme(legend.position = "bottom", axis.text.x = element_text(angle = 30, hjust = 1))

Interpretasi produk coffee-based (Coffee, Espresso) semuanya di bawah anggaran, sementara produk tea-based (Tea, Herbal Tea) semuanya di atas anggaran.

p2_pt <- df_clean %>%
  group_by(Product_Type) %>%
  summarise(
    N        = n(),
    Mean_Gap = mean(Profit_Gap),
    SD       = sd(Profit_Gap),
    t_stat   = t.test(Profit_Gap, mu = 0)$statistic,
    p_value  = t.test(Profit_Gap, mu = 0)$p.value,
    CI_Low   = t.test(Profit_Gap, mu = 0)$conf.int[1],
    CI_High  = t.test(Profit_Gap, mu = 0)$conf.int[2],
    Sig      = case_when(
      p_value < 0.001 ~ "***",
      p_value < 0.01  ~ "**",
      p_value < 0.05  ~ "*",
      TRUE            ~ "ns"
    )
  ) %>%
  arrange(Mean_Gap)

p2_pt %>%
  select(-CI_Low, -CI_High) %>%
  kable(digits = 3, caption = "Paired T-test Profit Gap per Product Type",
        col.names = c("Product Type", "N", "Mean Gap", "SD", "t", "p-value", "Sig")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
  row_spec(which(p2_pt$Sig != "ns"), bold = TRUE) %>%
  footnote(general = "Coffee & Espresso: di bawah target | Tea & Herbal Tea: di atas target")
Paired T-test Profit Gap per Product Type
Product Type N Mean Gap SD t p-value Sig
Coffee 1056 -8.709 38.238 -7.401 0.000 ***
Espresso 1176 -2.789 30.302 -3.156 0.002 **
Herbal Tea 1056 4.294 33.202 4.202 0.000 ***
Tea 960 9.090 50.041 5.628 0.000 ***
Note:
Coffee & Espresso: di bawah target | Tea & Herbal Tea: di atas target

Temuan 2 Pola berlawanan Coffee (-8.71) dan Tea (+9.09) menjelaskan mengapa overall tidak signifikan, keduanya saling mengkompensasi. South adalah satu-satunya market yang aktual-nya signifikan di bawah budget.Selain itu,SD South paling kecil (22,91) dibanding market lain, artinya gap negatif ini terjadi secara konsisten di hampir setiap transaksi, bukan ditarik oleh beberapa outlier ekstrem.

Identifikasi Signifikansi Perbedaan Gap Antar Segmen

#Welch's Anova & Post-hoc
anova_market <- oneway.test(Profit_Gap ~ Market,       data = df_clean, var.equal = FALSE)
anova_pt     <- oneway.test(Profit_Gap ~ Product_Type, data = df_clean, var.equal = FALSE)

tibble(
  Faktor  = c("Market", "Product_Type"),
  F_stat  = c(round(anova_market$statistic, 3), round(anova_pt$statistic, 3)),
  df1     = c(anova_market$parameter[1], anova_pt$parameter[1]),
  df2     = c(round(anova_market$parameter[2], 1), round(anova_pt$parameter[2], 1)),
  p_value = c(anova_market$p.value, anova_pt$p.value),
  Sig     = c(ifelse(anova_market$p.value < 0.001, "***", "**"), "***")
) %>%
  kable(digits = 4, caption = "Welch's One-Way ANOVA: Profit Gap ~ Faktor Segmentasi") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
  footnote(general = "Welch's ANOVA digunakan karena asumsi homogenitas variansi dilanggar (Levene, p<0.001)")
Welch’s One-Way ANOVA: Profit Gap ~ Faktor Segmentasi
Faktor F_stat df1 df2 p_value Sig
Market 7.329 3 2130.3 1e-04 ***
Product_Type 37.493 3 2265.8 0e+00 ***
Note:
Welch’s ANOVA digunakan karena asumsi homogenitas variansi dilanggar (Levene, p<0.001)

Visualisasi Boxplot untuk Memvisualisasikan Perbedaan Variansi

p_market <- ggplot(df_clean, aes(x = reorder(Market, Profit_Gap),
                                 y = Profit_Gap, fill = Market)) +
  geom_boxplot(outlier.alpha = 0.2, alpha = 0.8) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "black") +
  scale_fill_brewer(palette = "Set2") +
  labs(title = "Distribusi Profit Gap per Market", x = NULL, y = "Profit Gap") +
  theme_minimal(base_size = 11) +
  theme(legend.position = "none")

p_pt <- ggplot(df_clean, aes(x = reorder(Product_Type, Profit_Gap),
                             y = Profit_Gap, fill = Product_Type)) +
  geom_boxplot(outlier.alpha = 0.2, alpha = 0.8) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "black") +
  scale_fill_brewer(palette = "Set1") +
  labs(title = "Distribusi Profit Gap per Product Type", x = NULL, y = "Profit Gap") +
  theme_minimal(base_size = 11) +
  theme(legend.position = "none")

grid.arrange(p_market, p_pt, ncol = 2)

Post-hoc: Games-Howell

games_howell_test(df_clean, Profit_Gap ~ Market) %>%
  kable(digits = 3, caption = "Post-hoc Games-Howell: Profit Gap ~ Market") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
  row_spec(which(games_howell_test(df_clean, Profit_Gap ~ Market)$p.adj < 0.05),
           bold = TRUE, background = "#ffebee")
Post-hoc Games-Howell: Profit Gap ~ Market
.y. group1 group2 estimate conf.low conf.high p.adj p.adj.signif
Profit_Gap Central East 1.798 -2.554 6.150 0.712 ns
Profit_Gap Central South -4.759 -7.839 -1.679 0.000 ***
Profit_Gap Central West -1.217 -5.168 2.734 0.858 ns
Profit_Gap East South -6.557 -11.005 -2.108 0.001 ***
Profit_Gap East West -3.015 -8.105 2.075 0.424 ns
Profit_Gap South West 3.542 -0.515 7.599 0.112 ns
games_howell_test(df_clean, Profit_Gap ~ Product_Type) %>%
  kable(digits = 3, caption = "Post-hoc Games-Howell: Profit Gap ~ Product Type") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
  row_spec(which(games_howell_test(df_clean, Profit_Gap ~ Product_Type)$p.adj < 0.05),
           bold = TRUE, background = "#ffebee")
Post-hoc Games-Howell: Profit Gap ~ Product Type
.y. group1 group2 estimate conf.low conf.high p.adj p.adj.signif
Profit_Gap Coffee Espresso 5.920 2.137 9.704 0.000 ***
Profit_Gap Coffee Herbal Tea 13.003 8.996 17.010 0.000 ****
Profit_Gap Coffee Tea 17.799 12.660 22.937 0.000 ****
Profit_Gap Espresso Herbal Tea 7.083 3.610 10.556 0.000 ****
Profit_Gap Espresso Tea 11.879 7.144 16.614 0.000 ****
Profit_Gap Herbal Tea Tea 4.796 -0.119 9.711 0.059 ns

Investigasi Lanjutan Terhadap Pola Kontradikstif

Line Chart Temporal: Apakah Coffee Selalu Negatif?

df_clean %>%
  filter(Product_Type == "Coffee") %>%
  group_by(YearMonth) %>%
  summarise(Mean_Gap = mean(Profit_Gap, na.rm = TRUE), .groups = "drop") %>%
  ggplot(aes(x = YearMonth, y = Mean_Gap)) +
  annotate("rect",
           xmin = as.POSIXct("2012-01-01"), xmax = as.POSIXct("2013-12-31"),
           ymin = -Inf, ymax = 0, alpha = 0.05) +
  geom_hline(yintercept = 0, linetype = "dashed") +
  geom_line(linewidth = 1.1) +
  geom_point(size = 2) +
  scale_x_datetime(date_labels = "%b %Y", date_breaks = "3 months") +
  labs(title    = "Tren Bulanan Profit Gap Coffee",
       subtitle = "Garis di atas nol = melampaui budget; di bawah nol = di bawah target",
       y = "Rata-rata Profit Gap", x = NULL) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Interpretasi Coffee dominan berada di zona negatif sepanjang 2012, sempat membaik ke atas nol di awal 2013, namun kembali turun dan berfluktuasi. Rata-rata keseluruhan tetap signifikan negatif (-8.71, p < 0.001) meskipun tidak setiap bulan di bawah target.

Dekomposisi Bar: Mengapa Coffee dan Tea Berbeda Arah?

df_clean %>%
  filter(Product_Type %in% c("Coffee", "Tea")) %>%
  group_by(Product_Type) %>%
  summarise(
    Sales_Gap  = mean(Sales_Gap),
    COGS_Gap   = mean(COGS_Gap),
    Margin_Gap = mean(Margin_Gap),
    Profit_Gap = mean(Profit_Gap)
  ) %>%
  kable(digits = 2, caption = "Perbandingan Semua Gap: Coffee vs Tea") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Perbandingan Semua Gap: Coffee vs Tea
Product_Type Sales_Gap COGS_Gap Margin_Gap Profit_Gap
Coffee -11.17 -5.86 -9.63 -8.71
Tea 40.81 21.90 14.41 9.09
df_clean %>%
  filter(Product_Type %in% c("Coffee", "Tea")) %>%
  group_by(Product_Type) %>%
  summarise(
    Sales  = mean(Sales_Gap),
    COGS   = mean(COGS_Gap),
    Margin = mean(Margin_Gap),
    Profit = mean(Profit_Gap)
  ) %>%
  pivot_longer(-Product_Type, names_to = "Komponen", values_to = "Gap") %>%
  mutate(Komponen = factor(Komponen, levels = c("Sales", "COGS", "Margin", "Profit"))) %>%
  ggplot(aes(x = Komponen, y = Gap, fill = Product_Type)) +
  geom_col(position = "dodge", alpha = 0.88, width = 0.65, color = "white") +
  geom_hline(yintercept = 0, linewidth = 0.9, color = "gray20") +
  geom_text(aes(label = paste0(ifelse(Gap >= 0, "+", ""), round(Gap, 1)),
                vjust = ifelse(Gap >= 0, -0.5, 1.4)),
            position = position_dodge(0.65), size = 4, fontface = "bold") +
  scale_fill_manual(values = c("Coffee" = "#6D4C41", "Tea" = "#388E3C")) +
  labs(
    title    = "Dekomposisi Gap: Coffee vs Tea",
    subtitle = "Coffee: Sales sudah di bawah target → Profit makin turun\nTea: Sales jauh melampaui target (+17) tapi COGS ikut membengkak → Profit hanya +9",
    x = "Komponen Finansial",
    y = "Rata-rata Gap (Aktual − Anggaran)",
    fill = "Product Type"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "top", panel.grid.major.x = element_blank())

Interpretasi Coffee punya masalah di sisi permintaan, volume penjualannya tidak mencapai target sehingga seluruh rantai finansial ikut tertekan. Tea justru punya masalah di sisi efisiensi, permintaannya kuat, tapi struktur biayanya tidak sesuai sehingga pertumbuhan sales tidak berhasil dikonversi menjadi profit secara proporsional. Keduanya perlu intervensi yang berbeda.

Ranking Profit gap per state dengan konteks region

state_gap <- df_clean %>%
  group_by(State, Market) %>%
  summarise(Mean_Gap = mean(Profit_Gap), .groups = "drop") %>%
  mutate(Warna = ifelse(Mean_Gap >= 0,
                        "Di atas budget","Di bawah budget"))

ggplot(state_gap,
       aes(x = Mean_Gap, y = reorder(State, Mean_Gap))) +
  geom_vline(xintercept = 0, linetype = "dashed",
             color = "gray40", linewidth = 0.9) +
  geom_segment(
    aes(x = 0, xend = Mean_Gap,
        y = reorder(State, Mean_Gap),
        yend = reorder(State, Mean_Gap),
        color = Market),
    linewidth = 1.3, alpha = 0.7
  ) +
  geom_point(aes(color = Market, shape = Warna), size = 4) +
  geom_text(
    aes(label = paste0(ifelse(Mean_Gap >= 0, "+", ""),
                       round(Mean_Gap, 1)),
        color = Market,
        hjust = ifelse(state_gap$Mean_Gap >= 0, -0.3, 1.3)),
    size = 3.3, fontface = "bold"
  ) +
  scale_color_brewer(palette = "Set2", name = "Market") +
  scale_shape_manual(
    values = c("Di atas budget" = 16, "Di bawah budget" = 17),
    name   = NULL
  ) +
  labs(
    title    = "Profit Gap per State, Dikelompokkan per Market Region",
    subtitle = "▲ = di bawah budget | ● = di atas budget | Warna = Market region",
    x = "Rata-rata Profit Gap", y = NULL
  ) +
  theme_minimal(base_size = 11) +
  theme(panel.grid.major.y = element_line(color = "gray92"),
        panel.grid.major.x = element_blank(),
        legend.position    = "right")

Insight Iowa (+17.9) dan Massachusetts (+12.8) konsisten terbaik. New Mexico (–11.1) dan Utah (–9.5) paling jauh di bawah target. Tidak ada satu Market region yang seluruh state-nya seragam, variasi lebih banyak terjadi di level state daripada level region, mengindikasikan faktor lokal yang lebih dominan.

Ringkasan Temuan

tibble(
  No = 1:4,
  Pertanyaan = c("Gap keseluruhan",
                 "Segmen dengan gap terbesar",
                 "Perbedaan gap antar segmen",
                 "Identifikasi Lanjutan"),
  Temuan = c(
    "Profit overall tidak signifikan (p=0.756). Sales (+17.34***) dan COGS (+9.60***) sama-sama melampaui anggaran — surplus penjualan habis diserap kenaikan biaya ",
    "South satu-satunya market signifikan negatif (–3.81***). Coffee gap negatif terbesar (–8.71***), Tea positif terbesar (+9.09***)",
    "Welch's ANOVA: Product Type lebih kuat (F=42.97***) dari Market (F=3.92**). Post-hoc: Coffee berbeda signifikan dari semua product type lain ",
    "Coffee dominan berada di zona negatif sepanjang 2012, sempat membaik ke atas nol di awal 2013, namun kembali turun dan berfluktuasi. Rata-rata keseluruhan tetap signifikan negatif (–8.71, p<0.001) meskipun tidak setiap bulan di bawah target."
  )
) %>%
  kable(caption = "Ringkasan Temuan Analisis Budget vs Aktual") %>%
  kable_styling(bootstrap_options = c("striped","hover"),
                full_width = TRUE) %>%
  column_spec(3, width = "55%")
Ringkasan Temuan Analisis Budget vs Aktual
No Pertanyaan Temuan
1 Gap keseluruhan Profit overall tidak signifikan (p=0.756). Sales (+17.34) dan COGS (+9.60) sama-sama melampaui anggaran — surplus penjualan habis diserap kenaikan biaya
2 Segmen dengan gap terbesar South satu-satunya market signifikan negatif (–3.81). Coffee gap negatif terbesar (–8.71), Tea positif terbesar (+9.09***)
3 Perbedaan gap antar segmen Welch’s ANOVA: Product Type lebih kuat (F=42.97*) dari Market (F=3.92). Post-hoc: Coffee berbeda signifikan dari semua product type lain
4 Identifikasi Lanjutan Coffee dominan berada di zona negatif sepanjang 2012, sempat membaik ke atas nol di awal 2013, namun kembali turun dan berfluktuasi. Rata-rata keseluruhan tetap signifikan negatif (–8.71, p<0.001) meskipun tidak setiap bulan di bawah target.

Rekomendasi Bisnis

1. Audit cost structure untuk menekan pembengkakan COGS Setiap kenaikan penjualan diikuti kenaikan COGS yang hampir proporsional sehingga tidak menghasilkan kenaikan profit bersih. Perlu investigasi inefisiensi rantai pasokan atau kenaikan harga bahan baku yang tidak diantisipasi.Jika kenaikan COGS disebabkan harga bahan baku, perlu negosiasi kontrak jangka panjang dengan supplier untuk mengunci harga. Jika disebabkan biaya operasional produksi, perlu evaluasi efisiensi proses.

2. Adopsi metodologi perencanaan Tea untuk segmen Coffee Tea konsisten melampaui anggaran profit, artinya metodologi perencanaan untuk Tea lebih konservatif dan realistis.

3. Coffee Underperform Coffee bukan hanya rata-rata di bawah target, ia juga berbeda signifikan dari semua product type lain (post-hoc Games-Howell), dan polanya fluktuatif sepanjang dua tahun. Sempat membaik di awal 2013 tapi tidak bertahan. Ini punya dua kemungkinan penyebab yang butuh investigasi berbeda: - Jika penyebabnya adalah anggaran yang terlalu optimistis,perlu revisi metodologi penganggaran Coffee menjadi lebih konservatif berbasis data historis 2 tahun terakhir. - Jika penyebabnya adalah performa aktual yang memang menurun perlu evaluasi strategi harga, portofolio produk Coffee, atau tekanan kompetitif di segmen ini

Batasan

  • Data mencakup 2 tahun (2012-2013), tren jangka panjang tidak dapat disimpulkan.
  • Distribusi gap tidak normal (Kolmogorov-Smirnov p < 0.001), namun skewness <1 dan n=4248 menjamin robustness via CLT.
  • Variansi tidak homogen antar segmen, ditangani dengan Welch’s ANOVA dan Games-Howell.
  • Analisis bersifat observasional, tidak dapat menyimpulkan kausalitas.

Analisis dilakukan menggunakan R 4.4.1 dengan paket tidyverse, rstatix, ggpubr, dan gridExtra dari data Coffee Chain Dataset yang diunggah di SPADA UNS.