Apa Itu Cohort Analysis dan Retention?

Cohort analysis adalah teknik mengelompokkan pengguna berdasarkan karakteristik yang sama pada periode tertentu — paling umum, kapan mereka pertama kali melakukan transaksi atau mendaftar. Dengan cohort, kita bisa membandingkan perilaku kelompok yang berbeda secara "apple to apple", tanpa noise dari pertumbuhan user baru yang terus masuk.

Retention rate mengukur berapa persen dari cohort tersebut yang tetap aktif (melakukan pembelian, login, atau aksi yang relevan) pada periode berikutnya. Ini adalah metrik kesehatan produk paling jujur: akuisisi bisa dipalsukan dengan iklan, tapi retensi tidak bisa berbohong.

📊
Benchmark industri umum (e-commerce):
Retention M1 (bulan 2 setelah join): 20–40% dianggap baik
Retention M3: 15–25%
Retention M6+: 10–20%
Angka di bawah ini sinyal churn yang memerlukan perhatian produk.

Dataset Latihan

Kita akan menggunakan tabel orders sederhana dengan data transaksi e-commerce. Semua query menggunakan standard SQL yang kompatibel dengan MySQL 8.0+ dan PostgreSQL 14+.

SQL — Membuat tabel dan data contoh
CREATE TABLE orders (
  id          BIGINT         PRIMARY KEY,
  user_id     INT            NOT NULL,
  order_date  DATE           NOT NULL,
  amount      DECIMAL(10,2) NOT NULL
);

-- Contoh data (1000 user, 3 tahun transaksi)
-- user_id 1: cohort Jan 2023, aktif 6 bulan
-- user_id 2: cohort Jan 2023, hanya beli sekali
-- user_id 3: cohort Feb 2023, aktif 12 bulan
-- dst...

-- Di SQL Lab BASEGROUND, jalankan:
-- SELECT * FROM orders LIMIT 10;

Langkah 1: Menentukan Cohort Bulanan

Cohort bulan adalah bulan pertama kali seorang user melakukan order. Kita cari tanggal order minimum per user, lalu truncate ke awal bulan.

SQL — CTE cohort: bulan pertama transaksi tiap user
WITH cohort AS (
  SELECT
    user_id,
    DATE_FORMAT(MIN(order_date), '%Y-%m-01')  AS cohort_month
    -- PostgreSQL: DATE_TRUNC('month', MIN(order_date))::DATE
  FROM  orders
  GROUP BY user_id
)
SELECT
  cohort_month,
  COUNT(*) AS cohort_size
FROM  cohort
GROUP BY cohort_month
ORDER BY cohort_month;

Hasil yang diharapkan:

cohort_monthcohort_size
2023-01-01142
2023-02-01118
2023-03-01135
2023-04-01127
......

Langkah 2: Tabel Aktivitas per Periode

Setelah mengetahui cohort setiap user, kita perlu tahu di bulan mana saja mereka aktif (melakukan order). Kita join data order dengan data cohort, lalu hitung month_number — jarak bulan antara bulan aktif dan bulan cohort.

SQL — Menghitung month_number per user
WITH
cohort AS (
  SELECT
    user_id,
    DATE_FORMAT(MIN(order_date), '%Y-%m-01') AS cohort_month
  FROM orders
  GROUP BY user_id
),
activity AS (
  SELECT DISTINCT
    user_id,
    DATE_FORMAT(order_date, '%Y-%m-01')        AS active_month
  FROM orders
)
SELECT
  c.user_id,
  c.cohort_month,
  a.active_month,
  TIMESTAMPDIFF(MONTH, c.cohort_month, a.active_month) AS month_number
  -- PostgreSQL: EXTRACT(YEAR FROM AGE(a.active_month, c.cohort_month)) * 12
  --            + EXTRACT(MONTH FROM AGE(a.active_month, c.cohort_month))
FROM  cohort   c
JOIN  activity a ON c.user_id = a.user_id
ORDER BY c.user_id, month_number
LIMIT 10;
user_idcohort_monthactive_monthmonth_number
12023-01-012023-01-010
12023-01-012023-02-011
12023-01-012023-04-013
22023-01-012023-01-010
32023-02-012023-02-010
32023-02-012023-03-011

Perhatikan user_id 1: dia aktif di M0, M1, M3 — namun tidak di M2. Artinya dia "hilang" sebulan lalu kembali. Ini informasi penting yang bisa dit-gali lebih lanjut.

Langkah 3: Menghitung Retention Rate

Kita gabungkan semua CTE sebelumnya untuk menghasilkan retention rate per cohort per month_number.

SQL — Retention rate lengkap dengan 4 CTE
WITH
cohort AS (
  SELECT
    user_id,
    DATE_FORMAT(MIN(order_date), '%Y-%m-01') AS cohort_month
  FROM orders
  GROUP BY user_id
),
activity AS (
  SELECT DISTINCT
    user_id,
    DATE_FORMAT(order_date, '%Y-%m-01') AS active_month
  FROM orders
),
cohort_activity AS (
  SELECT
    c.cohort_month,
    TIMESTAMPDIFF(MONTH, c.cohort_month, a.active_month) AS month_number,
    COUNT(DISTINCT c.user_id) AS active_users
  FROM  cohort   c
  JOIN  activity a ON c.user_id = a.user_id
  GROUP BY c.cohort_month, month_number
),
cohort_size AS (
  SELECT cohort_month, COUNT(*) AS total_users
  FROM  cohort
  GROUP BY cohort_month
)
SELECT
  ca.cohort_month,
  ca.month_number,
  ca.active_users,
  cs.total_users,
  ROUND(ca.active_users / cs.total_users * 100, 1) AS retention_pct
FROM  cohort_activity ca
JOIN  cohort_size     cs ON ca.cohort_month = cs.cohort_month
ORDER BY ca.cohort_month, ca.month_number;
💡
M0 selalu 100% — semua user di cohort pasti aktif di bulan pertama mereka (itulah mengapa mereka masuk cohort). Gunakan nilai M0 sebagai pembagi (basis) seluruh perhitungan. Jika M0 bukan 100%, cek apakah ada data integrity issue.

Langkah 4: Pivot Retention Table

Data retention yang berguna untuk dibaca adalah format tabel pivot: baris = cohort, kolom = M0, M1, M2, M3, ... MySQL tidak punya PIVOT built-in, tapi kita bisa mensimulasikannya dengan MAX(CASE WHEN ...).

SQL — Pivot retention table (0–6 bulan)
WITH
retention_raw AS (
  -- ... salin query retention rate dari langkah 3 di sini ...
  -- atau gunakan sebagai subquery
)
SELECT
  cohort_month,
  MAX(CASE WHEN month_number = 0 THEN CONCAT(retention_pct, '%') END) AS `M0`,
  MAX(CASE WHEN month_number = 1 THEN CONCAT(retention_pct, '%') END) AS `M1`,
  MAX(CASE WHEN month_number = 2 THEN CONCAT(retention_pct, '%') END) AS `M2`,
  MAX(CASE WHEN month_number = 3 THEN CONCAT(retention_pct, '%') END) AS `M3`,
  MAX(CASE WHEN month_number = 4 THEN CONCAT(retention_pct, '%') END) AS `M4`,
  MAX(CASE WHEN month_number = 5 THEN CONCAT(retention_pct, '%') END) AS `M5`,
  MAX(CASE WHEN month_number = 6 THEN CONCAT(retention_pct, '%') END) AS `M6`
FROM  retention_raw
GROUP BY cohort_month
ORDER BY cohort_month;

Contoh output yang dihasilkan:

cohort_monthM0M1M2M3M4M5M6
2023-01-01100%42.3%31.7%26.1%22.5%19.8%17.6%
2023-02-01100%38.1%28.8%24.4%20.3%18.6%16.1%
2023-03-01100%44.4%33.3%27.4%23.0%20.7%
........................

Funnel Analysis

Funnel mengukur konversi di setiap langkah alur pengguna: dari melihat produk → menambahkan ke keranjang → checkout → pembelian. Teknik ini menggunakan conditional COUNT DISTINCT dalam satu query.

SQL — Funnel 4 langkah dengan conversion rate
-- Asumsi: tabel events (user_id, event_name, event_date)
SELECT
  COUNT(DISTINCT CASE WHEN event_name = 'view_product' THEN user_id END)
    AS s1_lihat,

  COUNT(DISTINCT CASE WHEN event_name = 'add_to_cart'  THEN user_id END)
    AS s2_keranjang,

  COUNT(DISTINCT CASE WHEN event_name = 'checkout'     THEN user_id END)
    AS s3_checkout,

  COUNT(DISTINCT CASE WHEN event_name = 'purchase'     THEN user_id END)
    AS s4_beli,

  ROUND(
    COUNT(DISTINCT CASE WHEN event_name = 'add_to_cart' THEN user_id END) * 100.0 /
    NULLIF(COUNT(DISTINCT CASE WHEN event_name = 'view_product' THEN user_id END), 0), 1
  ) AS lihat_ke_keranjang_pct,

  ROUND(
    COUNT(DISTINCT CASE WHEN event_name = 'purchase' THEN user_id END) * 100.0 /
    NULLIF(COUNT(DISTINCT CASE WHEN event_name = 'view_product' THEN user_id END), 0), 1
  ) AS konversi_total_pct

FROM events
WHERE event_date BETWEEN '2024-01-01' AND '2024-12-31';
💡
Gunakan NULLIF(divisor, 0) untuk menghindari division by zero error. Hasilnya akan NULL jika tidak ada user di langkah pertama, yang lebih mudah dideteksi daripada runtime error.

LTV (Customer Lifetime Value) per Cohort

LTV mengukur total pendapatan rata-rata yang dihasilkan satu user sepanjang hidupnya. Dianalisis per cohort, kita bisa melihat apakah cohort yang lebih baru menghasilkan LTV lebih tinggi atau lebih rendah — sinyal efektivitas produk atau akuisisi.

SQL — LTV kumulatif per cohort
WITH cohort AS (
  SELECT
    user_id,
    DATE_FORMAT(MIN(order_date), '%Y-%m-01') AS cohort_month
  FROM orders
  GROUP BY user_id
)
SELECT
  c.cohort_month,
  COUNT(DISTINCT c.user_id)                          AS cohort_size,
  COUNT(o.id)                                         AS total_orders,
  ROUND(SUM(o.amount), 2)                             AS total_revenue,
  ROUND(SUM(o.amount) / COUNT(DISTINCT c.user_id), 2) AS ltv_per_user,
  ROUND(AVG(o.amount), 2)                             AS avg_order_value,
  ROUND(COUNT(o.id) * 1.0 / COUNT(DISTINCT c.user_id), 1) AS avg_orders_per_user
FROM   cohort c
JOIN   orders o ON c.user_id = o.user_id
GROUP BY c.cohort_month
ORDER BY c.cohort_month;
cohort_monthcohort_sizeltv_per_useravg_order_valueavg_orders
2023-01-01142Rp 847.500Rp 185.2004.6
2023-02-01118Rp 792.300Rp 179.8004.4
2023-03-01135Rp 918.700Rp 192.6004.8
...............
⚠️
LTV bersifat kumulatif dan time-biased. Cohort yang lebih lama memiliki LTV lebih tinggi bukan karena lebih baik, tapi karena lebih banyak waktu untuk bertransaksi. Untuk perbandingan adil, bandingkan LTV hanya sampai bulan ke-N yang sama untuk semua cohort (misalnya: LTV 6 bulan pertama untuk semua cohort).

Latihan Mandiri

Lima soal latihan untuk memperdalam pemahaman. Gunakan dataset orders di SQL Lab:

1

Weekly Cohort Retention

Ubah analisis cohort dari bulanan menjadi mingguan. Gunakan YEARWEEK(order_date) di MySQL atau DATE_TRUNC('week', ...) di PostgreSQL. Hitung retention untuk 4 minggu pertama (W0, W1, W2, W3).

2

Cohort dengan Retention M3 Tertinggi

Dari hasil pivot retention table, temukan cohort mana yang memiliki retention rate tertinggi di bulan ke-3 (M3). Apa artinya jika cohort bulan tertentu memiliki M3 jauh di atas rata-rata?

3

LTV Kumulatif 6 Bulan Pertama

Modifikasi query LTV agar hanya menghitung order yang terjadi dalam 6 bulan pertama sejak cohort_month. Ini membuat perbandingan LTV antar cohort menjadi lebih adil (apple-to-apple).

4

Churn Rate Bulanan

Hitung churn rate sebagai 1 - retention_rate. Buat query yang menampilkan churn rate per cohort per bulan dalam format yang sama dengan pivot tabel retention. Cohort mana yang memiliki churn tertinggi di M1?

5

Funnel per Cohort

Jika tersedia tabel events, hitung conversion rate funnel view → purchase untuk setiap cohort. Apakah cohort yang lebih baru memiliki conversion rate yang lebih baik? Ini bisa menjadi indikator efektivitas perubahan UX.