Jembatan ekosistem

Cara pandas, scikit-learn, dan PyTorch mengonsumsi ndarray.

Daftar isi

Topik ini membahas ndarray sebagai bahasa bersama ekosistem sains data Python. Pembahasan mencakup konversi DataFrame pandas ke ndarray dengan to_numpy, penerimaan ndarray secara langsung oleh scikit-learn, jembatan menuju PyTorch melalui torch.from_numpy, serta prinsip nol-salinan yang menghubungkan semuanya. Setelah membacanya, data dapat mengalir antar pustaka tanpa konversi yang tidak perlu.

DataFrame ke ndarray dengan to_numpy()#

Metode df.to_numpy() mengubah DataFrame menjadi ndarray. Untuk DataFrame berkolom sejenis, hasilnya berupa matriks dua dimensi dengan dtype yang sama dengan kolomnya. Metode ini menerima parameter dtype dan copy untuk mengendalikan tipe serta kebutuhan salinan.

python
import numpy as np
import pandas as pd

df = pd.DataFrame({
    "suhu": [27.5, 28.0, 26.5],
    "kelembapan": [70.0, 65.0, 80.0],
})
array = df.to_numpy()
print(type(array))
print(array.dtype)
print(array)
print(np.shares_memory(array, df["suhu"].to_numpy()))
text Keluaran
<class 'numpy.ndarray'>
float64
[[27.5 70. ]
 [28.  65. ]
 [26.5 80. ]]
True

Keluaran mengonfirmasi tiga hal: tipenya ndarray asli dan bukan tipe khusus pandas, dtype-nya float64, dan isinya berupa matriks dengan urutan kolom mengikuti DataFrame-nya. Pemanggilan np.shares_memory melaporkan True, yang berarti hasilnya berbagi memori dengan data kolomnya di lingkungan penulisan. Dokumentasi pandas menyatakan hasil to_numpy dapat berupa view maupun salinan bergantung pada tipe kolomnya, sehingga pola yang aman adalah memeriksa dengan np.shares_memory apabila sifat berbagi memori itu penting.

Apabila kolomnya berbeda tipe, to_numpy mempromosikan tipe ke yang paling luas, dan salinan baru pasti dibuat.

python
import numpy as np
import pandas as pd

df = pd.DataFrame({
    "jumlah": [1, 2, 3],
    "harga": [2.5, 1.0, 4.0],
})
array = df.to_numpy()
print(array.dtype)
print(array)
print(np.shares_memory(array, df["jumlah"].to_numpy()))
text Keluaran
float64
[[1.  2.5]
 [2.  1. ]
 [3.  4. ]]
False

Kolom jumlah yang bertipe integer ikut menjadi float64, dan np.shares_memory melaporkan False karena penyangganya baru. Kolom berisi teks mengikuti mekanisme yang sama dengan menghasilkan dtype object. Untuk tabel besar, pemromosian ini berarti penggandaan ukuran memori ketika kolom integer ikut diangkat menjadi pecahan, sehingga pemeriksaan dtype sebelum konversi menjadi kebiasaan yang baik. Perilaku ini juga menyediakan jaminan praktis: satu ndarray untuk seluruh tabel, sekalipun kolom aslinya campuran.

Metode .values lama juga mengembalikan ndarray, tetapi dokumentasi pandas merekomendasikan .to_numpy() untuk kode baru. Alasannya ada dua: .to_numpy() menerima parameter dtype dan copy secara eksplisit, sedangkan .values tidak, dan perilaku .values pada beberapa objek bergantung definisi sehingga ambigu. Keduanya tetap dijumpai pada kode lama, sedangkan .to_numpy() menjadi pilihan default.

scikit-learn mengonsumsi ndarray langsung#

scikit-learn tidak melakukan konversi ketika menerima ndarray: masukan fit dan transform dipakai apa adanya, dan keluarannya juga ndarray. Alur data yang disiapkan dengan NumPy dapat langsung mengalir ke estimator tanpa satu baris konversi pun.

python
import numpy as np
from sklearn.preprocessing import StandardScaler

data = np.array([[1500.0, 3.0],
                 [2100.0, 4.0],
                 [1800.0, 3.0]])
scaler = StandardScaler()
terstandar = scaler.fit_transform(data)
print(terstandar)
print(scaler.mean_)
print(scaler.scale_)
print(np.array_equal(terstandar, (data - scaler.mean_) / scaler.scale_))
text Keluaran
[[-1.22474487 -0.70710678]
 [ 1.22474487  1.41421356]
 [ 0.         -0.70710678]]
[1800.            3.33333333]
[244.94897428   0.47140452]
True

Data dengan satuan berbeda, yaitu luas dalam ribuan dan jumlah kamar satu digit, terstandarisasi menjadi skala yang setara. Definisi standardisasinya sendiri berupa aritmetika array biasa, yaitu selisih terhadap rata-rata dibagi simpangan baku per kolom.

z=x−xˉs z = \frac{x - \bar{x}}{s}

Notasi xˉ\bar{x} tersimpan pada atribut mean_ dan notasi ss pada scale_, sehingga proses yang sama dapat diulang atas data baru dengan scaler.transform. Baris terakhir keluaran menegaskan bahwa hasil fit_transform identik dengan perhitungan manual (data - scaler.mean_) / scaler.scale_, dan perhitungan itu dilakukan sepenuhnya dengan NumPy. Karena masukan dan keluarannya sama-sama ndarray, pola penanganan array pada modul ini, mulai dari inspeksi dtype hingga verifikasi berbagi memori, berlaku pada alur scikit-learn tanpa penyesuaian apa pun.

Jembatan menuju PyTorch#

PyTorch tidak terpasang di lingkungan penulisan, sehingga contoh berikut ditandai tidak-dijalankan dan tidak disertai keluaran. Polanya tetap perlu dicatat karena memperlihatkan ujung dari prinsip nol-salinan.

python
# tidak-dijalankan
# PyTorch tidak terpasang di lingkungan penulisan, sehingga blok ini tidak dieksekusi
import numpy as np
import torch

array = np.arange(6, dtype=np.float64).reshape(2, 3)
tensor = torch.from_numpy(array)
array[0, 0] = 99.0
print(tensor[0, 0])

Fungsi torch.from_numpy tidak menyalin apa pun: tensor berbagi penyangga dengan ndarray-nya, sehingga perubahan array[0, 0] langsung terbaca pada tensor. Inilah bentuk paling murni dari jembatan ekosistem, yaitu dua pustaka yang berbeda mengoperasikan satu penyangga memori yang sama. Untuk pemasangan model, konversi dtype ke float32 melalui .astype biasanya dilakukan lebih dahulu, karena alur pelatihan umumnya menuntut tipe tersebut.

Dengan tiga jembatan ini, alur data nyata memakai satu penyangga dari ujung ke ujung: DataFrame dari berkas diubah menjadi ndarray, ndarray diolah dengan NumPy, diterima scikit-learn, dan bila diperlukan, diserahkan kepada PyTorch. Biaya jembatannya nyaris nol, sedangkan salinan yang mahal hanya muncul ketika tipe kolomnya harus dipromosikan.

Kesalahan umum#

Menganggap to_numpy selalu menyalin#

Anggapan bahwa hasil to_numpy selalu berdiri sendiri berbahaya pada DataFrame berkolom sejenis, karena hasilnya bisa berupa view. Mutasi pada array yang dikira salinan itu mengubah DataFrame aslinya tanpa pesan apa pun.

python
import numpy as np
import pandas as pd

df = pd.DataFrame({"suhu": [27.5, 28.0, 26.5]})
array = df.to_numpy()
array[0] = 0.0
print(df)
text Keluaran
   suhu
0   0.0
1  28.0
2  26.5

Nilai pertama kolom suhu berubah menjadi 0.0 karena array yang dimutasi adalah view atas data kolomnya. Perbaikannya adalah meminta salinan secara eksplisit dengan to_numpy(copy=True), atau menyalin arraynya dengan .copy(), apabila DataFrame asli harus tetap utuh.

Mengira keluaran scikit-learn tetap DataFrame#

Estimator scikit-learn mengembalikan ndarray, meskipun masukannya DataFrame dengan nama kolom yang rapi. Ekspektasi yang salah ini memunculkan error atribut ketika kode berikutnya memanggil metode DataFrame atas hasilnya.

python
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler

df = pd.DataFrame({"luas": [1500.0, 2100.0, 1800.0], "kamar": [3.0, 4.0, 3.0]})
terstandar = StandardScaler().fit_transform(df)
print(type(terstandar))
text Keluaran
<class 'numpy.ndarray'>

Perbaikannya adalah membungkus kembali hasilnya dengan pd.DataFrame(terstandar, columns=df.columns) apabila pemrosesan lanjutan memang membutuhkan DataFrame. Alternatif yang lebih hemat adalah melanjutkan pekerjaan dalam bentuk ndarray, karena sebagian besar operasi statistik NumPy bekerja langsung padanya.

Ringkasan#

  • df.to_numpy() mengubah DataFrame menjadi ndarray, dengan tipe kolom campuran dipromosikan dan salinan dibuat pada pemromosiannya.
  • scikit-learn menerima ndarray apa adanya dan mengembalikan ndarray, sehingga tidak ada konversi pada alur datanya.
  • torch.from_numpy berbagi penyangga dengan ndarray-nya, dan mutasi pada salah satu sisi terlihat pada sisi lain.
  • Prinsip nol-salinan berlaku sepanjang alur, tetapi sifat view dari to_numpy wajib diverifikasi, atau salinannya diminta secara eksplisit.

Referensi#