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.
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.
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.
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")
| 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
# 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)")
| 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
# 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)")
| 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
# 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"))
# 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"))
# 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")
)
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.
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
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
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'
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'
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")
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.
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
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
# 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")
| 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")
| 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.
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.
Model Sales:
Aktual Sales = β₀ + β₁ × Budget Sales
Model Profit:
Aktual Profit = β₀ + β₁ × Budget Profit
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.
Per Wilayah Pasar:
Per Lini Produk:
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.
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.
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.
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.
Rekomendasi Manajerial: