Functions & Loops

Prakticum ~ Week 5


Profile Photo
🎓 Data Science Student

👩‍💻 NAMA: Raihania Syah Putri

🎫 NIM: 52250054

🏛️ Institut Teknologi Sains Bandung

📊 R Programming
🧠 Data Science
📈 Statistics

1 Pendahuluan

Tugas Advanced Practicum ini merupakan implementasi dari konsep fungsi dan perulangan (loops) dalam konteks Data Science. Terdapat 8 tugas utama yang harus diselesaikan, mulai dari simulasi data penjualan, Monte Carlo, hingga pembuatan dashboard KPI perusahaan. Semua kode akan menggunakan R dan dipublikasikan ke RPubs.


2 Soal 1 — Dynamic Multi-Formula Function


2.1 Penjelasan

Tugas ini meminta kita membangun sebuah fungsi bernama compute_formula(x, formula) yang mampu menghitung empat jenis formula matematika sekaligus, yaitu linear, quadratic, cubic, dan exponential. Fungsi ini dilengkapi dengan validasi input sehingga jika pengguna memasukkan nama formula yang tidak dikenal, program akan langsung menampilkan pesan error. Nested loop digunakan untuk menghitung semua formula secara efisien, dan hasilnya divisualisasikan dalam satu grafik interaktif untuk x = 1 sampai 20.

2.2 Langkah-Langkah Pengerjaan

Langkah 1 — Definisi Formula

Setiap formula didefinisikan dalam bentuk fungsi anonim di dalam sebuah list, sehingga mudah dipanggil secara iteratif:

  • Linear: \(f(x) = 2x + 1\)
  • Quadratic: \(f(x) = x^2 - 3x + 2\)
  • Cubic: \(f(x) = x^3 - 2x^2 + x\)
  • Exponential: \(f(x) = e^{0.3x}\)

Langkah 2 — Validasi Input

Sebelum menghitung, fungsi memeriksa apakah nama formula yang dimasukkan valid. Jika tidak, fungsi menghentikan eksekusi dengan pesan error yang informatif. Ini penting agar fungsi tidak berjalan dengan data yang salah.

Langkah 3 — Nested Loop & Visualisasi

Loop digunakan untuk mengiterasi setiap nama formula dalam list dan menghitung nilainya untuk seluruh vektor x. Hasilnya digabungkan ke dalam satu data frame, kemudian diubah ke format panjang (long format) agar dapat divisualisasikan dalam satu grafik menggunakan plotly.

library(plotly)
library(reshape2)

# Fungsi utama dengan validasi dan nested loop
compute_formula <- function(x, formula = "all") {
  valid <- c("linear", "quadratic", "cubic", "exponential", "all")
  if (!(formula %in% valid))
    stop(paste("Formula tidak valid! Pilih:", paste(valid, collapse = ", ")))

  # Definisi semua formula dalam list
  formulas <- list(
    linear      = function(x) 2 * x + 1,
    quadratic   = function(x) x^2 - 3 * x + 2,
    cubic       = function(x) x^3 - 2 * x^2 + x,
    exponential = function(x) exp(0.3 * x)
  )

  hasil <- data.frame(x = x)
  # Loop untuk hitung semua formula sekaligus
  target <- if (formula == "all") names(formulas) else formula
  for (nm in target) {
    hasil[[nm]] <- round(formulas[[nm]](x), 2)
  }
  return(hasil)
}

# Hitung untuk x = 1:20
df_formula <- compute_formula(1:20, formula = "all")
head(df_formula, 5)
# Ubah ke format panjang untuk plotting
df_long <- melt(df_formula, id.vars = "x",
                variable.name = "Formula", value.name = "Nilai")

warna <- c("linear" = "#2196F3", "quadratic" = "#4CAF50",
           "cubic" = "#FF9800", "exponential" = "#E91E63")

fig <- plot_ly()
for (fm in unique(df_long$Formula)) {
  d <- df_long[df_long$Formula == fm, ]
  fig <- fig %>%
    add_trace(x = d$x, y = d$Nilai, type = "scatter", mode = "lines+markers",
              name = fm,
              line   = list(color = warna[fm], width = 2.5),
              marker = list(color = warna[fm], size = 6),
              text   = paste0("<b>Formula:</b> ", fm,
                              "<br><b>x =</b> ", d$x,
                              "<br><b>f(x) =</b> ", d$Nilai),
              hoverinfo = "text")
}

fig %>% layout(
  title  = list(text = "<b>Perbandingan Empat Formula Matematika (x = 1 sampai 20)</b>",
                x = 0.5, font = list(size = 15)),
  xaxis  = list(title = "<b>Nilai x</b>", dtick = 2),
  yaxis  = list(title = "<b>f(x)</b>"),
  legend = list(title = list(text = "<b>Formula</b>"), orientation = "h",
                x = 0.5, xanchor = "center", y = -0.15),
  hovermode     = "closest",
  plot_bgcolor  = "white",
  paper_bgcolor = "white",
  margin = list(l = 60, r = 40, t = 70, b = 80)
)

2.2.1 Interpretasi

Grafik memperlihatkan karakteristik pertumbuhan yang sangat berbeda dari keempat formula. Formula exponential tumbuh paling cepat — nilainya meledak secara drastis mendekati x = 20. Formula cubic juga tumbuh cepat namun masih jauh di bawah eksponensial. Formula quadratic membentuk parabola yang naik secara bertahap, sementara formula linear tumbuh paling lambat dan konsisten. Validasi input memastikan pengguna mendapat feedback langsung jika memasukkan nama formula yang salah.


3 Soal 2 — Nested Simulation: Multi-Sales & Discounts


3.1 Penjelasan

Soal ini meminta kita mensimulasikan data penjualan harian menggunakan fungsi simulate_sales(n_salesperson, days). Struktur fungsi menggunakan nested loop — loop luar mengiterasi setiap salesperson, sedangkan loop dalam mengiterasi setiap hari. Di dalam loop tersebut, terdapat nested function untuk menghitung diskon kondisional berdasarkan nilai penjualan, serta fungsi terpisah untuk menghitung penjualan kumulatif. Pendekatan ini mencerminkan cara kerja laporan penjualan nyata di sebuah perusahaan.

3.2 Langkah-Langkah Pengerjaan

Langkah 1 — Nested Function: Hitung Diskon

Aturan diskon bersifat kondisional berdasarkan sales_amount:

  • Penjualan ≥ 900 → diskon 20%
  • Penjualan 600–899 → diskon 15%
  • Penjualan 300–599 → diskon 10%
  • Penjualan < 300 → diskon 5%

Langkah 2 — Nested Loop

Loop luar berjalan untuk setiap salesperson (1 sampai n_salesperson). Di dalamnya, loop dalam berjalan untuk setiap hari (1 sampai days). Pada setiap iterasi, penjualan acak dibangkitkan, diskon dihitung, dan nilai kumulatif diperbarui secara bertahap.

Langkah 3 — Summary & Visualisasi

Setelah semua iterasi selesai, data dirangkum per salesperson menggunakan dplyr. Plot kumulatif menunjukkan tren pertumbuhan penjualan bersih masing-masing salesperson dari hari ke hari, sehingga manajemen dapat membandingkan performa dengan mudah.

library(dplyr)
library(knitr)
set.seed(42)

simulate_sales <- function(n_salesperson, days) {

  # Nested function 1: hitung diskon
  hitung_diskon <- function(amount) {
    if      (amount >= 900) return(0.20)
    else if (amount >= 600) return(0.15)
    else if (amount >= 300) return(0.10)
    else                    return(0.05)
  }

  hasil <- data.frame()

  # Nested loop: salesperson × hari
  for (sp in 1:n_salesperson) {
    kumulatif <- 0
    for (d in 1:days) {
      amount    <- round(runif(1, 100, 1000), 0)
      diskon    <- hitung_diskon(amount)
      net       <- round(amount * (1 - diskon), 2)
      kumulatif <- round(kumulatif + net, 2)

      hasil <- rbind(hasil, data.frame(
        sales_id      = paste0("SP-", sp),
        day           = d,
        sales_amount  = amount,
        discount_rate = paste0(diskon * 100, "%"),
        net_sales     = net,
        cumulative    = kumulatif
      ))
    }
  }
  return(hasil)
}

df_sales <- simulate_sales(n_salesperson = 5, days = 10)
head(df_sales, 10)
# Summary per salesperson
summary_sales <- df_sales %>%
  group_by(sales_id) %>%
  summarise(
    Total_Penjualan = sum(sales_amount),
    Total_Net       = sum(net_sales),
    Kumulatif_Akhir = max(cumulative),
    .groups = "drop"
  )
kable(summary_sales, caption = "Ringkasan Penjualan per Salesperson")
Ringkasan Penjualan per Salesperson
sales_id Total_Penjualan Total_Net Kumulatif_Akhir
SP-1 6726 5692.15 5692.15
SP-2 6309 5334.15 5334.15
SP-3 6538 5484.45 5484.45
SP-4 5675 4850.20 4850.20
SP-5 6701 5634.80 5634.80
# Plot kumulatif interaktif
warna_sp <- c("#2196F3","#4CAF50","#FF9800","#E91E63","#9C27B0")
fig2 <- plot_ly()

for (i in seq_along(unique(df_sales$sales_id))) {
  sp  <- unique(df_sales$sales_id)[i]
  d   <- df_sales[df_sales$sales_id == sp, ]
  fig2 <- fig2 %>%
    add_trace(x = d$day, y = d$cumulative, type = "scatter",
              mode = "lines+markers", name = sp,
              line   = list(color = warna_sp[i], width = 2.5),
              marker = list(color = warna_sp[i], size = 7),
              text   = paste0("<b>", sp, "</b>",
                              "<br>Hari ke-", d$day,
                              "<br>Kumulatif: Rp ", format(d$cumulative, big.mark = ",")),
              hoverinfo = "text")
}

fig2 %>% layout(
  title  = list(text = "<b>Penjualan Kumulatif per Salesperson (10 Hari)</b>",
                x = 0.5, font = list(size = 15)),
  xaxis  = list(title = "<b>Hari ke-</b>", dtick = 1),
  yaxis  = list(title = "<b>Total Kumulatif (Rp)</b>"),
  legend = list(title = list(text = "<b>Salesperson</b>"),
                orientation = "h", x = 0.5, xanchor = "center", y = -0.18),
  hovermode     = "closest",
  plot_bgcolor  = "white",
  paper_bgcolor = "white",
  margin = list(l = 60, r = 40, t = 70, b = 90)
)

3.2.1 Interpretasi

Hasil simulasi memperlihatkan 5 salesperson selama 10 hari dengan aturan diskon yang berbeda-beda tergantung nilai transaksi. Plot kumulatif menunjukkan bahwa pertumbuhan masing-masing salesperson relatif konsisten, namun ada perbedaan total akhir yang cukup signifikan. Salesperson dengan tren garis paling curam adalah yang paling produktif. Informasi ini sangat berguna bagi manajemen untuk menentukan target bonus dan merancang strategi insentif penjualan.


4 Soal 3 — Multi-Level Performance Categorization


4.1 Penjelasan

Fungsi categorize_performance(sales_amount) bertugas mengklasifikasikan setiap nilai penjualan ke dalam 5 level kinerja: Excellent, Very Good, Good, Average, dan Poor. Fungsi ini menggunakan loop untuk memproses setiap elemen dalam vektor satu per satu, menerapkan logika kondisional berdasarkan ambang batas yang telah ditentukan. Setelah klasifikasi selesai, distribusi kategori dihitung persentasenya dan divisualisasikan dalam dua jenis grafik untuk memudahkan analisis.

4.2 Langkah-Langkah Pengerjaan

Langkah 1 — Definisi Ambang Batas

Batas nilai untuk setiap kategori:

  • ≥ 850 → Excellent
  • 650–849 → Very Good
  • 450–649 → Good
  • 250–449 → Average
  • < 250 → Poor

Langkah 2 — Loop Kategorisasi

Fungsi menggunakan seq_along() untuk mengiterasi setiap elemen vektor. Di dalam setiap iterasi, nilai dibandingkan dengan ambang batas menggunakan if-else if-else untuk menentukan kategorinya.

Langkah 3 — Hitung Persentase & Visualisasi

Setelah seluruh data dikategorikan, persentase dihitung dengan membagi jumlah tiap kategori dengan total data. Hasil divisualisasikan dengan bar chart untuk perbandingan antar kategori dan pie chart untuk gambaran proporsi keseluruhan.

categorize_performance <- function(sales_amount) {
  kategori <- character(length(sales_amount))
  # Loop melalui setiap elemen vektor
  for (i in seq_along(sales_amount)) {
    val <- sales_amount[i]
    if      (val >= 850) kategori[i] <- "Excellent"
    else if (val >= 650) kategori[i] <- "Very Good"
    else if (val >= 450) kategori[i] <- "Good"
    else if (val >= 250) kategori[i] <- "Average"
    else                 kategori[i] <- "Poor"
  }
  return(kategori)
}

# Generate 200 data penjualan acak
set.seed(123)
sales_data    <- round(runif(200, 50, 1000), 0)
hasil_kategori <- categorize_performance(sales_data)

# Hitung distribusi dan persentase
level_order <- c("Poor","Average","Good","Very Good","Excellent")
df_kat <- as.data.frame(table(hasil_kategori))
names(df_kat) <- c("Kategori","Jumlah")
df_kat$Persentase <- round(df_kat$Jumlah / sum(df_kat$Jumlah) * 100, 1)
df_kat$Kategori   <- factor(df_kat$Kategori, levels = level_order)
df_kat <- df_kat[order(df_kat$Kategori), ]

kable(df_kat, caption = "Distribusi Kategori Kinerja dari 200 Data Penjualan")
Distribusi Kategori Kinerja dari 200 Data Penjualan
Kategori Jumlah Persentase
4 Poor 32 16.0
1 Average 54 27.0
3 Good 43 21.5
5 Very Good 39 19.5
2 Excellent 32 16.0
warna_kat <- c("Poor" = "#EF5350","Average" = "#FF9800",
               "Good" = "#FFCA28","Very Good" = "#66BB6A","Excellent" = "#42A5F5")

# Bar Chart Interaktif
fig_bar <- plot_ly(df_kat, x = ~Kategori, y = ~Jumlah,
                   type = "bar",
                   marker = list(color = warna_kat[as.character(df_kat$Kategori)],
                                 line   = list(color = "white", width = 1.5)),
                   text  = ~paste0(Persentase, "%\n(n = ", Jumlah, ")"),
                   textposition = "outside",
                   hovertext = ~paste0("<b>Kategori: </b>", Kategori,
                                       "<br><b>Jumlah: </b>", Jumlah, " orang",
                                       "<br><b>Persentase: </b>", Persentase, "%"),
                   hoverinfo = "text") %>%
  layout(title       = list(text = "<b>Distribusi Kategori Kinerja — Bar Chart</b>",
                            x = 0.5, font = list(size = 14)),
         xaxis       = list(title = "<b>Kategori Kinerja</b>",
                            categoryorder = "array",
                            categoryarray = level_order),
         yaxis       = list(title = "<b>Jumlah Karyawan</b>"),
         plot_bgcolor  = "white",
         paper_bgcolor = "white",
         margin = list(l = 50, r = 30, t = 60, b = 60),
         showlegend = FALSE)

# Pie Chart Interaktif
fig_pie <- plot_ly(df_kat, labels = ~Kategori, values = ~Jumlah,
                   type   = "pie",
                   marker = list(colors = warna_kat[as.character(df_kat$Kategori)],
                                 line   = list(color = "white", width = 2)),
                   textinfo     = "label+percent",
                   hovertext    = ~paste0("<b>", Kategori, "</b>",
                                          "<br>Jumlah: ", Jumlah, " orang",
                                          "<br>Persentase: ", Persentase, "%"),
                   hoverinfo    = "text",
                   textfont     = list(size = 13)) %>%
  layout(title         = list(text = "<b>Proporsi Kategori Kinerja — Pie Chart</b>",
                              x = 0.5, font = list(size = 14)),
         plot_bgcolor  = "white",
         paper_bgcolor = "white",
         legend        = list(orientation = "v", x = 1.0, y = 0.5),
         margin = list(l = 20, r = 20, t = 60, b = 20))

fig_bar
fig_pie

4.2.1 Interpretasi

Dari 200 data penjualan yang dibangkitkan secara acak, distribusi kategori kinerja cukup merata karena data menggunakan distribusi uniform. Bar chart memudahkan perbandingan jumlah absolut antar kategori, sementara pie chart memberikan gambaran proporsi keseluruhan secara sekilas. Pada data nyata, distribusi ini dapat digunakan manajemen untuk menilai komposisi tim dan merancang program pengembangan karyawan secara lebih terarah.


5 Soal 4 — Multi-Company Dataset Simulation


5.1 Penjelasan

Fungsi generate_company_data(n_company, n_employees) mensimulasikan dataset lengkap multi-perusahaan menggunakan nested loop — loop luar untuk setiap perusahaan dan loop dalam untuk setiap karyawan. Di dalam loop tersebut, logika kondisional menandai karyawan sebagai top performer apabila KPI_score > 90. Setelah dataset terbentuk, summary per perusahaan dihitung dan divisualisasikan untuk mendukung pengambilan keputusan manajerial.

5.2 Langkah-Langkah Pengerjaan

Langkah 1 — Nested Loop

Loop luar: for (co in 1:n_company) mengiterasi setiap perusahaan.
Loop dalam: for (emp in 1:n_employees) mengiterasi setiap karyawan di dalamnya.
Pendekatan ini memastikan setiap kombinasi perusahaan–karyawan diproses secara mandiri.

Langkah 2 — Conditional Logic: Top Performer

Setiap karyawan mendapat atribut top_performer berdasarkan kondisi:
KPI_score > 90 → "Ya", selain itu "Tidak".
Kondisi ini penting untuk program reward dan identifikasi talenta terbaik.

Langkah 3 — Summary & Visualisasi

Setelah dataset lengkap terbentuk, data dirangkum menggunakan dplyr untuk menghasilkan rata-rata gaji, rata-rata performa, KPI maksimum, dan jumlah top performer per perusahaan. Hasilnya divisualisasikan dalam dua grafik batang yang mudah dipahami.

set.seed(99)

generate_company_data <- function(n_company, n_employees) {
  dept   <- c("Finance","HR","IT","Marketing","Operations")
  hasil  <- data.frame()

  # Nested loop: perusahaan × karyawan
  for (co in 1:n_company) {
    for (emp in 1:n_employees) {
      salary     <- round(runif(1, 4000, 15000), 0)
      department <- sample(dept, 1)
      perf       <- round(runif(1, 50, 100), 1)
      kpi        <- round(runif(1, 50, 100), 1)
      top        <- ifelse(kpi > 90, "Ya", "Tidak")   # conditional logic

      hasil <- rbind(hasil, data.frame(
        company_id        = paste0("Perusahaan-", co),
        employee_id       = paste0("E", co, "_", emp),
        salary            = salary,
        department        = department,
        performance_score = perf,
        KPI_score         = kpi,
        top_performer     = top
      ))
    }
  }
  return(hasil)
}

# Jalankan: 4 perusahaan × 30 karyawan
df_company <- generate_company_data(n_company = 4, n_employees = 30)

# Summary per perusahaan
summary_co <- df_company %>%
  group_by(company_id) %>%
  summarise(
    Avg_Salary      = round(mean(salary), 0),
    Avg_Performance = round(mean(performance_score), 2),
    Max_KPI         = max(KPI_score),
    Top_Performers  = sum(top_performer == "Ya"),
    .groups = "drop"
  )

kable(summary_co, caption = "Summary per Perusahaan (4 Perusahaan × 30 Karyawan)")
Summary per Perusahaan (4 Perusahaan × 30 Karyawan)
company_id Avg_Salary Avg_Performance Max_KPI Top_Performers
Perusahaan-1 8554 71.89 99.8 9
Perusahaan-2 9846 75.00 98.4 8
Perusahaan-3 9259 74.40 99.3 7
Perusahaan-4 10272 75.21 99.3 2
warna_co <- c("#2196F3","#4CAF50","#FF9800","#E91E63")

# Plot 1: Rata-rata gaji
fig_gaji <- plot_ly(summary_co, x = ~company_id, y = ~Avg_Salary,
                    type = "bar",
                    marker = list(color = warna_co,
                                  line = list(color = "white", width = 1.5)),
                    text     = ~paste0("Rp ", format(Avg_Salary, big.mark = ",")),
                    textposition = "outside",
                    hovertext = ~paste0("<b>", company_id, "</b>",
                                        "<br>Avg Salary: Rp ",
                                        format(Avg_Salary, big.mark = ",")),
                    hoverinfo = "text") %>%
  layout(title = list(text = "<b>Rata-rata Gaji per Perusahaan</b>",
                      x = 0.5, font = list(size = 13)),
         xaxis = list(title = ""), yaxis = list(title = "<b>Gaji (Rp)</b>"),
         plot_bgcolor = "white", paper_bgcolor = "white",
         showlegend = FALSE, margin = list(t = 60, b = 50))

# Plot 2: Jumlah top performer
fig_top <- plot_ly(summary_co, x = ~company_id, y = ~Top_Performers,
                   type = "bar",
                   marker = list(color = warna_co,
                                 line = list(color = "white", width = 1.5)),
                   text     = ~paste0(Top_Performers, " orang"),
                   textposition = "outside",
                   hovertext = ~paste0("<b>", company_id, "</b>",
                                       "<br>Top Performer (KPI > 90): ",
                                       Top_Performers, " orang"),
                   hoverinfo = "text") %>%
  layout(title = list(text = "<b>Jumlah Top Performer (KPI > 90)</b>",
                      x = 0.5, font = list(size = 13)),
         xaxis = list(title = ""), yaxis = list(title = "<b>Jumlah Karyawan</b>"),
         plot_bgcolor = "white", paper_bgcolor = "white",
         showlegend = FALSE, margin = list(t = 60, b = 50))

fig_gaji
fig_top

5.2.1 Interpretasi

Dataset berhasil dibuat untuk 4 perusahaan dengan total 120 karyawan. Masing-masing perusahaan memiliki profil yang berbeda karena data dibangkitkan secara acak. Grafik gaji rata-rata memperlihatkan perbedaan kompensasi antar perusahaan yang dapat menjadi bahan evaluasi kebijakan penggajian. Grafik top performer menunjukkan distribusi talenta terbaik — perusahaan dengan top performer terbanyak memiliki modal sumber daya manusia yang lebih kuat untuk bersaing.


6 Soal 5 — Monte Carlo Simulation: Pi & Probability


6.1 Penjelasan

Simulasi Monte Carlo adalah metode statistik yang menggunakan titik acak untuk memperkirakan nilai suatu besaran matematis. Pada soal ini, kita memperkirakan nilai π (pi) dengan cara melempar titik acak ke dalam kotak 2×2. Titik yang jatuh di dalam lingkaran satuan (jarak dari pusat ≤ 1) dihitung, lalu rasionya terhadap total titik digunakan untuk mengestimasi π. Selain itu, kita juga menghitung probabilitas titik jatuh dalam sub-kotak [0, 0.5] × [0, 0.5] dan membandingkannya dengan nilai teoritis.

6.2 Langkah-Langkah Pengerjaan

Langkah 1 — Bangkitkan Titik Acak

Sebanyak n_points titik dibangkitkan secara acak pada koordinat (x, y) dalam rentang [−1, 1]. Setiap titik mewakili satu “lemparan” dalam simulasi.

Langkah 2 — Cek Kondisi dengan Loop

Loop mengecek setiap titik: apakah \(x^2 + y^2 \leq 1\) (dalam lingkaran) dan apakah \(0 \leq x \leq 0.5\), \(0 \leq y \leq 0.5\) (dalam sub-kotak). Hasil dicatat sebagai TRUE atau FALSE.

Langkah 3 — Estimasi π & Probabilitas

\[\hat{\pi} = 4 \times \frac{\text{titik dalam lingkaran}}{n}\]

Probabilitas sub-kotak teoritis: \[P = \frac{0.5 \times 0.5}{2 \times 2} = \frac{0.25}{4} = 0.0625\]

set.seed(2024)

monte_carlo_pi <- function(n_points) {
  x <- runif(n_points, -1, 1)
  y <- runif(n_points, -1, 1)

  dalam_lingkaran <- logical(n_points)
  dalam_subkotak  <- logical(n_points)

  # Loop untuk cek setiap titik
  for (i in 1:n_points) {
    dalam_lingkaran[i] <- (x[i]^2 + y[i]^2) <= 1
    dalam_subkotak[i]  <- (x[i] >= 0 & x[i] <= 0.5 &
                            y[i] >= 0 & y[i] <= 0.5)
  }

  list(
    pi_estimasi   = round(4 * sum(dalam_lingkaran) / n_points, 6),
    prob_subkotak = round(sum(dalam_subkotak) / n_points, 4),
    df_plot       = data.frame(
      x      = x, y = y,
      status = ifelse(dalam_lingkaran, "Dalam Lingkaran", "Luar Lingkaran")
    )
  )
}

hasil_mc <- monte_carlo_pi(5000)
cat("=== Hasil Monte Carlo Simulation (n = 5.000 titik) ===\n")
## === Hasil Monte Carlo Simulation (n = 5.000 titik) ===
cat("Estimasi π         :", hasil_mc$pi_estimasi, "\n")
## Estimasi π         : 3.176
cat("Nilai π sebenarnya :", round(pi, 6), "\n")
## Nilai π sebenarnya : 3.141593
cat("Selisih            :", round(abs(pi - hasil_mc$pi_estimasi), 6), "\n")
## Selisih            : 0.034407
cat("Prob. sub-kotak    :", hasil_mc$prob_subkotak,
    "(teoritis: 0.0625)\n")
## Prob. sub-kotak    : 0.0626 (teoritis: 0.0625)
# Ambil 2000 titik untuk plot agar tidak terlalu berat
df_plot <- hasil_mc$df_plot[sample(nrow(hasil_mc$df_plot), 2000), ]

fig_mc <- plot_ly(df_plot, x = ~x, y = ~y, color = ~status,
                  colors = c("Dalam Lingkaran" = "#4CAF50",
                             "Luar Lingkaran"  = "#EF5350"),
                  type = "scatter", mode = "markers",
                  marker = list(size = 3, opacity = 0.5),
                  text  = ~paste0("<b>Status:</b> ", status,
                                  "<br>x = ", round(x, 3),
                                  "<br>y = ", round(y, 3)),
                  hoverinfo = "text") %>%
  layout(
    title  = list(
      text = paste0("<b>Monte Carlo — Estimasi π ≈ ",
                    hasil_mc$pi_estimasi, "</b>"),
      x = 0.5, font = list(size = 14)),
    xaxis  = list(title = "<b>x</b>", range = c(-1.1, 1.1), dtick = 0.5,
                  zeroline = TRUE, zerolinecolor = "#888"),
    yaxis  = list(title = "<b>y</b>", range = c(-1.1, 1.1), dtick = 0.5,
                  zeroline = TRUE, zerolinecolor = "#888",
                  scaleanchor = "x", scaleratio = 1),
    legend = list(title  = list(text = "<b>Status Titik</b>"),
                  orientation = "h", x = 0.5, xanchor = "center", y = -0.18),
    annotations = list(
      list(x = 0.25, y = 0.55, text = "<b>Sub-kotak</b>",
           showarrow = FALSE, font = list(size = 12, color = "#7B1FA2")),
      list(x = 0.25, y = -1.0,
           text = paste0("n = 5.000 | π ≈ ", hasil_mc$pi_estimasi,
                         " | Prob sub-kotak = ", hasil_mc$prob_subkotak),
           showarrow = FALSE, font = list(size = 11, color = "#555"))
    ),
    shapes = list(
      # Lingkaran
      list(type = "circle", x0 = -1, y0 = -1, x1 = 1, y1 = 1,
           line = list(color = "#1565C0", width = 2.5)),
      # Sub-kotak
      list(type = "rect", x0 = 0, y0 = 0, x1 = 0.5, y1 = 0.5,
           line = list(color = "#7B1FA2", width = 2, dash = "dash"))
    ),
    plot_bgcolor  = "white",
    paper_bgcolor = "white",
    margin = list(l = 60, r = 40, t = 60, b = 90)
  )

fig_mc

6.2.1 Interpretasi

Dengan 5.000 titik acak, estimasi nilai π yang dihasilkan sangat mendekati nilai sebenarnya (3.14159…). Titik hijau mewakili titik yang jatuh di dalam lingkaran, sedangkan titik merah berada di luar. Semakin banyak titik yang digunakan, semakin akurat estimasi π-nya — ini adalah prinsip utama metode Monte Carlo. Sub-kotak berwarna ungu menunjukkan area [0, 0.5] × [0, 0.5] dengan probabilitas teoritis 0.0625, dan hasil simulasi mendekati nilai tersebut dengan cukup baik.


7 Soal 6 — Advanced Data Transformation & Feature Engineering


7.1 Penjelasan

Pada soal ini kita membangun dua fungsi transformasi data: normalize_columns(df) untuk normalisasi min-max yang mengubah semua nilai numerik ke rentang [0, 1], dan z_score(df) untuk standardisasi yang menghasilkan distribusi dengan rata-rata 0 dan standar deviasi 1. Selain itu, kita menambahkan dua fitur baru pada dataset: performance_category untuk mengklasifikasikan level kinerja, dan salary_bracket untuk mengelompokkan karyawan berdasarkan rentang gaji. Distribusi data sebelum dan sesudah transformasi kemudian dibandingkan secara visual.

# Fungsi normalisasi min-max (loop-based)
normalize_columns <- function(df) {
  df_norm  <- df
  num_cols <- sapply(df, is.numeric)
  for (col in names(df)[num_cols]) {
    mn <- min(df[[col]], na.rm = TRUE)
    mx <- max(df[[col]], na.rm = TRUE)
    df_norm[[col]] <- round((df[[col]] - mn) / (mx - mn), 4)
  }
  return(df_norm)
}

# Fungsi z-score standardisasi (loop-based)
z_score <- function(df) {
  df_z     <- df
  num_cols <- sapply(df, is.numeric)
  for (col in names(df)[num_cols]) {
    m <- mean(df[[col]], na.rm = TRUE)
    s <- sd(df[[col]],   na.rm = TRUE)
    df_z[[col]] <- round((df[[col]] - m) / s, 4)
  }
  return(df_z)
}

# Terapkan ke kolom numerik df_company
df_num  <- df_company[, c("salary", "performance_score", "KPI_score")]
df_norm <- normalize_columns(df_num)
df_z    <- z_score(df_num)

# Tambah fitur baru
df_company$performance_category <- categorize_performance(df_company$performance_score)
df_company$salary_bracket <- cut(
  df_company$salary,
  breaks = c(0, 6000, 9000, 12000, Inf),
  labels = c("Rendah (<6K)","Sedang (6K-9K)","Tinggi (9K-12K)","Sangat Tinggi (>12K)")
)

cat("=== Perbandingan Statistik Salary ===\n")
## === Perbandingan Statistik Salary ===
cat("Sebelum   → Min:", min(df_num$salary), "| Max:", max(df_num$salary),
    "| Mean:", round(mean(df_num$salary), 0), "\n")
## Sebelum   → Min: 4014 | Max: 14804 | Mean: 9483
cat("Min-Max   → Min:", min(df_norm$salary), "| Max:", max(df_norm$salary),
    "| Mean:", round(mean(df_norm$salary), 4), "\n")
## Min-Max   → Min: 0 | Max: 1 | Mean: 0.5069
cat("Z-Score   → Min:", round(min(df_z$salary), 3),
    "| Max:", round(max(df_z$salary), 3),
    "| Mean:", round(mean(df_z$salary), 6), "\n")
## Z-Score   → Min: -1.647 | Max: 1.602 | Mean: 2e-06
library(reshape2)

# Siapkan data gabungan untuk 3 kondisi
df_plot6 <- data.frame(
  Nilai   = c(df_num$salary, df_norm$salary, df_z$salary),
  Kondisi = rep(c("Sebelum (Asli)", "Min-Max [0,1]", "Z-Score [μ=0]"),
                each = nrow(df_num))
)

fig6 <- plot_ly(df_plot6, x = ~Nilai, color = ~Kondisi,
                type = "histogram",
                nbinsx = 20,
                opacity = 0.8,
                colors  = c("Sebelum (Asli)" = "#2196F3",
                             "Min-Max [0,1]"  = "#4CAF50",
                             "Z-Score [μ=0]"  = "#FF9800")) %>%
  layout(
    barmode = "overlay",
    title   = list(text = "<b>Distribusi Salary: Sebelum vs Sesudah Transformasi</b>",
                   x = 0.5, font = list(size = 14)),
    xaxis   = list(title = "<b>Nilai</b>"),
    yaxis   = list(title = "<b>Frekuensi</b>"),
    legend  = list(title = list(text = "<b>Kondisi</b>"),
                   orientation = "h", x = 0.5, xanchor = "center", y = -0.18),
    plot_bgcolor  = "white",
    paper_bgcolor = "white",
    margin = list(l = 60, r = 40, t = 70, b = 90)
  )

fig6
# Boxplot KPI berdasarkan salary bracket
fig6b <- plot_ly(df_company, x = ~salary_bracket, y = ~KPI_score,
                 type   = "box",
                 color  = ~salary_bracket,
                 colors = c("#42A5F5","#66BB6A","#FF9800","#EF5350"),
                 text   = ~paste0("<b>Bracket:</b> ", salary_bracket,
                                  "<br><b>KPI:</b> ", KPI_score),
                 hoverinfo = "text") %>%
  layout(
    title  = list(text = "<b>Distribusi KPI Score berdasarkan Salary Bracket</b>",
                  x = 0.5, font = list(size = 14)),
    xaxis  = list(title = "<b>Salary Bracket</b>"),
    yaxis  = list(title = "<b>KPI Score</b>"),
    legend = list(title = list(text = "<b>Bracket</b>")),
    plot_bgcolor  = "white",
    paper_bgcolor = "white",
    margin = list(l = 60, r = 40, t = 70, b = 80),
    showlegend = FALSE
  )

fig6b

7.1.1 Interpretasi

Histogram perbandingan memperlihatkan bahwa ketiga kondisi (asli, min-max, z-score) memiliki bentuk distribusi yang identik — yang berubah hanyalah skala nilainya. Min-max mengompresi semua nilai ke [0, 1], sementara z-score memusatkan data di sekitar 0. Boxplot KPI berdasarkan salary bracket membantu menjawab pertanyaan: apakah karyawan bergaji lebih tinggi cenderung memiliki KPI lebih baik? Jika median kotak bergerak naik seiring naiknya bracket gaji, maka ada korelasi positif yang patut diperhatikan manajemen dalam kebijakan kompensasi.


8 Soal 7 — Mini Project: Company KPI Dashboard


8.1 Penjelasan

Mini project ini mengintegrasikan semua konsep yang telah dipelajari ke dalam sebuah dashboard analitik yang komprehensif. Dataset berskala besar dibuat untuk 6 perusahaan dengan 60 karyawan masing-masing. Loop digunakan untuk mengkategorikan setiap karyawan ke dalam KPI tier (Bronze, Silver, Gold, Platinum). Kemudian serangkaian visualisasi canggih — grouped bar chart, scatter plot dengan regression line, dan analisis departemen — dibangun untuk mendukung pengambilan keputusan berbasis data.

set.seed(777)
df_dash <- generate_company_data(n_company = 6, n_employees = 60)

# Kategorisasi KPI tier dengan loop
kpi_tier <- character(nrow(df_dash))
for (i in 1:nrow(df_dash)) {
  kpi <- df_dash$KPI_score[i]
  if      (kpi >= 90) kpi_tier[i] <- "Platinum"
  else if (kpi >= 75) kpi_tier[i] <- "Gold"
  else if (kpi >= 60) kpi_tier[i] <- "Silver"
  else                kpi_tier[i] <- "Bronze"
}
df_dash$KPI_tier <- factor(kpi_tier,
                            levels = c("Bronze","Silver","Gold","Platinum"))

# Summary per perusahaan
summary_dash <- df_dash %>%
  group_by(company_id) %>%
  summarise(
    Avg_Salary     = round(mean(salary), 0),
    Avg_KPI        = round(mean(KPI_score), 2),
    Top_Performers = sum(top_performer == "Ya"),
    Total          = n(),
    .groups = "drop"
  ) %>%
  mutate(Pct_Top = paste0(round(Top_Performers / Total * 100, 1), "%"))

kable(summary_dash, caption = "Dashboard Summary — 6 Perusahaan × 60 Karyawan")
Dashboard Summary — 6 Perusahaan × 60 Karyawan
company_id Avg_Salary Avg_KPI Top_Performers Total Pct_Top
Perusahaan-1 9567 76.17 16 60 26.7%
Perusahaan-2 9726 72.45 8 60 13.3%
Perusahaan-3 9098 73.28 10 60 16.7%
Perusahaan-4 9927 75.09 13 60 21.7%
Perusahaan-5 8663 76.95 14 60 23.3%
Perusahaan-6 9642 77.19 12 60 20%
# Hitung distribusi KPI tier per perusahaan
tier_df <- df_dash %>%
  group_by(company_id, KPI_tier) %>%
  summarise(jumlah = n(), .groups = "drop")

warna_tier <- c("Bronze" = "#CD7F32","Silver" = "#A8A9AD",
                "Gold"   = "#FFD700","Platinum" = "#42A5F5")

fig7a <- plot_ly()
for (tier in c("Bronze","Silver","Gold","Platinum")) {
  d <- tier_df[tier_df$KPI_tier == tier, ]
  fig7a <- fig7a %>%
    add_trace(x = d$company_id, y = d$jumlah,
              type = "bar", name = tier,
              marker = list(color = warna_tier[tier]),
              text  = d$jumlah, textposition = "inside",
              hovertext = paste0("<b>", d$company_id, "</b>",
                                 "<br>Tier: ", tier,
                                 "<br>Jumlah: ", d$jumlah, " orang"),
              hoverinfo = "text")
}

fig7a %>% layout(
  barmode = "group",
  title   = list(text = "<b>Distribusi KPI Tier per Perusahaan</b>",
                 x = 0.5, font = list(size = 14)),
  xaxis   = list(title = ""),
  yaxis   = list(title = "<b>Jumlah Karyawan</b>"),
  legend  = list(title = list(text = "<b>KPI Tier</b>"),
                 orientation = "h", x = 0.5, xanchor = "center", y = -0.18),
  plot_bgcolor  = "white",
  paper_bgcolor = "white",
  margin = list(l = 60, r = 40, t = 70, b = 90)
)
# Scatter plot: salary vs KPI dengan regression line per perusahaan
warna_co6 <- c("#2196F3","#4CAF50","#FF9800","#E91E63","#9C27B0","#00BCD4")
perusahaan_list <- unique(df_dash$company_id)

fig7b <- plot_ly()
for (i in seq_along(perusahaan_list)) {
  co   <- perusahaan_list[i]
  d    <- df_dash[df_dash$company_id == co, ]
  fit  <- lm(KPI_score ~ salary, data = d)
  x_rng <- seq(min(d$salary), max(d$salary), length.out = 50)
  y_pred <- predict(fit, newdata = data.frame(salary = x_rng))

  # Titik scatter
  fig7b <- fig7b %>%
    add_trace(x = d$salary, y = d$KPI_score,
              type = "scatter", mode = "markers",
              name = co, legendgroup = co,
              marker = list(color = warna_co6[i], size = 6, opacity = 0.7),
              text  = paste0("<b>", co, "</b>",
                             "<br>Salary: Rp ", format(d$salary, big.mark = ","),
                             "<br>KPI: ", d$KPI_score),
              hoverinfo = "text", showlegend = TRUE)

  # Garis regresi
  fig7b <- fig7b %>%
    add_trace(x = x_rng, y = y_pred,
              type = "scatter", mode = "lines",
              name = paste0(co, " (regresi)"), legendgroup = co,
              line = list(color = warna_co6[i], width = 2, dash = "dash"),
              hoverinfo = "none", showlegend = FALSE)
}

fig7b %>% layout(
  title  = list(text = "<b>Salary vs KPI Score per Perusahaan (+ Regression Line)</b>",
                x = 0.5, font = list(size = 14)),
  xaxis  = list(title = "<b>Salary (Rp)</b>"),
  yaxis  = list(title = "<b>KPI Score</b>"),
  legend = list(title = list(text = "<b>Perusahaan</b>"),
                orientation = "h", x = 0.5, xanchor = "center", y = -0.2),
  hovermode     = "closest",
  plot_bgcolor  = "white",
  paper_bgcolor = "white",
  margin = list(l = 60, r = 40, t = 70, b = 100)
)
# Analisis departemen: rata-rata salary
dept_sum <- df_dash %>%
  group_by(department) %>%
  summarise(Avg_Salary = round(mean(salary), 0),
            Jumlah     = n(), .groups = "drop") %>%
  arrange(desc(Avg_Salary))

fig7c <- plot_ly(dept_sum,
                 x = ~reorder(department, -Avg_Salary),
                 y = ~Avg_Salary,
                 type = "bar",
                 marker = list(color = "#3F51B5",
                               line  = list(color = "white", width = 1.5)),
                 text     = ~paste0("Rp ", format(Avg_Salary, big.mark = ","),
                                    "\n(", Jumlah, " org)"),
                 textposition = "outside",
                 hovertext = ~paste0("<b>", department, "</b>",
                                     "<br>Avg Salary: Rp ",
                                     format(Avg_Salary, big.mark = ","),
                                     "<br>Jumlah: ", Jumlah, " karyawan"),
                 hoverinfo = "text") %>%
  layout(
    title  = list(text = "<b>Rata-rata Salary per Departemen (Semua Perusahaan)</b>",
                  x = 0.5, font = list(size = 14)),
    xaxis  = list(title = "<b>Departemen</b>"),
    yaxis  = list(title = "<b>Avg Salary (Rp)</b>"),
    plot_bgcolor  = "white",
    paper_bgcolor = "white",
    showlegend = FALSE,
    margin = list(l = 60, r = 40, t = 70, b = 60)
  )

fig7c

8.1.1 Interpretasi

Dashboard ini menyajikan analisis tiga lapis untuk 6 perusahaan dengan 360 karyawan total. Grouped bar chart memperlihatkan komposisi KPI tier per perusahaan — semakin banyak Platinum dan Gold, semakin kuat kualitas SDM-nya. Scatter plot dengan regression line menunjukkan arah hubungan antara gaji dan KPI di masing-masing perusahaan: jika garis regresi cenderung naik, berarti karyawan bergaji lebih tinggi umumnya memiliki KPI lebih baik. Analisis departemen membantu manajemen mengidentifikasi divisi mana yang memiliki rata-rata gaji tertinggi dan terendah, yang berguna untuk evaluasi struktur kompensasi secara menyeluruh.


9 Soal 8 — Automated Report Generation (Bonus)


9.1 Penjelasan

Soal bonus ini meminta kita membangun sistem pelaporan otomatis menggunakan kombinasi fungsi dan loop. Fungsi generate_report(df, co_id) mengekstrak semua informasi penting dari satu perusahaan, kemudian sebuah loop mengeksekusinya untuk seluruh perusahaan dalam dataset secara otomatis. Pendekatan ini mencerminkan workflow nyata di dunia industri, di mana laporan harus dibuat secara rutin untuk puluhan atau ratusan entitas sekaligus tanpa intervensi manual. Laporan akhir dapat diekspor ke CSV untuk keperluan lebih lanjut.

# Fungsi generate report untuk satu perusahaan
generate_report <- function(df, co_id) {
  d <- df[df$company_id == co_id, ]
  list(
    company        = co_id,
    total_karyawan = nrow(d),
    avg_salary     = round(mean(d$salary), 0),
    avg_kpi        = round(mean(d$KPI_score), 2),
    avg_perf       = round(mean(d$performance_score), 2),
    top_performers = sum(d$top_performer == "Ya"),
    dept_terbanyak = names(sort(table(d$department), decreasing = TRUE))[1],
    kpi_max        = max(d$KPI_score),
    kpi_min        = min(d$KPI_score)
  )
}

# Loop otomatis untuk semua perusahaan
co_list      <- unique(df_dash$company_id)
semua_laporan <- list()

for (co in co_list) {
  semua_laporan[[co]] <- generate_report(df_dash, co)
}

# Gabungkan ke satu data frame
df_laporan <- do.call(rbind, lapply(semua_laporan, function(x) {
  data.frame(
    Perusahaan     = x$company,
    Total_Karyawan = x$total_karyawan,
    Avg_Salary     = x$avg_salary,
    Avg_KPI        = x$avg_kpi,
    Top_Performers = x$top_performers,
    Dept_Terbanyak = x$dept_terbanyak,
    KPI_Max        = x$kpi_max,
    KPI_Min        = x$kpi_min,
    stringsAsFactors = FALSE
  )
}))

kable(df_laporan, caption = "Laporan Otomatis — 6 Perusahaan (Auto-Generated)")
Laporan Otomatis — 6 Perusahaan (Auto-Generated)
Perusahaan Total_Karyawan Avg_Salary Avg_KPI Top_Performers Dept_Terbanyak KPI_Max KPI_Min
Perusahaan-1 Perusahaan-1 60 9567 76.17 16 Operations 99.8 50.5
Perusahaan-2 Perusahaan-2 60 9726 72.45 8 HR 99.2 50.6
Perusahaan-3 Perusahaan-3 60 9098 73.28 10 Operations 99.6 50.4
Perusahaan-4 Perusahaan-4 60 9927 75.09 13 Marketing 98.0 50.8
Perusahaan-5 Perusahaan-5 60 8663 76.95 14 HR 98.5 50.4
Perusahaan-6 Perusahaan-6 60 9642 77.19 12 HR 99.8 51.6
# Visualisasi KPI Max vs Min vs Average per perusahaan
df_kpi_vis <- df_laporan[, c("Perusahaan","KPI_Max","KPI_Min","Avg_KPI")]

fig8 <- plot_ly()

# Area antara min dan max (shading)
for (i in 1:nrow(df_kpi_vis)) {
  fig8 <- fig8 %>%
    add_trace(x = c(df_kpi_vis$Perusahaan[i], df_kpi_vis$Perusahaan[i]),
              y = c(df_kpi_vis$KPI_Min[i], df_kpi_vis$KPI_Max[i]),
              type = "scatter", mode = "lines",
              line = list(color = "#B0BEC5", width = 8),
              hoverinfo = "none", showlegend = FALSE)
}

# KPI Max
fig8 <- fig8 %>%
  add_trace(x = df_kpi_vis$Perusahaan, y = df_kpi_vis$KPI_Max,
            type = "scatter", mode = "lines+markers",
            name = "KPI Tertinggi",
            line   = list(color = "#4CAF50", width = 2.5),
            marker = list(color = "#4CAF50", size = 10, symbol = "triangle-up"),
            text   = paste0("<b>KPI Tertinggi</b>: ", df_kpi_vis$KPI_Max),
            hoverinfo = "text")

# KPI Min
fig8 <- fig8 %>%
  add_trace(x = df_kpi_vis$Perusahaan, y = df_kpi_vis$KPI_Min,
            type = "scatter", mode = "lines+markers",
            name = "KPI Terendah",
            line   = list(color = "#EF5350", width = 2.5),
            marker = list(color = "#EF5350", size = 10, symbol = "triangle-down"),
            text   = paste0("<b>KPI Terendah</b>: ", df_kpi_vis$KPI_Min),
            hoverinfo = "text")

# KPI Average
fig8 <- fig8 %>%
  add_trace(x = df_kpi_vis$Perusahaan, y = df_kpi_vis$Avg_KPI,
            type = "scatter", mode = "lines+markers",
            name = "KPI Rata-rata",
            line   = list(color = "#2196F3", width = 2.5, dash = "dot"),
            marker = list(color = "#2196F3", size = 9, symbol = "diamond"),
            text   = paste0("<b>KPI Rata-rata</b>: ", df_kpi_vis$Avg_KPI),
            hoverinfo = "text")

fig8 %>% layout(
  title  = list(text = "<b>Rentang KPI Score per Perusahaan — Laporan Otomatis</b>",
                x = 0.5, font = list(size = 14)),
  xaxis  = list(title = "<b>Perusahaan</b>"),
  yaxis  = list(title = "<b>KPI Score</b>", range = c(45, 105)),
  legend = list(title = list(text = "<b>Metrik</b>"),
                orientation = "h", x = 0.5, xanchor = "center", y = -0.18),
  hovermode     = "closest",
  plot_bgcolor  = "white",
  paper_bgcolor = "white",
  margin = list(l = 60, r = 40, t = 70, b = 90)
)
# Ekspor laporan ke CSV
write.csv(df_laporan, "laporan_otomatis_perusahaan.csv", row.names = FALSE)

cat("✅ File 'laporan_otomatis_perusahaan.csv' berhasil diekspor!\n\n")
## ✅ File 'laporan_otomatis_perusahaan.csv' berhasil diekspor!
cat("=== Ringkasan Eksekusi Automated Report ===\n")
## === Ringkasan Eksekusi Automated Report ===
cat("Total perusahaan diproses  :", nrow(df_laporan), "\n")
## Total perusahaan diproses  : 6
cat("Total karyawan seluruh co. :", sum(df_laporan$Total_Karyawan), "\n")
## Total karyawan seluruh co. : 360
cat("Rata-rata top performer/co.:", round(mean(df_laporan$Top_Performers), 1), "orang\n")
## Rata-rata top performer/co.: 12.2 orang
cat("Avg KPI tertinggi (co.)    :", df_laporan$Perusahaan[which.max(df_laporan$Avg_KPI)],
    "→", max(df_laporan$Avg_KPI), "\n")
## Avg KPI tertinggi (co.)    : Perusahaan-6 → 77.19
cat("Avg KPI terendah (co.)     :", df_laporan$Perusahaan[which.min(df_laporan$Avg_KPI)],
    "→", min(df_laporan$Avg_KPI), "\n")
## Avg KPI terendah (co.)     : Perusahaan-2 → 72.45

9.1.1 Interpretasi

Fungsi generate_report berhasil mengotomasi proses pelaporan — cukup satu fungsi yang dipanggil dalam satu loop, dan laporan untuk seluruh 6 perusahaan langsung tersedia tanpa perlu menulis kode berulang. Visualisasi KPI range memperlihatkan kesenjangan antara karyawan terbaik dan terburuk di setiap perusahaan: semakin sempit rentang antara segitiga hijau (KPI max) dan merah (KPI min), semakin merata kualitas SDM perusahaan tersebut. Laporan CSV yang diekspor dapat langsung dibuka di Excel atau diolah lebih lanjut menggunakan tools BI seperti Tableau atau Power BI.


10 Kesimpulan

Rangkuman Konsep yang Dikuasai

Dari 8 soal advanced practicum ini, kita telah berhasil mendemonstrasikan penguasaan konsep pemrograman data science secara menyeluruh:

  1. Dynamic Multi-Formula — Validasi input dan komputasi multi-formula dalam satu fungsi modular.
  2. Nested Simulation — Nested loop dengan diskon kondisional dan akumulasi penjualan harian.
  3. Performance Categorization — Loop berbasis vektor untuk klasifikasi 5 level kinerja beserta visualisasi distribusi.
  4. Multi-Company Simulation — Nested loop antar perusahaan dan karyawan dengan penandaan top performer.
  5. Monte Carlo — Estimasi π dan probabilitas menggunakan ribuan titik acak dengan loop.
  6. Data Transformation — Normalisasi dan standardisasi berbasis loop, dilengkapi feature engineering.
  7. KPI Dashboard — Analisis komprehensif multi-perusahaan dengan visualisasi lanjutan dan regression line.
  8. Automated Report — Otomasi laporan per perusahaan menggunakan fungsi dan loop, dengan ekspor CSV.

Pelajaran Utama

Kombinasi fungsi, loop, dan logika kondisional terbukti menjadi fondasi yang sangat kuat dalam membangun workflow data science yang efisien. Kode yang modular — artinya setiap tugas dikerjakan oleh fungsinya sendiri — membuat program lebih mudah dipahami, diuji, dan dikembangkan di masa mendatang. Inilah prinsip yang diterapkan oleh para data scientist profesional dalam proyek nyata sehari-hari.


11 Daftar Referensi