Bab Normal Distribution menghasilkan satu kolom angka. Data nyata jarang sezaman itu: pengukuran tubuh memuat tinggi dan berat badan yang naik-turun bersama, sementara catatan pengeluaran rumah tangga memuat pos pangan dan transportasi yang bergerak searah ketika harga naik. Untuk membangkitkan banyak kolom sekaligus dengan hubungan antarkolom yang terkendali, Generator menyediakan multivariate_normal(mean, cov, size).

Argumen mean adalah vektor pusat untuk tiap dimensi, sedangkan cov adalah matriks kovarians yang menentukan dua hal sekaligus: varians tiap dimensi (diagonal) dan kekuatan hubungan tiap pasangan dimensi (di luar diagonal). Konsep di balik kedua angka itu sudah dibahas pada Korelasi dan Kovarians; bab ini memakainya sebagai bahan pembentuk data, lalu membongkar mekanisme yang bekerja di balik layar.

Vektor Fitur Berkorelasi dengan rng.multivariate_normal #

Kita bangun data pengukuran 100.000 orang dengan dua fitur: tinggi (cm) dan berat (kg). Target sebarannya dirumuskan sebagai matriks kovarians: varians tinggi 64 (std 8 cm), varians berat 36 (std 6 kg), dan kovarians 24. Kovarians 24 tersebut menyiratkan korelasi 24 / (8 * 6) = 0.5 — makin tinggi, kecenderungannya makin berat.

python
import numpy as np

mean = np.array([165.0, 60.0])
cov = np.array([[64.0, 24.0], [24.0, 36.0]])
rng = np.random.default_rng(42)
sampel = rng.multivariate_normal(mean, cov, size=100_000)
print(sampel[:5].round(2))

Output yang Dihasilkan:

text
[[165.11  54.41]
 [157.05  60.55]
 [182.98  63.25]
 [164.76  58.15]
 [167.13  56.59]]

Bedah baris demi baris:

  1. Baris 3-4 (mean, cov): mean berupa vektor dua elemen sehingga tiap baris sampel juga dua elemen; cov berupa matriks 2x2 yang selalu simetris.
  2. Baris 6 (size=100_000): hasilnya matriks 100.000 x 2 — tiap baris adalah satu pengamatan (satu “orang”) dengan dua fitur, persis format data tabular pada umumnya.
  3. Baris 7 (sampel[:5].round(2)): lima baris pertama saja yang dicetak, dibulatkan dua desimal; baris pertama, misalnya, berupa orang dengan tinggi 165,11 cm dan berat 54,41 kg.

Verifikasi dengan np.cov dan np.corrcoef #

Sampel berukuran besar harus mereproduksi matriks kovarians target. Fungsi np.cov menganggap tiap baris input sebagai satu variabel, sementara sampel kita berupa baris pengamatan — karena itu inputnya ditranspos menjadi np.cov(sampel.T). Pasangannya, np.corrcoef, mengubah kovarians menjadi matriks korelasi:

python
import numpy as np

mean = np.array([165.0, 60.0])
cov = np.array([[64.0, 24.0], [24.0, 36.0]])
rng = np.random.default_rng(42)
sampel = rng.multivariate_normal(mean, cov, size=100_000)
print("cov sampel :", np.cov(sampel.T).round(3))
print("cov target :", cov)
print("korelasi   :", np.corrcoef(sampel.T).round(2))

Output yang Dihasilkan:

text
cov sampel : [[63.856 23.827]
 [23.827 36.249]]
cov target : [[64. 24.]
 [24. 36.]]
korelasi   : [[1.  0.5]
 [0.5 1. ]]

Angka sampel 63.856, 23.827, dan 36.249 mendekati target 64, 24, dan 36; korelasi sampel tepat 0,5 pada dua desimal. Pola ini berlaku sama untuk pasangan pengeluaran pangan-transportasi: isi diagonal dengan varians tiap pos dan isi diagonal luar dengan kovarians yang diinginkan, lalu satu panggilan method menghasilkan seluruh dataset.

Pola Cholesky: mean + Z @ L.T #

Bagaimana method memaksakan korelasi pada angka yang acak? Jawabannya ada di aljabar linear. Matriks kovarians difaktorkan menjadi matriks segitiga L — dekomposisi Cholesky yang dibahas pada Decompositions — lalu sampel standar tanpa korelasi Z (dari standard_normal) ditransformasi dengan pola mean + Z @ L.T. Argumen method mengatur jenis faktorisasinya:

methodMekanismeCatatan
'svd'dekomposisi nilai singularpilihan bawaan (default)
'eigh'dekomposisi eigen matriks simetrissetara 'svd' untuk matriks simetris
'cholesky'faktor segitiga bawah Lpaling murah secara komputasi

Kita buktikan pola itu secara manual dan bandingkan dengan method 'cholesky':

python
import numpy as np

mean = np.array([165.0, 60.0])
cov = np.array([[64.0, 24.0], [24.0, 36.0]])
L = np.linalg.cholesky(cov)
print(L.round(3))
rng = np.random.default_rng(42)
manual = mean + rng.standard_normal((100_000, 2)) @ L.T
rng = np.random.default_rng(42)
bawaan = rng.multivariate_normal(mean, cov, size=100_000, method="cholesky")
print(np.array_equal(manual, bawaan))
print(np.cov(manual.T).round(3))

Output yang Dihasilkan:

text
[[8.    0.   ]
 [3.    5.196]]
True
[[63.839 23.857]
 [23.857 36.281]]

Bedah baris demi baris:

  1. Baris 5 (np.linalg.cholesky(cov)): menghasilkan faktor segitiga bawah; perkalian L @ L.T mengembalikan cov secara persis.
  2. Baris 8 (manual = mean + ... @ L.T): Z berupa 100.000 sampel normal standar yang kolom-kolomnya saling bebas. Perkalian Z @ L.T memutar dan meregangkan sebaran tersebut sehingga kovariansnya menjadi cov, lalu mean menggeser pusatnya.
  3. Baris 9-11: dengan seed yang sama, multivariate_normal(..., method="cholesky") menghasilkan angka yang identik bit-per-bit — np.array_equal bernilai True — sehingga pola mean + Z @ L.T memang dipakai di dalam method tersebut.

Matriks Kovarians Harus Positif Semidefinite #

Tidak semua matriks boleh menjadi cov. Syaratnya positive semidefinite (PSD): tidak ada kombinasi arah yang memperkecil varians. Korelasi 1,2 antara dua variabel, misalnya, mustahil terjadi. Matriks [[64, 80], [80, 36]] memiliki nilai eigen -31,2 dan 131,2 — salah satunya negatif — sehingga matriks tersebut bukan PSD:

python
import numpy as np
import warnings

mean = np.array([165.0, 60.0])
cov_rusak = np.array([[64.0, 80.0], [80.0, 36.0]])
rng = np.random.default_rng(42)
with warnings.catch_warnings(record=True) as caught:
    warnings.simplefilter("always")
    rng.multivariate_normal(mean, cov_rusak, size=3)
print("Warning :", caught[0].message)
try:
    rng.multivariate_normal(mean, cov_rusak, size=3, check_valid="raise")
except ValueError as e:
    print("ValueError:", e)

Output yang Dihasilkan:

text
Warning : covariance is not symmetric positive-semidefinite.
ValueError: covariance is not symmetric positive-semidefinite.

Dengan multivariate_normal, pola mean + Z @ L.T dari Decompositions, dan jaga-jaga PSD, kita dapat menyimulasikan dataset fitur berkorelasi tanpa menyentuh data pribadi — pemutusan yang rapi dari Normal Distribution satu dimensi menuju data tabular multikolom.