Backend pyarrow pada chapter sebelumnya memangkas memori data teks, tetapi masih menyimpan satu juta sel apa pun isinya. Ada kelompok data yang menuntut strategi berbeda: matriks one-hot, hasil one-hot encoding, absensi harian, atau skema spesialis yang isinya nyaris seluruhnya nol. Untuk pola “satu nilai mendominasi” ini pandas menyediakan SparseDtype — dtype ekstensi yang menyimpan hanya sel-sel yang berbeda dari satu nilai pengisi (fill value) dan membiarkan sisanya tersirat. Script berikut mengukur penghematannya, menunjukkan jebakan fill value bawaan, lalu menutup dengan pertukaran data bolak-balik dengan scipy.

Menyimpan Hanya yang Berbeda dari fill_value #

Script berikut membuat Series satu juta float berisi nol, lalu mengonversinya menjadi sparse dengan fill value 0.0.

python
import numpy as np
import pandas as pd

# SparseDtype menyimpan hanya nilai yang berbeda dari fill_value
n = 1_000_000
padat = pd.Series(np.zeros(n))
jarang = padat.astype(pd.SparseDtype("float64", fill_value=0.0))
print("dtype  :", str(jarang.dtype))
print("dense  :", f"{padat.memory_usage() / 1e6:.1f} MB")
print("sparse :", f"{jarang.memory_usage() / 1e3:.1f} KB")
print("npoints:", jarang.sparse.npoints)
print()

# default fill_value untuk float adalah NaN, bukan 0
tanpa_fill = padat.astype("Sparse[float64]")
print("astype('Sparse[float64]') ->", str(tanpa_fill.dtype))
print("npoints tanpa fill 0      :", tanpa_fill.sparse.npoints)
print()

# density, sp_values, dan konversi balik
s = pd.Series([0, 7, 0, 0, 5, 0], dtype="Sparse[int64]")
print("dtype    :", str(s.dtype))
print("density  :", s.sparse.density)
print("sp_values:", s.values.sp_values.tolist())
print("to_dense :", s.sparse.to_dense().tolist())
print("s + 1    :", (s + 1).sparse.to_dense().tolist())
print()

# scipy: coo_matrix <-> DataFrame sparse
from scipy import sparse as sps

mat = sps.coo_matrix(([1.0, 2.0], ([0, 2], [1, 0])), shape=(3, 3))
df_sp = pd.DataFrame.sparse.from_spmatrix(mat)
print(df_sp)
kembali = df_sp.sparse.to_coo()
print("kembali ke scipy:", type(kembali).__name__, "| nnz:", kembali.nnz)

Output yang Dihasilkan:

text
dtype  : Sparse[float64, 0.0]
dense  : 8.0 MB
sparse : 0.1 KB
npoints: 0

astype('Sparse[float64]') -> Sparse[float64, nan]
npoints tanpa fill 0      : 1000000

dtype    : Sparse[int64, 0]
density  : 0.3333333333333333
sp_values: [7, 5]
to_dense : [0, 7, 0, 0, 5, 0]
s + 1    : [1, 8, 1, 1, 6, 1]

     0    1    2
0  NaN  1.0  NaN
1  NaN  NaN  NaN
2  2.0  NaN  NaN
kembali ke scipy: coo_matrix | nnz: 2

Mari kita bedah bagian pentingnya:

  1. Baris 5-7 (konversi): pd.SparseDtype("float64", fill_value=0.0) menyatakan “tipe dasarnya float64 dan nilai pengisinya 0.0”; astype() menerima objek dtype ini sebagaimana menerima nama dtype string.
  2. Baris 8-11 (hasil ukuran): representasi dense menempati 8.0 MB (satu juta × 8 byte), sedangkan versi sparse tinggal 0.1 KB dengan npoints bernilai 0 — tidak ada satu pun sel yang perlu disimpan karena semuanya sama dengan fill value.
  3. Baris 15-17 (jebakan default): astype("Sparse[float64]") tanpa dtype eksplisit menghasilkan Sparse[float64, nan] dan npoints-nya satu juta; tanpa fill value yang tepat, tidak ada penghematan sama sekali.
  4. Baris 21-26 (anatomi): Series [0, 7, 0, 0, 5, 0] memakai Sparse[int64] yang fill value default-nya 0; method .sparse.density menghitung rasio data tersimpan (2 dari 6 = 0.33), .values.sp_values membuka array [7, 5] yang memang disimpan, dan to_dense() mengembalikan wujud penuhnya.
  5. Baris 32-36 (scipy): pd.DataFrame.sparse.from_spmatrix() mengubah coo_matrix scipy menjadi DataFrame sparse, dan to_coo() mengembalikannya — nnz tetap 2.

fill_value Menentukan Semuanya #

Fill value adalah jantung dtype sparse: sel bernilai persis sama dengan fill value tidak disimpan, dan sisanya disimpan sebagai sp_values beserta posisi indeksnya. Karena itu pemilihan fill value harus mengikuti data, bukan kebiasaan. Output blok kedua memperlihatkan akibatnya: string dtype "Sparse[float64]" bawaan pandas memakai nan sebagai fill value (mengikuti konvensi missing float), sehingga jutaan angka nol tetap tersimpan satu per satu dan memori justru bertambah. Kebalikannya terjadi pada "Sparse[int64]" yang default fill-nya 0 — kebetulan tepat untuk data one-hot.

Pola aman yang direkomendasikan tulisan ini adalah selalu menuliskan fill value secara eksplisit lewat pd.SparseDtype(..., fill_value=...) seperti baris 7. Ciri dtype hasilnya langsung terbaca pada repr: Sparse[float64, 0.0] berarti float64 dengan pengisi 0.0.

Bekerja dengan Accessor .sparse #

Operasi matematika pada Series sparse tetap berjalan seperti biasa: s + 1 menghasilkan [1, 8, 1, 1, 6, 1] dan hasilnya sendiri masih sparse (fill value-nya bergeser ikut menjadi 1). Saat perlu kembali ke dunia dense, s.sparse.to_dense() mengembalikan Series biasa. Accessor .sparse menyediakan kumpulan method khusus: density untuk rasio kelengkapan, npoints untuk jumlah nilai tersimpan, to_dense() untuk konversi balik, dan pada Series dengan MultiIndex tersedia to_coo() untuk ekspor ke format scipy.

Nilai density berguna sebagai penentu kapan sparse memang tepat. Sebagai pegangan praktis, sparse menguntungkan ketika density jauh di bawah 0.5 dan fill value benar-benar mendominasi; pada density mendekati satu, overhead indeks posisi justru membuat representasi sparse lebih berat daripada dense.

Pertukaran dengan scipy #

Ekosistem Python punya pustaka sparse terstandar, scipy.sparse, yang dipakai scikit-learn dan banyak pustaka lain. Dua jembatan pandas membuat pertukarannya satu baris: pd.DataFrame.sparse.from_spmatrix(mat) menarik matriks scipy masuk ke DataFrame (output menampilkan isi 1.0 dan 2.0 di posisi [0, 1] dan [2, 0], sisanya NaN karena fill value default dari jalur ini adalah nan), sedangkan df_sp.sparse.to_coo() mengirim DataFrame sparse kembali ke scipy sebagai coo_matrix dengan nnz yang tetap 2. Perhatikan bahwa fill value hasil from_spmatrix adalah nan — bila Anda ingin nol sebagai pengisi, tulis ulang dtype-nya lewat astype(pd.SparseDtype("float64", fill_value=0.0)).

Dengan sparse, seluruh keluarga dtype ekstensi pada section ini sudah lengkap dari sisi penyimpanan. Satu risiko baru muncul dari kekayaan API sebanyak itu: API berubah antar versi pandas, dan chapter penutup ini — sekaligus penutup section Advanced Pandas — membahas bagaimana pandas mengumumkan perubahannya lewat kebijakan deprecation, warning, dan halaman whatsnew.

Referensi resmi untuk chapter ini: user guide Sparse data structures , pandas.SparseDtype , dan pandas.DataFrame.sparse.from_spmatrix .