1. Pendahuluan

1.1 Latar Belakang

Dalam dunia bisnis, anggaran (budget) digunakan sebagai acuan dalam perencanaan keuangan perusahaan. Budget Sales dan Budget Profit ditetapkan di awal periode sebagai target kinerja yang harus dicapai oleh perusahaan. Namun, dalam praktiknya, realisasi yang terjadi sering kali tidak sama dengan yang telah direncanakan. Hal ini bisa dipengaruhi oleh berbagai faktor, seperti perubahan permintaan pasar, biaya operasional yang tidak stabil, serta kondisi pasar yang dinamis.

Dataset Coffee Chain berisi data penjualan dari jaringan kedai kopi di beberapa wilayah di Amerika Serikat (Market: Central, East, South, West) selama tahun 2012–2013. Dataset ini memuat informasi mengenai nilai budget dan aktual untuk beberapa variabel seperti Sales, Profit, Margin, dan COGS. Dengan adanya data tersebut, kita dapat melakukan analisis untuk melihat selisih (Gap) antara nilai aktual dan budget yang telah ditentukan.

Analisis dilakukan dengan menggunakan statistik deskriptif, uji-t berpasangan, dan regresi linear sederhana. Statistik deskriptif digunakan untuk memberikan gambaran umum data, sedangkan uji-t berpasangan digunakan untuk mengetahui apakah terdapat perbedaan yang signifikan antara nilai budget dan aktual. Selain itu, regresi linear digunakan untuk melihat hubungan antara budget dan realisasi aktual, sehingga dapat diketahui seberapa baik budget dapat memprediksi hasil yang sebenarnya. Regresi awal menggunakan pendekatan Ordinary Least Squares (OLS), kemudian dilakukan uji asumsi klasik. Jika ditemukan heteroskedastisitas, maka digunakan pendekatan robust standard error untuk memastikan hasil uji signifikansi tetap valid.

1.2 Tujuan

Tujuan dari analisis ini adalah:
1. Menjelaskan distribusi serta karakteristik variabel Budget dan Aktual (Sales dan Profit) pada data Coffee Chain.
2. Mengidentifikasi dan menampilkan selisih (gap) antara nilai aktual dan budget pada penjualan serta profit.
3. Menganalisis apakah terdapat hubungan linear yang signifikan antara nilai budget dan aktual dengan menggunakan regresi linear sederhana, serta melakukan penyesuaian inferensi menggunakan pendekatan robust apabila ditemukan pelanggaran asumsi klasik (heteroskedastisitas).
4. Memberikan interpretasi hasil analisis dalam konteks bisnis serta menyusun rekomendasi yang dapat digunakan oleh manajemen.


2. Deskripsi Data

2.1 Data

Data yang digunakan adalah Coffee Chain Datasets yang terdiri dari 4.248 observasi dan 20 variabel. Data mencakup periode Januari 2012 hingga Desember 2013 dengan cakupan geografis 4 wilayah pasar di Amerika Serikat.

2.2 Deskripsi Variabel

variabel <- c(
  "Area.Code","Date","Market","Market.Size","Product",
  "Product.Line","Product.Type","State","Type",
  "Budget.COGS","Budget.Margin","Budget.Profit","Budget.Sales",
  "COGS","Inventory","Margin","Marketing","Profit","Sales","Total.Expenses"
)

tipe <- c(
  "Numerik","Tanggal","Kategorik","Kategorik","Kategorik",
  "Kategorik","Kategorik","Kategorik","Kategorik",
  "Numerik","Numerik","Numerik","Numerik",
  "Numerik","Numerik","Numerik","Numerik","Numerik","Numerik","Numerik"
)

deskripsi <- c(
  "Kode area telepon lokasi penjualan",
  "Tanggal pencatatan transaksi",
  "Wilayah pasar (Central, East, South, West)",
  "Ukuran pasar (Major/Small Market)",
  "Nama produk",
  "Lini produk (Beans/Leaves)",
  "Jenis produk (Coffee, Tea, dll)",
  "Nama negara bagian",
  "Tipe produk (Decaf/Regular)",
  "Anggaran biaya pokok penjualan",
  "Anggaran margin",
  "Anggaran laba bersih",
  "Anggaran penjualan",
  "Realisasi biaya pokok penjualan",
  "Nilai inventaris",
  "Realisasi margin",
  "Biaya pemasaran",
  "Realisasi laba bersih",
  "Realisasi penjualan",
  "Total biaya operasional"
)

tabel_variabel <- data.frame(
  No = 1:20,
  Variabel = variabel,
  Tipe = tipe,
  Deskripsi = deskripsi
)

knitr::kable(tabel_variabel, caption = "Deskripsi Variabel Dataset Coffee Chain")
Deskripsi Variabel Dataset Coffee Chain
No Variabel Tipe Deskripsi
1 Area.Code Numerik Kode area telepon lokasi penjualan
2 Date Tanggal Tanggal pencatatan transaksi
3 Market Kategorik Wilayah pasar (Central, East, South, West)
4 Market.Size Kategorik Ukuran pasar (Major/Small Market)
5 Product Kategorik Nama produk
6 Product.Line Kategorik Lini produk (Beans/Leaves)
7 Product.Type Kategorik Jenis produk (Coffee, Tea, dll)
8 State Kategorik Nama negara bagian
9 Type Kategorik Tipe produk (Decaf/Regular)
10 Budget.COGS Numerik Anggaran biaya pokok penjualan
11 Budget.Margin Numerik Anggaran margin
12 Budget.Profit Numerik Anggaran laba bersih
13 Budget.Sales Numerik Anggaran penjualan
14 COGS Numerik Realisasi biaya pokok penjualan
15 Inventory Numerik Nilai inventaris
16 Margin Numerik Realisasi margin
17 Marketing Numerik Biaya pemasaran
18 Profit Numerik Realisasi laba bersih
19 Sales Numerik Realisasi penjualan
20 Total.Expenses Numerik Total biaya operasional

Variabel utama analisis: Budget.Sales, Sales, Budget.Profit, Profit

2.3 Import dan Persiapan Data

# Library yang digunakan
library(readxl)      # Membaca file Excel
library(dplyr)       # Manipulasi data
## 
## 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(ggplot2)     # Visualisasi data
library(tidyr)       # Transformasi data (pivot)
## Warning: package 'tidyr' was built under R version 4.5.1
library(scales)      # Format angka pada plot
library(knitr)       # Tabel yang rapi di HTML
library(lmtest)      # Uji asumsi
## Warning: package 'lmtest' was built under R version 4.5.1
## Loading required package: zoo
## 
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
library(sandwich)
## Warning: package 'sandwich' was built under R version 4.5.3
# Load dataset
df <- read_excel("D:/Semester 4/Sistem Informasi Manajemen/1. Tugas SIM 2025B - Coffee Chain Datasets.xlsx", sheet = "data")

# Mengubah nama kolom (mengganti spasi dengan titik)
colnames(df) <- make.names(colnames(df))

# Dimensi dataset
cat("Dimensi dataset:", nrow(df), "baris x", ncol(df), "kolom\n")
## Dimensi dataset: 4248 baris x 20 kolom
# Data
df
## # A tibble: 4,248 × 20
##    Area.Code Date                Market  Market.Size  Product       Product.Line
##        <dbl> <dttm>              <chr>   <chr>        <chr>         <chr>       
##  1       719 2012-01-01 00:00:00 Central Major Market Amaretto      Beans       
##  2       970 2012-01-01 00:00:00 Central Major Market Colombian     Beans       
##  3       970 2012-01-01 00:00:00 Central Major Market Decaf Irish … Beans       
##  4       303 2012-01-01 00:00:00 Central Major Market Green Tea     Leaves      
##  5       303 2012-01-01 00:00:00 Central Major Market Caffe Mocha   Beans       
##  6       720 2012-01-01 00:00:00 Central Major Market Decaf Espres… Beans       
##  7       970 2012-01-01 00:00:00 Central Major Market Chamomile     Leaves      
##  8       719 2012-01-01 00:00:00 Central Major Market Lemon         Leaves      
##  9       970 2012-01-01 00:00:00 Central Major Market Mint          Leaves      
## 10       719 2012-01-01 00:00:00 Central Major Market Darjeeling    Leaves      
## # ℹ 4,238 more rows
## # ℹ 14 more variables: 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>
# 10 baris pertama data (variabel utama)
kable(head(df[, c("Date","Market","Product","Budget.Sales","Sales","Budget.Profit","Profit")], 10),
      caption = "10 Baris Pertama Dataset (Variabel Utama)")
10 Baris Pertama Dataset (Variabel Utama)
Date Market Product Budget.Sales Sales Budget.Profit Profit
2012-01-01 Central Amaretto 220 219 100 94
2012-01-01 Central Colombian 190 190 80 68
2012-01-01 Central Decaf Irish Cream 240 234 110 101
2012-01-01 Central Green Tea 80 100 30 30
2012-01-01 Central Caffe Mocha 150 134 70 54
2012-01-01 Central Decaf Espresso 210 180 80 53
2012-01-01 Central Chamomile 300 341 110 99
2012-01-01 Central Lemon 130 150 20 0
2012-01-01 Central Mint 120 140 40 33
2012-01-01 Central Darjeeling 110 130 20 17
# cek struktur data
str(df)
## 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(df))
##      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
# Membuat variabel gap (selisih aktual - budget)
# Gap positif = aktual melampaui budget
# Gap negatif = aktual di bawah budget

df <- df %>%
  mutate(
    Gap.Sales  = Sales - Budget.Sales,           # Selisih penjualan
    Gap.Profit = Profit - Budget.Profit,         # Selisih profit
    Pct.Gap.Sales  = (Gap.Sales / abs(Budget.Sales)) * 100,   # Gap dalam %
    Pct.Gap.Profit = (Gap.Profit / abs(Budget.Profit)) * 100, # Gap dalam %
    Status.Sales  = ifelse(Gap.Sales >= 0, "Melampaui Budget", "Di Bawah Budget"),
    Status.Profit = ifelse(Gap.Profit >= 0, "Melampaui Budget", "Di Bawah Budget"),
    Year = format(as.Date(Date), "%Y")
  )

cat("Variabel baru dibuat: Gap.Sales, Gap.Profit, Pct.Gap.Sales, Pct.Gap.Profit\n")
## Variabel baru dibuat: Gap.Sales, Gap.Profit, Pct.Gap.Sales, Pct.Gap.Profit

3. Eksplorasi dan Analisis Data

3.1 Eksplorasi Data

3.1.1 Statistik Deskriptif

# Statistik deskriptif variabel utama
stats_summary <- df %>%
  summarise(
    # Sales
    Mean_Budget_Sales   = round(mean(Budget.Sales, na.rm=TRUE), 2),
    Mean_Actual_Sales   = round(mean(Sales, na.rm=TRUE), 2),
    Mean_Gap_Sales      = round(mean(Gap.Sales, na.rm=TRUE), 2),
    SD_Gap_Sales        = round(sd(Gap.Sales, na.rm=TRUE), 2),
    # Profit
    Mean_Budget_Profit  = round(mean(Budget.Profit, na.rm=TRUE), 2),
    Mean_Actual_Profit  = round(mean(Profit, na.rm=TRUE), 2),
    Mean_Gap_Profit     = round(mean(Gap.Profit, na.rm=TRUE), 2),
    SD_Gap_Profit       = round(sd(Gap.Profit, na.rm=TRUE), 2)
  )

# Tampilan dalam bentuk tabel vertikal
stats_df <- data.frame(
  Metrik = c(
    "Rata-rata Budget Sales", "Rata-rata Aktual Sales", "Rata-rata Gap Sales", "SD Gap Sales",
    "Rata-rata Budget Profit", "Rata-rata Aktual Profit", "Rata-rata Gap Profit", "SD Gap Profit"
  ),
  Nilai = c(
    stats_summary$Mean_Budget_Sales, stats_summary$Mean_Actual_Sales,
    stats_summary$Mean_Gap_Sales, stats_summary$SD_Gap_Sales,
    stats_summary$Mean_Budget_Profit, stats_summary$Mean_Actual_Profit,
    stats_summary$Mean_Gap_Profit, stats_summary$SD_Gap_Profit
  )
)

kable(stats_df, caption = "Statistik Deskriptif: Budget vs. Aktual (Sales & Profit)")
Statistik Deskriptif: Budget vs. Aktual (Sales & Profit)
Metrik Nilai
Rata-rata Budget Sales 175.65
Rata-rata Aktual Sales 192.99
Rata-rata Gap Sales 17.34
SD Gap Sales 44.28
Rata-rata Budget Profit 60.91
Rata-rata Aktual Profit 61.10
Rata-rata Gap Profit 0.18
SD Gap Profit 38.74
# Proporsi transaksi yang melampaui vs di bawah budget
prop_sales <- df %>%
  count(Status.Sales) %>%
  mutate(Proporsi = round(n / sum(n) * 100, 1))

prop_profit <- df %>%
  count(Status.Profit) %>%
  mutate(Proporsi = round(n / sum(n) * 100, 1))

cat("=== Proporsi Status Sales ===\n")
## === Proporsi Status Sales ===
print(prop_sales)
## # A tibble: 2 × 3
##   Status.Sales         n Proporsi
##   <chr>            <int>    <dbl>
## 1 Di Bawah Budget   1033     24.3
## 2 Melampaui Budget  3215     75.7
cat("\n=== Proporsi Status Profit ===\n")
## 
## === Proporsi Status Profit ===
print(prop_profit)
## # A tibble: 2 × 3
##   Status.Profit        n Proporsi
##   <chr>            <int>    <dbl>
## 1 Di Bawah Budget   2507       59
## 2 Melampaui Budget  1741       41

3.1.2 Visualisasi Distribusi Gap Sales dan Gap Profit

# Mengubah data ke format panjang untuk visualisasi
gap_long <- df %>%
  select(Gap.Sales, Gap.Profit) %>%
  pivot_longer(cols = everything(),
               names_to = "Jenis_Gap",
               values_to = "Nilai") %>%
  mutate(Jenis_Gap = recode(Jenis_Gap,
                             "Gap.Sales"  = "Gap Sales (Aktual - Budget)",
                             "Gap.Profit" = "Gap Profit (Aktual - Budget)"))

# Plot histogram distribusi gap
ggplot(gap_long, aes(x = Nilai, fill = Jenis_Gap)) +
  geom_histogram(binwidth = 10, color = "white", alpha = 0.85) +
  geom_vline(xintercept = 0, color = "red", linetype = "dashed", linewidth = 1) +
  facet_wrap(~ Jenis_Gap, scales = "free") +
  scale_fill_manual(values = c("#2196F3", "#4CAF50")) +
  labs(
    title    = "Distribusi Gap: Aktual vs. Budget (Sales & Profit)",
    subtitle = "Garis merah putus-putus = titik impas (Gap = 0); Bar di kanan = melampaui budget",
    x        = "Gap (Aktual - Budget)",
    y        = "Frekuensi",
    fill     = "Jenis"
  ) +
  theme_minimal(base_size = 13) +
  theme(legend.position = "none",
        strip.text = element_text(face = "bold"))

3.1.3 Visualisasi Rata-rata Gap per Wilayah Pasar

# Menghitung rata-rata gap per market
gap_market <- df %>%
  group_by(Market) %>%
  summarise(
    Avg_Gap_Sales  = mean(Gap.Sales, na.rm = TRUE),
    Avg_Gap_Profit = mean(Gap.Profit, na.rm = TRUE)
  ) %>%
  pivot_longer(cols = c(Avg_Gap_Sales, Avg_Gap_Profit),
               names_to = "Jenis",
               values_to = "Rata_Gap") %>%
  mutate(Jenis = recode(Jenis,
                         "Avg_Gap_Sales"  = "Gap Sales",
                         "Avg_Gap_Profit" = "Gap Profit"),
         Warna = ifelse(Rata_Gap >= 0, "Positif", "Negatif"))

# Plot bar chart
ggplot(gap_market, aes(x = Market, y = Rata_Gap, fill = Warna)) +
  geom_col(width = 0.6) +
  geom_hline(yintercept = 0, color = "black", linewidth = 0.5) +
  geom_text(aes(label = round(Rata_Gap, 1),
                vjust = ifelse(Rata_Gap >= 0, -0.4, 1.2)),
            size = 4, fontface = "bold") +
  facet_wrap(~ Jenis) +
  scale_fill_manual(values = c("Positif" = "#4CAF50", "Negatif" = "#F44336")) +
  labs(
    title    = "Rata-rata Gap (Aktual - Budget) per Wilayah Pasar",
    subtitle = "Hijau = aktual melampaui budget; Merah = aktual di bawah budget",
    x        = "Wilayah Pasar",
    y        = "Rata-rata Gap",
    fill     = "Status"
  ) +
  theme_minimal(base_size = 13) +
  theme(legend.position = "bottom",
        strip.text = element_text(face = "bold"))

3.1.4 Visualisasi Perbandingan Budget vs. Aktual per Lini Produk & Tahun

# Agregasi bulanan
df <- df %>%
  mutate(YearMonth = format(as.Date(Date), "%Y-%m"))

bulanan <- df %>%
  group_by(YearMonth, Product.Line) %>%
  summarise(
    Budget_Sales  = sum(Budget.Sales, na.rm = TRUE),
    Actual_Sales  = sum(Sales, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  pivot_longer(cols = c(Budget_Sales, Actual_Sales),
               names_to = "Tipe",
               values_to = "Nilai") %>%
  mutate(Tipe = recode(Tipe,
                        "Budget_Sales" = "Budget Sales",
                        "Actual_Sales" = "Aktual Sales"))

# Line chart perbandingan
ggplot(bulanan, aes(x = YearMonth, y = Nilai, color = Tipe, group = Tipe)) +
  geom_line(linewidth = 1.1) +
  geom_point(size = 1.5) +
  facet_wrap(~ Product.Line, scales = "free_y") +
  scale_color_manual(values = c("Budget Sales" = "#FF9800", "Aktual Sales" = "#1976D2")) +
  scale_y_continuous(labels = comma) +
  labs(
    title    = "Perbandingan Budget Sales vs. Aktual Sales per Bulan",
    subtitle = "Dibedakan per lini produk (Beans dan Leaves)",
    x        = "Bulan",
    y        = "Total Sales",
    color    = "Keterangan"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    axis.text.x  = element_text(angle = 60, hjust = 1, size = 8),
    legend.position = "bottom",
    strip.text   = element_text(face = "bold")
  )


3.2 Analisis Statistik

Metode: Uji-t Berpasangan & Regresi Linear Sederhana
Alasan pemilihan metode:
- Uji-t berpasangan dipilih karena membandingkan dua pengukuran pada observasi yang sama, yaitu Budget vs. Aktual untuk setiap baris transaksi.
- Regresi linear sederhana dipilih untuk mengukur seberapa baik Budget dapat memprediksi realisasi Aktual. Jika koefisien regresi ≈ 1 dan intersep ≈ 0, berarti budget sangat akurat. Koefisien determinasi (R²) menunjukkan proporsi variansi aktual yang dapat dijelaskan oleh budget.

3.2.1 Uji-t Berpasangan: Aktual Sales vs. Budget Sales

Uji-t berpasangan untuk Sales

Hipotesis:
- H0: rata-rata Gap Sales = 0 (tidak ada perbedaan signifikan)
- H1: rata-rata Gap Sales ≠ 0 (ada perbedaan signifikan)

Taraf signifikansi: α = 0,05 (5%)

Kriteria keputusan:
- Jika p-value < 0,05 → Tolak H0
- Jika p-value ≥ 0,05 → Gagal tolak H0

# Uji-t berpasangan untuk Sales
t_test_sales <- t.test(df$Sales, df$Budget.Sales, paired = TRUE)
print(t_test_sales)
## 
##  Paired t-test
## 
## data:  df$Sales and df$Budget.Sales
## t = 25.518, df = 4247, p-value < 2.2e-16
## alternative hypothesis: true mean difference is not equal to 0
## 95 percent confidence interval:
##  16.00575 18.66986
## sample estimates:
## mean difference 
##        17.33781

Uji-t berpasangan untuk Profit

Hipotesis:
- H0: rata-rata Gap Profit = 0 (tidak ada perbedaan signifikan)
- H1: rata-rata Gap Profit ≠ 0 (ada perbedaan signifikan)

Taraf signifikansi: α = 0,05 (5%)

Kriteria keputusan:
- Jika p-value < 0,05 → Tolak H0
- Jika p-value ≥ 0,05 → Gagal tolak H0

# Uji-t berpasangan untuk Profit
t_test_profit <- t.test(df$Profit, df$Budget.Profit, paired = TRUE)
print(t_test_profit)
## 
##  Paired t-test
## 
## data:  df$Profit and df$Budget.Profit
## t = 0.31011, df = 4247, p-value = 0.7565
## alternative hypothesis: true mean difference is not equal to 0
## 95 percent confidence interval:
##  -0.9809752  1.3496193
## sample estimates:
## mean difference 
##        0.184322

3.2.2 Regresi Linear: Budget Sales → Aktual Sales

Hipotesis:
- H0: Tidak terdapat pengaruh signifikan antara Budget Sales dan Aktual Sales)
- H1: Terdapat pengaruh signifikan antara Budget Sales dan Aktual Sales

Taraf signifikansi: α = 0,05 (5%)

Kriteria keputusan:
- Jika p-value < 0,05 → Tolak H0
- Jika p-value ≥ 0,05 → Gagal tolak H0

# Model regresi linear: Aktual Sales ~ Budget Sales
model_sales <- lm(Sales ~ Budget.Sales, data = df)

# Ringkasan model
summary(model_sales)
## 
## Call:
## lm(formula = Sales ~ Budget.Sales, data = df)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -304.54  -18.99   -3.25   16.10  332.99 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  22.442442   1.045937   21.46   <2e-16 ***
## Budget.Sales  0.970939   0.004543  213.74   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 44.08 on 4246 degrees of freedom
## Multiple R-squared:  0.915,  Adjusted R-squared:  0.9149 
## F-statistic: 4.569e+04 on 1 and 4246 DF,  p-value: < 2.2e-16
# Visualisasi scatterplot + garis regresi untuk Sales
ggplot(df, aes(x = Budget.Sales, y = Sales)) +
  geom_point(alpha = 0.15, color = "#1976D2", size = 1.5) +
  geom_smooth(method = "lm", color = "#F44336", se = TRUE, linewidth = 1.2) +
  geom_abline(slope = 1, intercept = 0, linetype = "dashed",
              color = "gray40", linewidth = 0.8) +
  annotate("text", x = 100, y = 850,
           label = paste0("R² = ", round(summary(model_sales)$r.squared, 3)),
           size = 5, color = "#F44336", fontface = "bold") +
  labs(
    title    = "Regresi Linear: Budget Sales vs. Aktual Sales",
    subtitle = "Garis merah = garis regresi; Garis putus-putus abu = garis identitas (Budget = Aktual)",
    x        = "Budget Sales",
    y        = "Aktual Sales"
  ) +
  theme_minimal(base_size = 13)
## `geom_smooth()` using formula = 'y ~ x'

3.2.3 Regresi Linear: Budget Profit → Aktual Profit

Hipotesis:
- H0: Tidak terdapat pengaruh signifikan antara Budget Profit dan Aktual Profit
- H1: Terdapat pengaruh signifikan antara Budget Profit dan Aktual Profit

Taraf signifikansi: α = 0,05 (5%)

Kriteria keputusan:
- Jika p-value < 0,05 → Tolak H0
- Jika p-value ≥ 0,05 → Gagal tolak H0

# Model regresi linear: Aktual Profit ~ Budget Profit
model_profit <- lm(Profit ~ Budget.Profit, data = df)

# Ringkasan model
summary(model_profit)
## 
## Call:
## lm(formula = Profit ~ Budget.Profit, data = df)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -323.27  -12.05   -0.04   12.04  219.17 
## 
## Coefficients:
##                 Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   -11.927300   0.683508  -17.45   <2e-16 ***
## Budget.Profit   1.198834   0.006823  175.71   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 35.37 on 4246 degrees of freedom
## Multiple R-squared:  0.8791, Adjusted R-squared:  0.8791 
## F-statistic: 3.088e+04 on 1 and 4246 DF,  p-value: < 2.2e-16
# Visualisasi scatterplot + garis regresi untuk Profit
ggplot(df, aes(x = Budget.Profit, y = Profit)) +
  geom_point(alpha = 0.15, color = "#4CAF50", size = 1.5) +
  geom_smooth(method = "lm", color = "#F44336", se = TRUE, linewidth = 1.2) +
  geom_abline(slope = 1, intercept = 0, linetype = "dashed",
              color = "gray40", linewidth = 0.8) +
  annotate("text", x = -200, y = 700,
           label = paste0("R² = ", round(summary(model_profit)$r.squared, 3)),
           size = 5, color = "#F44336", fontface = "bold") +
  labs(
    title    = "Regresi Linear: Budget Profit vs. Aktual Profit",
    subtitle = "Garis merah = garis regresi; Garis putus-putus abu = garis identitas (Budget = Aktual)",
    x        = "Budget Profit",
    y        = "Aktual Profit"
  ) +
  theme_minimal(base_size = 13)
## `geom_smooth()` using formula = 'y ~ x'

3.2.4 Uji Asumsi Regresi

- Uji Normalitas Residual (Shapiro-Wilk)

Hipotesis:
- H0 : Residual berdistribusi normal
- H1 : Residual tidak berdistribusi normal

Taraf signifikansi: α = 0,05 (5%)

Kriteria keputusan:
- Jika p-value ≤ 0,05 → Tolak H0
- Jika p-value > 0,05 → Gagal tolak H0

# Mengambil residual dari model
res_sales  <- residuals(model_sales)
res_profit <- residuals(model_profit)

# Uji Shapiro-Wilk
shapiro_sales  <- shapiro.test(res_sales)
shapiro_profit <- shapiro.test(res_profit)

# Menampilkan hasil
shapiro_sales
## 
##  Shapiro-Wilk normality test
## 
## data:  res_sales
## W = 0.87028, p-value < 2.2e-16
shapiro_profit
## 
##  Shapiro-Wilk normality test
## 
## data:  res_profit
## W = 0.82265, p-value < 2.2e-16

Plot:

# Q-Q plot untuk Sales
qqnorm(res_sales, main = "Q-Q Plot Residual (Sales)")
qqline(res_sales, col = "red")

# Q-Q plot untuk Profit
qqnorm(res_profit, main = "Q-Q Plot Residual (Profit)")
qqline(res_profit, col = "red")

- Uji Heteroskedastisitas (Breusch-Pagan)

Hipotesis:
- H0 : Tidak terjadi heteroskedastisitas (varians residual konstan)
- H1 : Terjadi heteroskedastisitas (varians residual tidak konstan)

Taraf signifikansi: α = 0,05 (5%)

Kriteria keputusan:
- Jika p-value ≤ 0,05 → Tolak H0
- Jika p-value > 0,05 → Gagal tolak H0

# Uji Breusch-Pagan
bp_sales  <- bptest(model_sales)
bp_profit <- bptest(model_profit)

# Menampilkan hasil
bp_sales
## 
##  studentized Breusch-Pagan test
## 
## data:  model_sales
## BP = 1114.9, df = 1, p-value < 2.2e-16
bp_profit
## 
##  studentized Breusch-Pagan test
## 
## data:  model_profit
## BP = 264.3, df = 1, p-value < 2.2e-16

Plot:

# Plot untuk Sales
plot(model_sales$fitted.values, residuals(model_sales),
     main = "Residual vs Fitted (Sales)",
     xlab = "Nilai Prediksi",
     ylab = "Residual")
abline(h = 0, col = "red")

# Plot untuk Profit
plot(model_profit$fitted.values, residuals(model_profit),
     main = "Residual vs Fitted (Profit)",
     xlab = "Nilai Prediksi",
     ylab = "Residual")
abline(h = 0, col = "red")

Untuk mengatasi pelanggaran asumsi heteroskedastisitas yang terdeteksi pada model regresi, digunakan pendekatan robust standard error dengan metode White (HC1). Metode ini tidak mengubah nilai koefisien regresi, tetapi memperbaiki estimasi standard error sehingga hasil uji signifikansi parameter menjadi lebih reliabel meskipun varians residual tidak konstan. Dengan demikian, interpretasi hubungan antara variabel budget dan aktual tetap valid secara statistik.

Robust Standard Error Regression (White’s HC1 Estimator)

Model 1: Sales
- H0: β₁ = 0 (Tidak terdapat pengaruh signifikan antara Budget Sales terhadap Actual Sales)
- H1: β₁ ≠ 0 (Terdapat pengaruh signifikan antara Budget Sales terhadap Actual Sales)

Model 2: Profit
- H0: β₁ = 0 (Tidak terdapat pengaruh signifikan antara Budget Profit terhadap Actual Profit)
- H1: β₁ ≠ 0 (Terdapat pengaruh signifikan antara Budget Profit terhadap Actual Profit)

Taraf signifikansi: α = 0,05 (5%)

Kriteria Keputusan:
- Jika p-value ≤ 0,05 → Tolak H0
- Jika p-value > 0,05 → Gagal tolak H0

coeftest(model_sales, vcov = vcovHC(model_sales, type = "HC1"))
## 
## t test of coefficients:
## 
##               Estimate Std. Error t value  Pr(>|t|)    
## (Intercept)  22.442442   1.571571  14.280 < 2.2e-16 ***
## Budget.Sales  0.970939   0.011083  87.609 < 2.2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
coeftest(model_profit, vcov = vcovHC(model_profit, type = "HC1"))
## 
## t test of coefficients:
## 
##                 Estimate Std. Error t value  Pr(>|t|)    
## (Intercept)   -11.927300   1.126124 -10.591 < 2.2e-16 ***
## Budget.Profit   1.198834   0.019557  61.300 < 2.2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

- Uji Goodness of Fit (R²)

R² menunjukkan proporsi variasi variabel dependen yang dapat dijelaskan oleh model.
Nilai berkisar antara 0–1:
- Mendekati 1 → model sangat baik dalam menjelaskan data
- Mendekati 0 → kemampuan penjelasan model rendah

# Ringkasan model
summary(model_sales)
## 
## Call:
## lm(formula = Sales ~ Budget.Sales, data = df)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -304.54  -18.99   -3.25   16.10  332.99 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  22.442442   1.045937   21.46   <2e-16 ***
## Budget.Sales  0.970939   0.004543  213.74   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 44.08 on 4246 degrees of freedom
## Multiple R-squared:  0.915,  Adjusted R-squared:  0.9149 
## F-statistic: 4.569e+04 on 1 and 4246 DF,  p-value: < 2.2e-16
summary(model_profit)
## 
## Call:
## lm(formula = Profit ~ Budget.Profit, data = df)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -323.27  -12.05   -0.04   12.04  219.17 
## 
## Coefficients:
##                 Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   -11.927300   0.683508  -17.45   <2e-16 ***
## Budget.Profit   1.198834   0.006823  175.71   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 35.37 on 4246 degrees of freedom
## Multiple R-squared:  0.8791, Adjusted R-squared:  0.8791 
## F-statistic: 3.088e+04 on 1 and 4246 DF,  p-value: < 2.2e-16

3.2.5 Analisis Gap per Wilayah Pasar

# Tabel ringkasan gap per Market
tabel_gap_market <- df %>%
  group_by(Market) %>%
  summarise(
    N               = n(),
    Avg_Budget_Sales  = round(mean(Budget.Sales, na.rm=TRUE), 1),
    Avg_Actual_Sales  = round(mean(Sales, na.rm=TRUE), 1),
    Avg_Gap_Sales     = round(mean(Gap.Sales, na.rm=TRUE), 1),
    Pct_Gap_Sales     = round(mean(Pct.Gap.Sales, na.rm=TRUE), 1),
    Avg_Budget_Profit = round(mean(Budget.Profit, na.rm=TRUE), 1),
    Avg_Actual_Profit = round(mean(Profit, na.rm=TRUE), 1),
    Avg_Gap_Profit    = round(mean(Gap.Profit, na.rm=TRUE), 1),
    Pct_Gap_Profit    = round(mean(Pct.Gap.Profit, na.rm=TRUE), 1),
    .groups = "drop"
  )

kable(tabel_gap_market,
      col.names = c("Market", "N", "Avg Budget Sales", "Avg Aktual Sales", "Avg Gap Sales",
                    "% Gap Sales", "Avg Budget Profit", "Avg Aktual Profit",
                    "Avg Gap Profit", "% Gap Profit"),
      caption = "Ringkasan Gap Budget vs. Aktual per Wilayah Pasar")
Ringkasan Gap Budget vs. Aktual per Wilayah Pasar
Market N Avg Budget Sales Avg Aktual Sales Avg Gap Sales % Gap Sales Avg Budget Profit Avg Aktual Profit Avg Gap Profit % Gap Profit
Central 1344 186.3 197.2 10.9 10.5 68.9 69.8 0.9 NaN
East 888 177.8 201.1 23.3 16.5 63.9 66.7 2.7 -Inf
South 672 146.1 154.7 8.5 12.9 52.1 48.3 -3.8 NaN
West 1344 178.3 202.6 24.2 Inf 55.3 55.1 -0.3 NaN
# Tabel ringkasan gap per Product Line
tabel_gap_produk <- df %>%
  group_by(Product.Line) %>%
  summarise(
    N               = n(),
    Avg_Gap_Sales   = round(mean(Gap.Sales, na.rm=TRUE), 1),
    Pct_Gap_Sales   = round(mean(Pct.Gap.Sales, na.rm=TRUE), 1),
    Avg_Gap_Profit  = round(mean(Gap.Profit, na.rm=TRUE), 1),
    Pct_Gap_Profit  = round(mean(Pct.Gap.Profit, na.rm=TRUE), 1),
    .groups = "drop"
  )

kable(tabel_gap_produk,
      col.names = c("Lini Produk", "N", "Avg Gap Sales", "% Gap Sales",
                    "Avg Gap Profit", "% Gap Profit"),
      caption = "Ringkasan Gap Budget vs. Aktual per Lini Produk")
Ringkasan Gap Budget vs. Aktual per Lini Produk
Lini Produk N Avg Gap Sales % Gap Sales Avg Gap Profit % Gap Profit
Beans 2232 -1.0 3.8 -5.6 NaN
Leaves 2016 37.7 Inf 6.6 NaN

Nilai NaN dan Inf muncul karena terdapat nilai budget yang sama dengan nol. Dalam perhitungan persentase gap, nilai gap dibagi dengan budget, sehingga pembagian dengan nol menyebabkan hasil tidak terdefinisi. Nilai NaN terjadi pada kondisi 0/0, sedangkan Inf terjadi ketika suatu nilai dibagi dengan nol. Hal ini menunjukkan bahwa persentase gap tidak dapat dihitung pada beberapa observasi.


3.3 Interpretasi

3.3.1 Distribusi Gap dan Uji-t

Berdasarkan statistik deskriptif dan uji-t berpasangan:

  • Gap Sales memiliki rata-rata sebesar 17,34 dengan standar deviasi yang cukup besar. Hal ini menunjukkan bahwa secara rata-rata, realisasi penjualan cenderung lebih tinggi dibandingkan budget (over-budget), namun terdapat variasi yang cukup tinggi antar observasi.

  • Gap Profit memiliki rata-rata yang sangat kecil yaitu sekitar 0,18 dengan standar deviasi yang relatif besar. Hal ini mengindikasikan bahwa secara rata-rata, realisasi profit hampir seimbang dengan budget, namun tetap terdapat fluktuasi yang cukup besar pada tingkat individu.

  • Dari histogram distribusi gap, kedua distribusi mendekati simetris di sekitar 0, namun terdapat outlier ekstrem di kedua sisi. Hal ini menandakan adanya transaksi individual yang mengalami deviasi sangat besar dari budget, baik ke arah positif maupun negatif.

Hasil uji-t berpasangan menunjukkan bahwa:
- Terdapat perbedaan yang signifikan antara Budget Sales dan Actual Sales (p-value < 0,05), sehingga H0 ditolak.
- Tidak terdapat perbedaan yang signifikan antara Budget Profit dan Actual Profit (p-value > 0,05), sehingga H0 gagal ditolak.

Hal ini mengindikasikan bahwa perencanaan penjualan (sales budgeting) cenderung mengalami deviasi dari realisasi, sedangkan perencanaan profit lebih stabil dan sesuai dengan realisasi aktual.

3.3.2 Hasil Regresi Linear

Model Sales: Aktual Sales = β₀ + β₁ × Budget Sales

  • Koefisien Budget Sales sebesar 0,97 menunjukkan hubungan yang sangat kuat dan hampir proporsional antara budget dan realisasi sales.
  • Hal ini menunjukkan bahwa setiap kenaikan 1 unit budget sales diikuti oleh kenaikan sekitar 0,97 unit aktual sales.
  • Nilai ini mendekati 1 yang mengindikasikan bahwa proses budgeting sales cukup akurat dalam memprediksi kondisi aktual.

Model Profit: Aktual Profit = β₀ + β₁ × Budget Profit

  • Koefisien Budget Profit sebesar 1,20 menunjukkan bahwa perubahan budget profit diikuti oleh perubahan actual profit yang lebih besar.
  • Hal ini mengindikasikan bahwa profit lebih sensitif terhadap perubahan anggaran dibandingkan sales.
  • Artinya, perubahan kecil pada budget profit dapat menghasilkan variasi yang lebih besar pada realisasi profit.

3.3.3 Hasil Uji Asumsi

Uji Normalitas Residual Uji normalitas residual menunjukkan bahwa residual tidak sepenuhnya berdistribusi normal (p-value < 0,05). Namun, karena jumlah sampel sangat besar (n = 4.248), pelanggaran ini tidak menjadi masalah serius berdasarkan prinsip Central Limit Theorem, di mana distribusi sampling cenderung normal pada ukuran sampel besar.

Uji Heteroskedastisitas Hasil uji Breusch-Pagan menunjukkan adanya heteroskedastisitas pada model (p-value < 0,05), yang berarti varians residual tidak konstan. Untuk mengatasi hal ini digunakan robust standard error (White HC1) sehingga estimasi standard error menjadi lebih reliabel tanpa mengubah nilai koefisien regresi.

Goodness of Fit (Kelayakan Model) Goodness of fit diukur menggunakan nilai R-squared (R²) dari model regresi.

Hasil menunjukkan bahwa:
- Model Sales memiliki nilai R² yang sangat tinggi (±0,91), yang berarti sekitar 91% variasi Aktual Sales dapat dijelaskan oleh Budget Sales. - Model Profit memiliki nilai R² yang juga tinggi (±0,88), yang berarti sekitar 88% variasi Aktual Profit dapat dijelaskan oleh Budget Profit.

Nilai R² yang tinggi menunjukkan bahwa model regresi memiliki kemampuan prediktif yang sangat baik dalam menjelaskan hubungan antara budget dan realisasi.

Namun demikian, adanya heteroskedastisitas menunjukkan bahwa meskipun model memiliki daya jelaskan yang tinggi, varians error tidak konstan sehingga diperlukan robust standard error untuk menjaga validitas inferensi statistik.

3.3.4 Interpretasi per Wilayah dan Lini Produk

Per Wilayah Pasar:

  • Wilayah dengan Gap Sales positif (aktual > budget) merupakan wilayah yang kinerjanya melebihi ekspektasi. Manajemen perlu mengidentifikasi faktor keberhasilan di wilayah ini untuk direplikasi di wilayah lain.
  • Wilayah dengan Gap Profit negatif meski Gap Sales positif mengindikasikan adanya masalah pada efisiensi biaya, penjualan tinggi tetapi profit tidak mengikuti, kemungkinan karena biaya pemasaran atau COGS yang membengkak.

Per Lini Produk:

  • Perbedaan gap antara lini Beans dan Leaves mencerminkan perbedaan karakteristik pasar. Lini produk dengan gap profit lebih besar menunjukkan bahwa proses penetapan budget untuk lini tersebut perlu dikalibrasi ulang.

Implikasi Bisnis:

Secara keseluruhan, analisis ini mengindikasikan bahwa proses budgeting Coffee Chain sudah cukup baik karena R² yang tinggi (budget merupakan prediktor kuat aktual). Namun, terdapat inkonsistensi di level wilayah dan produk yang perlu diperhatikan manajemen. Deviasi negatif yang signifikan pada profit di beberapa segmen menunjukkan perlunya review rutin anggaran berbasis data aktual (rolling forecast) agar perencanaan lebih responsif terhadap dinamika pasar.


4. Kesimpulan

  1. Karakteristik Gap Budget vs. Aktual: Rata-rata gap sales bernilai positif sebesar 17,34, sedangkan gap profit bernilai sangat kecil yaitu sekitar 0,18. Hal ini menunjukkan bahwa secara umum realisasi penjualan cenderung sedikit melampaui budget, sementara profit relatif seimbang dengan budget. Namun, standar deviasi yang besar pada kedua variabel menunjukkan adanya variasi yang tinggi pada tingkat transaksi individual.

  2. Gap Antar Wilayah: Terdapat perbedaan kinerja yang signifikan antar wilayah pasar. Sebagian wilayah secara konsisten melampaui budget (gap positif), sementara wilayah lainnya cenderung berada di bawah target. Hal ini menunjukkan perlunya pendekatan budgeting yang lebih spesifik berdasarkan karakteristik masing-masing wilayah.

  3. Hubungan Linear Budget–Aktual: Hasil regresi linear dengan robust standard error menunjukkan bahwa Budget memiliki pengaruh yang signifikan secara statistik terhadap Aktual, terutama pada variabel Sales yang memiliki nilai R² tinggi. Hal ini menunjukkan adanya hubungan linear yang kuat antara perencanaan anggaran dan realisasi penjualan. Sementara itu, pada variabel Profit, nilai R² yang lebih rendah menunjukkan bahwa profit lebih dipengaruhi oleh faktor lain di luar budget, seperti efisiensi biaya dan kondisi operasional.

  4. Rekomendasi Manajerial:

    • Menerapkan rolling forecast secara berkala untuk menyesuaikan budget dengan kondisi pasar terkini.
    • Memfokuskan evaluasi pada wilayah dengan gap profit negatif untuk mengidentifikasi sumber inefisiensi biaya.
    • Melakukan penyesuaian metode budgeting pada lini produk dengan variasi gap yang tinggi.
    • Memanfaatkan model regresi sebagai alat bantu dalam memproyeksikan hasil aktual berdasarkan nilai budget.