Analisis Eksploratif dan Visualisasi Data Transaksi Penjualan pada Dataset Online Retail

Studi Kasus

Analisis Transaksi Penjualan Toko Online “NapMart”

NapMart adalah sebuah toko online yang menjual berbagai produk kebutuhan rumah tangga dan dekorasi. Dalam beberapa waktu terakhir, jumlah transaksi di toko ini terus meningkat. Setiap harinya, ratusan hingga ribuan transaksi tercatat, mulai dari pembelian dalam jumlah kecil hingga transaksi besar dalam jumlah banyak. Meskipun data transaksi tersebut terus bertambah, pihak manajemen NapMart mulai menyadari bahwa mereka belum benar-benar memahami pola di balik data tersebut. Mereka tidak mengetahui produk apa yang sebenarnya paling diminati pelanggan, kapan waktu penjualan paling tinggi, serta bagaimana karakteristik pelanggan yang memberikan kontribusi terbesar terhadap pendapatan. Selama ini, keputusan bisnis lebih banyak didasarkan pada perkiraan dan pengalaman, bukan berdasarkan analisis data yang mendalam. Akibatnya, sering terjadi ketidaksesuaian dalam pengelolaan stok, strategi promosi yang kurang tepat sasaran, serta kurang optimalnya pemanfaatan peluang pasar. Berangkat dari permasalahan tersebut, analisis terhadap data transaksi penjualan yang telah dikumpulkan menjadi langkah yang perlu dilakukan. Data ini diharapkan dapat memberikan gambaran yang lebih jelas mengenai perilaku pelanggan dan pola penjualan yang terjadi. Melalui pendekatan eksploratif dan bantuan visualisasi data, setiap informasi yang tersembunyi di dalam dataset akan diungkap secara bertahap. Hasil dari analisis ini nantinya diharapkan dapat membantu NapMart dalam memahami bisnisnya secara lebih mendalam dan mengambil keputusan yang lebih tepat berbasis data.

Berdasarkan permasalahan yang dihadapi oleh manajemen NapMart, analisis eksploratif dan visualisasi data ini memiliki tujuan utama sebagai berikut: 1. Memetakan Pasar dan Produk Unggulan: Mengidentifikasi distribusi geografis pelanggan dan menemukan produk-produk utama yang menjadi tulang punggung penjualan. 2. Menganalisis Strategi Harga: Mengetahui pola sebaran harga produk yang paling sering dibeli oleh pelanggan untuk menentukan strategi promosi yang tepat. 3. Mengidentifikasi Pelanggan Paling Berharga: Melakukan segmentasi pelanggan berdasarkan frekuensi belanja dan total uang yang dihabiskan untuk menemukan kelompok pelanggan (VIP) yang berkontribusi terbesar. 4. Mendeteksi Tren dan Momentum Penjualan: Mengamati pergerakan transaksi dari waktu ke waktu (berdasarkan bulan dan tipe hari) untuk mengetahui kapan puncak penjualan terjadi dan kapan waktu terbaik untuk melakukan promosi.

library(tidyverse)
library(lubridate)
library(ggplot2)
library(writexl)
library(plotly)
library(RColorBrewer)
library(DT)
library(forecast)
library(dplyr)
library(scales)
library(zoo)
library(janitor)
library(readxl)

Import Data

Nah sebelum memulai analisis kita, disini kami meng import dataset yang telah dibersihkan, dan memeriksa karakteristik variabel yang ada di dalam dataset.

## # A tibble: 6 × 9
##   invoice_no stock_code description      invoice_date        customer_id country
##   <chr>      <chr>      <chr>            <dttm>              <chr>       <chr>  
## 1 536365     21730      GLASS STAR FROS… 2010-12-01 08:26:00 17850       United…
## 2 536365     22752      SET 7 BABUSHKA … 2010-12-01 08:26:00 17850       United…
## 3 536365     71053      WHITE METAL LAN… 2010-12-01 08:26:00 17850       United…
## 4 536365     84029E     RED WOOLLY HOTT… 2010-12-01 08:26:00 17850       United…
## 5 536365     84029G     KNITTED UNION F… 2010-12-01 08:26:00 17850       United…
## 6 536365     84406B     CREAM CUPID HEA… 2010-12-01 08:26:00 17850       United…
## # ℹ 3 more variables: quantity <dbl>, unit_price <dbl>, Revenue <dbl>
## tibble [519,611 × 9] (S3: tbl_df/tbl/data.frame)
##  $ invoice_no  : chr [1:519611] "536365" "536365" "536365" "536365" ...
##  $ stock_code  : chr [1:519611] "21730" "22752" "71053" "84029E" ...
##  $ description : chr [1:519611] "GLASS STAR FROSTED T-LIGHT HOLDER" "SET 7 BABUSHKA NESTING BOXES" "WHITE METAL LANTERN" "RED WOOLLY HOTTIE WHITE HEART." ...
##  $ invoice_date: POSIXct[1:519611], format: "2010-12-01 08:26:00" "2010-12-01 08:26:00" ...
##  $ customer_id : chr [1:519611] "17850" "17850" "17850" "17850" ...
##  $ country     : chr [1:519611] "United Kingdom" "United Kingdom" "United Kingdom" "United Kingdom" ...
##  $ quantity    : num [1:519611] 6 2 6 6 6 8 6 6 6 3 ...
##  $ unit_price  : num [1:519611] 4.25 7.65 3.39 3.39 3.39 2.75 2.55 1.85 1.85 5.95 ...
##  $ Revenue     : num [1:519611] 25.5 15.3 20.3 20.3 20.3 ...

Nah disini kita melakukan pemeriksaan ulang apakah masih terdapat missing value, data duplikat, dan melihat statistik deskriptif dataset ini.

##   invoice_no   stock_code  description invoice_date  customer_id      country 
##            0            0            0            0            0            0 
##     quantity   unit_price      Revenue 
##            0            0            0
## [1] 0
summary(data)
##   invoice_no         stock_code        description       
##  Length:519611      Length:519611      Length:519611     
##  Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character  
##                                                          
##                                                          
##                                                          
##   invoice_date                    customer_id          country         
##  Min.   :2010-12-01 08:26:00.00   Length:519611      Length:519611     
##  1st Qu.:2011-03-28 11:34:00.00   Class :character   Class :character  
##  Median :2011-07-19 14:03:00.00   Mode  :character   Mode  :character  
##  Mean   :2011-07-04 08:55:54.34                                        
##  3rd Qu.:2011-10-18 17:05:00.00                                        
##  Max.   :2011-12-09 12:50:00.00                                        
##     quantity          unit_price           Revenue         
##  Min.   :    1.00   Min.   :    0.001   Min.   :     0.00  
##  1st Qu.:    1.00   1st Qu.:    1.250   1st Qu.:     4.13  
##  Median :    4.00   Median :    2.100   Median :     9.96  
##  Mean   :   10.75   Mean   :    3.922   Mean   :    20.53  
##  3rd Qu.:   12.00   3rd Qu.:    4.130   3rd Qu.:    17.70  
##  Max.   :80995.00   Max.   :13541.330   Max.   :168469.60

Disini kita melakukan visualisasi dengan boxplot, guna melihat nilai pada kuantitas dan harga unit

ggplot(data, aes(y = quantity)) +
  geom_boxplot() +
  labs(title = "Boxplot Quantity")

ggplot(data, aes(y = unit_price)) +
  geom_boxplot() +
  labs(title = "Boxplot Price")

Disini kami melakukan visualisasi dengan boxplot, guna melihat nilai pada kuantitas dan harga unit.

Boxplot quantity menunjukkan adanya nilai-nilai yang jauh dari kelompok utama data, baik pada sisi positif maupun negatif. Nilai positif yang sangat besar menunjukkan adanya pembelian dalam jumlah yang sangat banyak dalam satu transaksi, sementara sebagian besar transaksi berada pada nilai yang relatif kecil sehingga distribusinya tidak simetris. Pada boxplot harga, terlihat bahwa hampir semua data terkonsentrasi di kisaran nilai rendah yang berdekatan dengan nol. Namun terdapat beberapa nilai yang sangat jauh ke atas, menandakan adanya produk dengan harga yang jauh lebih tinggi dari harga produk pada umumnya.

Melihat Pola Distribusi

Setelah mengenal struktur datanya langkah pertama untuk memahami bisnis NapMart adalah melihat lanskap pasar secara makro. Dari mana saja sebenarnya pelanggan toko ini berasal? Dan produk apa yang paling sering mereka beli? Dua pertanyaan sederhana ini menjadi titik awal perjalanan eksplorasi kami.

country_counts <- data %>%
  count(country, sort = TRUE)
top10 <- data %>%
  count(country, sort = TRUE) %>%
  head(10)
top10 <- top10 %>%
  mutate(group = ifelse(country == "United Kingdom", "UK", "Other"))

plot_ly(
  data = top10,
  x = ~n,
  y = ~reorder(country, n),
  type = "bar",
  orientation = "h",
  color = ~group,
  colors = c("UK" = "orange", "Other" = "steelblue"),
  text = ~paste(
    "Negara:", country,
    "<br>Jumlah Transaksi:", n
  ),
  hoverinfo = "text",
  textposition = "none"
) %>%
  layout(
    hoverlabel = list(
      bgcolor = "black",
      font = list(color = "white")
    )
  )

Grafik menunjukkan bahwa United Kingdom memiliki jumlah transaksi yang sangat dominan dibandingkan negara lainnya. Perbedaan yang terlihat sangat signifikan ini menandakan bahwa sebagian besar aktivitas transaksi berasal dari UK. Negara-negara lain memang muncul dalam Top 10, namun kontribusinya relatif kecil jika dibandingkan dengan UK.

top10_no_uk <- top10 %>%
  filter(country != "United Kingdom")

plot_ly(
  data = top10_no_uk,
  x = ~n,
  y = ~reorder(country, n),
  type = "bar",
  orientation = "h",
  marker = list(color = "steelblue"),
  text = ~paste(
    "Negara:", country,
    "<br>Jumlah Transaksi:", n
  ),
  hoverinfo = "text",
  textposition = "none"
) %>%
  layout(
    hoverlabel = list(
      bgcolor = "black",
      font = list(color = "white")
    )
  )

Setelah United Kingdom dikeluarkan dari Grafik, terlihat bahwa distribusi transaksi antar negara menjadi lebih merata. Beberapa negara seperti Germany, France, dan Netherlands muncul sebagai kontributor utama di pasar internasional. Perbedaan antar negara tidak terlalu ekstrem, menunjukkan bahwa tidak ada satu negara lain yang benar-benar mendominasi seperti UK.

top10_products <- data %>%
  filter(quantity > 0, !is.na(description)) %>%
  group_by(description) %>%
  summarise(total_qty = sum(quantity)) %>%
  arrange(desc(total_qty)) %>%
  slice_head(n = 10)

plot_ly(
  data = top10_products,
  x = ~total_qty,
  y = ~reorder(description, total_qty),
  type = "bar",
  orientation = "h",
  text = ~paste(
    "Produk:", description,
    "<br>Total Terjual:", total_qty
  ),
  hoverinfo = "text",
  textposition = "none"
) %>%
  layout(
    hoverlabel = list(
      bgcolor = "black",
      font = list(color = "white")
    )
  )

Grafik menunjukkan bahwa distribusi penjualan tidak merata di seluruh produk. Sebaliknya, terdapat konsentrasi penjualan yang signifikan pada beberapa produk tertentu yang memiliki jumlah pembelian jauh lebih tinggi dibandingkan produk lainnya.

Total penjualan yang dihasilkan oleh 10 produk terlaris juga menunjukkan kontribusi yang dominan terhadap keseluruhan transaksi. Hal ini mengindikasikan bahwa sebagian besar aktivitas pembelian pelanggan berfokus pada produk-produk tertentu yang memiliki tingkat permintaan tinggi.

Pola ini mencerminkan fenomena Pareto Principle (80/20), di mana sebagian kecil produk memberikan kontribusi besar terhadap total penjualan. Dengan kata lain, tidak semua produk memiliki peran yang sama dalam mendorong performa bisnis.

l <- ggplot(data, aes(x = unit_price)) +
  geom_histogram(bins = 40, fill = "skyblue", color = "black") +
  geom_vline(aes(xintercept = mean(unit_price, na.rm = TRUE)),
             color = "red", linetype = "dashed", size = 1) +
  labs(
    title = "Distribusi Harga Dari Produk-Produk yang Dijual",
    x = "Unit Price",
    y = "Frekuensi"
  ) +
  theme_minimal()

ggplotly(l)

Grafik tersebut menunjukkan distribusi Unit Price yang cenderung tidak simetris (right-skewed). Artinya, sebagian besar harga produk berada di kisaran rendah, sementara hanya sedikit produk yang memiliki harga tinggi. Hal ini mengindikasikan bahwa mayoritas produk dijual dengan harga murah sampai menengah, sedangkan produk dengan harga tinggi relatif jarang muncul dalam data.

Pada bagian awal distribusi, terlihat bahwa frekuensi paling tinggi ada di rentang harga sekitar 1 sampai 2. Ini berarti kisaran harga tersebut adalah yang paling sering muncul dalam transaksi. Seiring meningkatnya harga, frekuensi mulai menurun secara bertahap. Dengan kata lain, semakin mahal suatu produk, semakin sedikit jumlah transaksi yang terjadi. Pola ini cukup wajar karena biasanya produk dengan harga terjangkau memang lebih banyak diminati dibandingkan produk premium.

Garis merah putus-putus pada grafik menunjukkan nilai rata-rata (mean) yang berada di sekitar angka 3. Letaknya yang lebih ke kanan dibandingkan puncak distribusi menunjukkan adanya pengaruh dari beberapa nilai ekstrem (outlier) pada harga tinggi. Jadi, meskipun sebagian besar harga berada di bawah 3, adanya beberapa produk mahal membuat nilai rata-rata ikut terdorong ke kanan.

Selain itu, terlihat juga adanya ekor distribusi yang cukup panjang hingga kisaran harga 8–9. Ini menandakan adanya beberapa produk dengan harga sangat tinggi. Walaupun jumlahnya sedikit, nilai-nilai ini tetap berpengaruh terhadap bentuk distribusi secara keseluruhan.

Secara keseluruhan, distribusi Unit Price didominasi oleh harga rendah, dengan sedikit produk mahal yang menyebabkan distribusi menjadi miring ke kanan. Hal ini bisa memberikan gambaran bahwa penjualan lebih banyak terjadi pada produk dengan harga terjangkau, sementara produk mahal cenderung sebagai pelengkap atau untuk segmen tertentu saja. Untuk analisis selanjutnya, bisa dipertimbangkan menggunakan median agar lebih mewakili data, serta melakukan segmentasi produk supaya pola harga bisa dianalisis lebih dalam.

# Agregasi: dari per-transaksi menjadi per-pelanggan
pelanggan <- data %>%
  filter(!is.na(customer_id), customer_id != "") %>%
  group_by(customer_id, country) %>%
  summarise(
    frekuensi_transaksi = n_distinct(invoice_no),
    total_revenue       = sum(Revenue, na.rm = TRUE),
    .groups = "drop"
  )

# Hitung median sebagai garis batas segmentasi
median_frek <- median(pelanggan$frekuensi_transaksi)
median_rev  <- median(pelanggan$total_revenue)

# Bagi pelanggan ke 4 segmen berdasarkan posisi relatif terhadap median
pelanggan <- pelanggan %>%
  mutate(
    segmen = case_when(
      frekuensi_transaksi >= median_frek & total_revenue >= median_rev
        ~ "VIP",
      frekuensi_transaksi <  median_frek & total_revenue >= median_rev
        ~ "Big Spender",
      frekuensi_transaksi >= median_frek & total_revenue <  median_rev
        ~ "Loyal Kecil",
      TRUE
        ~ "Dormant"
    ),
    # Atur urutan tampilan segmen di sumbu X (kiri ke kanan)
    segmen = factor(segmen,
                    levels = c("Dormant", "Big Spender",
                               "Loyal Kecil", "VIP"))
  )

# Ringkasan jumlah pelanggan per segmen
cat("Distribusi pelanggan per segmen:\n")
## Distribusi pelanggan per segmen:
pelanggan %>% count(segmen) %>% print()
## # A tibble: 4 × 2
##   segmen          n
##   <fct>       <int>
## 1 Dormant      1354
## 2 Big Spender   147
## 3 Loyal Kecil   823
## 4 VIP          2031
# Palet warna per segmen
palet_segmen <- c(
  "Dormant"     = "#AAAAAA",
  "Big Spender" = "#E06C2A",
  "Loyal Kecil" = "#27AE60",
  "VIP"         = "#2C7BB6"
)

# Label deskriptif untuk sumbu X
label_segmen <- c(
  "Dormant"     = "Dormant\n(Jarang, Rev Rendah)",
  "Big Spender" = "Big Spender\n(Jarang, Rev Tinggi)",
  "Loyal Kecil" = "Loyal Kecil\n(Sering, Rev Rendah)",
  "VIP"         = "VIP\n(Sering, Rev Tinggi)"
)

# Buat jitter plot yang ditingkatkan
grafik_jitter <- ggplot(pelanggan,
                        aes(x     = segmen,
                            y     = total_revenue,
                            color = segmen)) +

  # Layer 1: Layer boxplot dasar untuk identifikasi outlier visual
  # outlier.color diatur MERAH untuk pembeda, titik jitter utama diturunkan transparansinya
  geom_boxplot(outlier.color = "red", # Pembeda nilai ekstrem (merah)
               outlier.size = 2.5,
               outlier.alpha = 0.8,
               fill = NA,          # Kotak transparan
               color = "grey60",   # Garis kotak abu-abu tipis
               size = 0.4) +

  # Layer 2: Jitter plot (titik pelanggan utama)
  # Transparansi (alpha) diturunkan agar outlier merah lebih menonjol
  geom_jitter(width = 0.22,
              size  = 2,
              alpha = 0.35) + # Dibuat lebih transparan

  # Layer 3: Penanda median yang simple (Task: Penanda median visual)
  stat_summary(fun = median,
               geom = "point",
               shape = 18,      # Bentuk berlian
               size = 5,
               color = "black",  # Bingkai hitam
               fill = "white") + # Isi putih agar kontras

  # Sumbu Y skala logaritmik
  scale_y_log10(labels = label_comma()) +

  # Label sumbu X deskriptif
  scale_x_discrete(labels = label_segmen) +

  # Palet warna per segmen
  scale_color_manual(values = palet_segmen) +

  # Judul dan label
  labs(
    title    = "Distribusi Revenue Berdasarkan Segmentasi Pelanggan",
    subtitle = "Titik Merah: Nilai Ekstrem | Lambang Belah Ketupat : Lokasi Median Segmen\nSetiap titik mewakili satu pelanggan | Sumbu Y: skala logaritmik",
    x        = "Segmen Pelanggan",
    y        = "Total Revenue per Pelanggan (\u00a3, skala log)",
    caption  = "Sumber: Online Retail Dataset — UCI Machine Learning Repository"
  ) +

  theme_minimal(base_size = 12) +
  theme(
    plot.title    = element_text(face = "bold", size = 14),
    plot.subtitle = element_text(color = "grey30", size = 10, lineheight = 1.1),
    plot.caption  = element_text(color = "grey55", size = 9, hjust = 0),
    legend.position  = "none",
    panel.grid.minor = element_blank(),
    axis.text.x      = element_text(size = 10, lineheight = 1.2),
    axis.title.y     = element_text(size = 11)
  )

# Ubah menjadi interaktif
ggplotly(grafik_jitter, tooltip = "text") %>%
  layout(
    showlegend = FALSE,
    margin      = list(t = 90)
  )

Ringkasan Statistik per Segmen

pelanggan %>%
  group_by(segmen) %>%
  summarise(
    jumlah_pelanggan     = n(),
    median_frekuensi     = round(median(frekuensi_transaksi), 1),
    median_revenue       = round(median(total_revenue), 0),
    max_revenue          = round(max(total_revenue), 0),
    total_revenue_segmen = round(sum(total_revenue), 0)
  ) %>%
  arrange(desc(total_revenue_segmen)) %>%
  mutate(
    median_revenue       = paste0("\u00a3", format(median_revenue,
                                                   big.mark = ",")),
    max_revenue          = paste0("\u00a3", format(max_revenue,
                                                   big.mark = ",")),
    total_revenue_segmen = paste0("\u00a3", format(total_revenue_segmen,
                                                   big.mark = ","))
  ) %>%
  knitr::kable(
    col.names = c("Segmen", "Jml Pelanggan", "Median Frekuensi",
                  "Median Revenue", "Revenue Tertinggi",
                  "Total Revenue Segmen"),
    align   = c("l", "c", "c", "c", "c", "c"),
    caption = "Tabel 1. Ringkasan statistik per segmen pelanggan"
  )
Tabel 1. Ringkasan statistik per segmen pelanggan
Segmen Jml Pelanggan Median Frekuensi Median Revenue Revenue Tertinggi Total Revenue Segmen
VIP 2031 5 £1,745 £1,718,253 £9,702,594
Dormant 1354 1 £ 231 £ 672 £ 354,531
Loyal Kecil 823 2 £ 423 £ 673 £ 346,080
Big Spender 147 1 £1,002 £ 77,184 £ 265,196

Jitter plot di atas menampilkan distribusi total pengeluaran per pelanggan yang dibagi ke dalam empat segmen berdasarkan frekuensi transaksi dan total nilainya. Sumbu Y menggunakan skala logaritmik agar perbedaan rentang pengeluaran yang sangat lebar tetap terlihat proporsional. Pada grafik ini, simbol belah ketupat hitam berukuran besar menandakan titik nilai tengah (median) dari setiap segmen. Sementara itu, titik-titik hitam kecil yang menyebar di luar kotak merupakan pencilan, yaitu pelanggan dengan perilaku belanja ekstrem atau pengecualian dari kebiasaan umum kelompoknya.

Segmen VIP mendominasi dan menjadi penyumbang pendapatan paling signifikan. Dengan 2.031 pelanggan, segmen ini menyumbang total pengeluaran mencapai £9.702.594. Posisi belah ketupat hitam mereka berada di level teratas dengan median £1.745, jauh melampaui kelompok lainnya. Hal ini menjawab rumusan masalah bahwa khusus pada segmen VIP, rutinitas kedatangan yang tinggi (median frekuensi 5 kali) memang berbanding lurus dengan besarnya uang yang masuk. Di segmen ini juga terlihat titik hitam pencilan tertinggi di mana satu pelanggan VIP mencatatkan transaksi ekstrem hingga £1.718.253. Pelanggan tunggal ini kemungkinan besar adalah wholesaler yang menunjukkan betapa pentingnya menerapkan pelayanan istimewa (Key Account Management) untuk menjaga loyalitas pelanggan kunci ini.

Big Spender membuktikan bahwa transaksi jarang tetap bisa bernilai besar. Meski menjadi segmen terkecil dengan 147 pelanggan dan median frekuensi hanya 1 kali, median pengeluaran mereka mampu mencapai £1.002. Artinya, dalam sekali kunjungan, kemampuan belanja mereka sangat besar. Fakta ini sekaligus menjawab pertanyaan analisis bahwa pendapatan besar tidak selalu bergantung pada frekuensi transaksi yang sering. Segmen ini harus menjadi prioritas strategi pemasaran kedua setelah VIP. Perusahaan perlu merancang promosi khusus untuk memancing mereka agar berbelanja lebih sering sehingga dapat bertransisi menjadi pelanggan VIP.

Loyal Kecil dan Dormant berada di tingkat kontribusi terendah. Loyal Kecil (823 pelanggan) memiliki frekuensi belanja lumayan rutin (median 2 kali) namun median pengeluarannya hanya £423. Ini menegaskan kembali bahwa rutinitas tinggi tidak akan berdampak besar pada bisnis jika nominal belanjanya kecil. Rekomendasi strategis untuk kelompok ini adalah penawaran paket produk (bundling) untuk meningkatkan nilai keranjang belanja mereka. Sementara itu, Dormant (1.354 pelanggan) memiliki median pengeluaran terendah yakni £231 dan jarang berbelanja. Demi efisiensi, perusahaan sebaiknya tidak menghabiskan terlalu banyak alokasi anggaran pemasaran pada segmen Dormant ini.

data_bulan <- data %>%
  mutate(month = floor_date(invoice_date, "month")) %>%
  group_by(month) %>%
  summarise(total_revenue = sum(Revenue, na.rm = TRUE), .groups = "drop") %>%
  arrange(month)

peak_month <- data_bulan %>%
  filter(total_revenue == max(total_revenue)) %>%
  pull(month)

cat("Peak month (highest revenue):", as.character(peak_month), "\n")
## Peak month (highest revenue): 2011-11-01
cat("Revenue in peak month:", 
    data_bulan$total_revenue[data_bulan$month == peak_month], "\n")
## Revenue in peak month: 1510515
p <- plot_ly(data_bulan, x = ~month, y = ~total_revenue,
             type = 'scatter', mode = 'lines+markers',
             line = list(color = "#FFEC89", width = 2),
             marker = list(color = "#BA3801", size = 6),
             text = ~paste("Month:", format(month, "%b %Y"),
                           "<br>Revenue:", round(total_revenue, 2)),
             hoverinfo = 'text') %>%
  layout(title = "Tren Penjualan per Bulan",
         xaxis = list(title = "Bulan",
                      tickformat = "%b %Y",
                      tickangle = -45),
         yaxis = list(title = "Total Revenue"),
         annotations = list(
           x = peak_month,
           y = data_bulan$total_revenue[data_bulan$month == peak_month],
           text = paste("Peak:", format(peak_month, "%b %Y")),
           showarrow = TRUE,
           arrowhead = 2,
           ax = 0,
           ay = -40
         ))

p

Visualisasi ini menggunakan grafik garis untuk menunjukkan perubahan total revenue setiap bulan selama tahun 2011. Nilai revenue dihitung dari hasil perkalian quantity dan unitprice, sementara invoice_date diolah menjadi data bulanan agar pola waktunya lebih mudah diamati. Grafik ini dibuat untuk melihat bagaimana kinerja bisnis berubah dari waktu ke waktu, apakah ada pertumbuhan yang konsisten, kapan terjadi puncak penjualan, serta apakah ada pola musiman tertentu.

Dari grafik tersebut, terlihat bahwa revenue tidak bergerak secara stabil, tetapi naik turun sepanjang tahun. Di awal tahun, nilainya cenderung menurun dan berada di level yang relatif rendah hingga sekitar Maret–April. Memasuki pertengahan tahun, pergerakannya mulai lebih stabil, meskipun belum menunjukkan kenaikan yang signifikan. Perubahan yang cukup jelas baru terlihat di akhir tahun, di mana revenue mulai meningkat sejak sekitar September dan mencapai titik tertinggi pada November. Setelah itu, terjadi penurunan yang cukup tajam pada bulan Desember.

Secara keseluruhan, bisnis tidak menunjukkan pertumbuhan yang konsisten dari awal hingga akhir tahun. Polanya lebih terlihat seperti tiga tahap: turun di awal, stabil di tengah, lalu meningkat di akhir. Kenaikan yang cukup besar menjelang November kemungkinan dipengaruhi oleh momen tertentu seperti promosi atau peningkatan belanja di akhir tahun. Namun, turunnya revenue di bulan Desember menunjukkan bahwa peningkatan tersebut tidak berlangsung lama, atau bisa juga disebabkan oleh data yang belum lengkap. Jadi, meskipun ada tanda-tanda pola musiman, analisis lebih lanjut dengan data yang lebih panjang tetap diperlukan untuk memastikan hal tersebut.

daily_trans <- data %>%
  mutate(invoice_date = as.Date(invoice_date)) %>%  
  group_by(invoice_date) %>%
  summarise(total_trans = n_distinct(invoice_no), .groups = "drop") %>%
  mutate(
    weekday_name = wday(invoice_date, label = TRUE, abbr = FALSE, week_start = 1),
    is_weekend = ifelse(weekday_name %in% c("Sabtu", "Minggu"), "Weekend", "Weekday")
  ) %>%
  arrange(invoice_date)
# Hitung rolling average 7 hari 
daily_trans <- daily_trans %>%
  mutate(roll_avg = rollmean(total_trans, k = 7, fill = NA, align = "center"))


# Analisis Harian dengan Moving Average
p1 <- plot_ly() %>%
  add_lines(data = daily_trans, x = ~invoice_date, y = ~total_trans,
            color = ~is_weekend, 
            colors = c("Weekday" = "#2c7fb8", "Weekend" = "#f9a65a"),  # warna jelas & ramah colorblind
            line = list(width = 1.5), name = ~is_weekend) %>%
  add_markers(data = daily_trans, x = ~invoice_date, y = ~total_trans,
              color = ~is_weekend, 
              colors = c("Weekday" = "#2c7fb8", "Weekend" = "#f9a65a"),
              marker = list(size = 3), showlegend = FALSE) %>%
  add_lines(data = daily_trans, x = ~invoice_date, y = ~roll_avg,
            line = list(color = "black", width = 2),  # solid line, tidak putus-putus
            name = "7-day moving average", hoverinfo = "text",
            text = ~paste("Date:", format(invoice_date, "%d %b %Y"),
                          "<br>7-day avg:", round(roll_avg, 1))) %>%
  layout(title = "Tren Transaksi Harian (Weekday vs Weekend)",
         xaxis = list(title = "Tanggal", tickformat = "%b %Y"),
         yaxis = list(title = "Jumlah Transaksi"),
         legend = list(title = list(text = "Tipe Hari")))

p1

Dari grafik terlihat bahwa jumlah transaksi harian berbeda cukup jelas antara hari kerja (weekday) dan akhir pekan (weekend), sekaligus menunjukkan perubahan pola dari waktu ke waktu. Secara umum, transaksi pada hari kerja cenderung lebih tinggi dan relatif stabil, sedangkan pada akhir pekan jumlahnya lebih rendah dan lebih naik turun. Ini menunjukkan bahwa aktivitas pembelian lebih banyak terjadi di hari kerja, kemungkinan karena pelanggan lebih aktif bertransaksi saat menjalani rutinitas harian.

Jika memperhatikan garis moving average 7 hari, terlihat adanya kecenderungan peningkatan secara perlahan, terutama mendekati akhir tahun. Walaupun data harian terlihat cukup fluktuatif, garis rata-rata ini membantu memperjelas bahwa secara keseluruhan jumlah transaksi mengalami pertumbuhan. Peningkatan ini semakin terlihat di kuartal terakhir, di mana transaksi baik pada hari kerja maupun akhir pekan sama-sama meningkat, meskipun hari kerja tetap memberikan kontribusi terbesar.

Dari kondisi ini dapat dipahami bahwa pola transaksi pelanggan dipengaruhi oleh waktu dalam seminggu, dengan hari kerja sebagai periode utama. Namun, adanya peningkatan transaksi di akhir pekan menjelang akhir tahun menunjukkan adanya perubahan perilaku, yang kemungkinan dipicu oleh faktor musiman seperti promosi atau periode belanja tertentu. Hal ini menjadi peluang bagi bisnis untuk lebih memaksimalkan penjualan di akhir pekan, terutama pada waktu-waktu dengan potensi permintaan yang tinggi.

Insight

• Ketergantungan Absolut pada Pasar Domestik (UK): Meskipun dikategorikan sebagai ritel internasional, lebih dari 90% aktivitas transaksi NapMart didominasi secara penuh oleh pasar United Kingdom. Kontribusi dari negara lain sangat kecil dan tersebar, yang menandakan bahwa strategi pemasaran atau ekspansi ke pasar global saat ini belum berjalan optimal.

• Dominasi Produk Murah dan Efek Pareto: Penjualan NapMart digerakkan oleh volume barang dengan harga yang sangat terjangkau (mayoritas di kisaran 1-2 poundsterling). Selain itu, total pendapatan sangat bertumpu pada 10 produk unggulan saja, menegaskan fenomena Pareto di mana sebagian kecil produk menyumbang porsi terbesar dari keseluruhan omzet perusahaan.

• Peran Krusial Segmen VIP dan Potensi Big Spender: Segmen VIP (sebanyak 2.031 pelanggan) adalah nyawa bisnis NapMart, menyumbangkan pendapatan hingga £9,7 juta dengan frekuensi kunjungan yang tinggi. Menariknya, terdapat segmen Big Spender (147 pelanggan) yang sangat jarang berbelanja namun sekali melakukan transaksi nilainya sangat besar (nilai tengah £1.002).

• Perilaku Pelanggan B2B dan Siklus Akhir Tahun: Tren transaksi pada hari kerja (weekday) secara konsisten selalu mengalahkan akhir pekan (weekend). Hal ini mencerminkan bahwa pelanggan utama NapMart didominasi oleh kelas profesional atau pembeli grosir (wholesaler) yang beroperasi di hari kerja. Tren ini baru berubah dan mengalami lonjakan drastis pada bulan November akibat momentum persiapan belanja akhir tahun.

Saran

• Amankan Stok 10 Produk Inti (Zero-Stockout): Karena kelangsungan bisnis sangat bergantung pada segelintir produk terlaris, manajemen rantai pasok (supply chain) harus memastikan ketersediaan stok untuk 10 barang unggulan tersebut tidak pernah kosong.

• Terapkan Layanan Khusus (Key Account Management): Pelanggan VIP dan para pembeli grosir ekstrem harus diberikan perlakuan istimewa. Berikan mereka jalur pemesanan khusus, staf account manager pribadi, atau fasilitas pengiriman prioritas agar kesetiaan mereka terhadap NapMart tetap terjaga.

• Strategi Bundling & Promosi Penstimulasi: Untuk pelanggan segmen Loyal Kecil (sering datang tapi belanjanya sedikit), terapkan strategi penawaran paket produk (bundling) agar mereka terdorong membeli lebih banyak dalam satu keranjang. Untuk segmen Big Spender, berikan promosi khusus seperti diskon pesanan ulang (re-order) untuk memancing mereka agar lebih rutin berbelanja dan naik kelas menjadi VIP.

• Pusatkan Amunisi di Kuartal Keempat: Alih-alih membakar uang iklan di awal hingga pertengahan tahun yang sedang lesu, simpan dan lipatgandakan anggaran promosi, jumlah pekerja gudang, serta ketersediaan barang pada bulan September hingga November. Ini akan memastikan NapMart bisa meraup keuntungan maksimal dari gelombang transaksi akhir tahun.

• Evaluasi Ulang Ekspansi Internasional: Kurangi pengeluaran promosi internasional yang tidak efektif, dan pusatkan terlebih dahulu dominasi pangsa pasar di kawasan United Kingdom sampai bisnis benar-benar matang untuk berekspansi secara serius ke Perancis atau Jerman.