Proyek Machine Learning dari Hulu ke Hilir (End-to-End) – Part 5: Mempersiapkan Data untuk Algoritma Machine Learning: Mengatasi Missing Values dan Variabel Teks/Kategori

Blog Banner End To End Machine Learning With Python

“Before anything else, preparation is the key to success.”

~ Alexander Graham Bell

hkaLabs: hakim-azizul.com – Sekarang, kita akan mulai mempersiapkan data kita sebelum dimasukkan ke algoritma Machine Learning. Kita akan berusaha untuk meminimalkan pekerjaan-pekerjaan manual, dengan membuat function untuk automatisasi. Beberapa alasan pendukung untuk membuat automatisasi ini, diantaranya:

  • Memungkinkan kita untuk mereproduksi ulang tahap transformasi data dengan mudah, pada dataset apapun yang akan kita gunakan ke depannya.
  • Kita akan perlahan-lahan membangun library sendiri untuk menghandle transformasi data ini, sehingga bisa kita gunakan di proyek-proyek kita berikutnya.
  • Function dan library yang telah kita buat ini akan kita gunakan pada model Machine Learning kita yang akan kita launching secara live nanti. Sehingga data akan ditransformasikan dahulu secara otomatis, sebelum masuk ke algoritma Machine Learning.
  • Automatisasi ini memungkinkan kita untuk dengan mudah menguji beragam metode transformasi, dan melihat kombinasi transformasi yang memberikan hasil terbaik.

 

1. Membersihkan Data (Data Cleaning) dari Missing Values

Sebelum memulai membersihkan data, mari kita kembali ke training data set kita yang telah kita terapkan stratified random sampling (strat_train_set), lalu kita pisahkan variabel predictor, dengan variabel target (median_house_value):

#Pisahkan variabel predictor dan label:
housing = strat_train_set.drop("median_house_value", axis=1)
housing_labels = strat_train_set["median_house_value"].copy()

Mayoritas algoritma Machine Learning tidak dapat berfungsi apabila ada missing values pada variabel-variabel features. Sehingga, kita akan membuat functions untuk menangani permasalahan tersebut.

Sebelumnya, kita cek terlebih dahulu, variabel manakah yang memiliki missing values, dan ada berapa banyak data yang kosong tersebut:

sample_incomplete_rows_head = housing[housing.isnull().any(axis=1)].head()
sample_incomplete_rows_head

Hasil di console:

missing Values Head

Missing Values Head. Sumber Gambar: Dokumentasi Pribadi.

Dapat kita lihat, bahwa variabel total_bedrooms memiliki NaN atau missing values. Mari kita konfirmasi berapa jumlah missing values tersebut, dan apakah variabel-variabel lain ada yang memilikinya:

housing.isnull().sum()

Hasilnya:

jumlah Missing Values Pada Setiap Variabel

Jumlah Missing Values Pada Setiap Variabel. Sumber Gambar: Dokumentasi Pribadi.

Ternyata telah terkonfirmasi bahwa hanya variabel total_bedrooms yang memiliki missing values, sebanyak 158 baris observasi.

Terdapat tiga opsi untuk menangani missing values, diantaranya:

  • Menghapus baris data distrik yang memiliki missing values.
  • Menghapus seluruh variabel dengan missing values tersebut (total_bedrooms pada contoh kali ini)
  • Mengganti missing values dengan suatu nilai (nol, nilai rata-rata (mean), nilai tengah (median), dll).

Kita dapat menggunakan ketiga metode-metode tersebut menggunakan methods dropna(), drop(), dan fillna() yang tersedia pada DataFrame, sebagai berikut:

  • Opsi pertama:
#Opsi 1: Menghapus data distrik yang memiliki missing values:
housing_drop_na1 = housing.dropna(subset=["total_bedrooms"])

Cek hasilnya:

housing_drop_na1.isnull().sum() #Cek hasilnya
Opsi 1: Menghapus Baris Data Distrik yang Memiliki Missing Values

Opsi 1: Menghapus Baris Data Distrik yang Memiliki Missing Values. Sumber Gambar: Dokumentasi Pribadi.

  • Opsi kedua:
#Opsi 2: Menghapus keseluruhan atribut yang memiliki missing values:
housing_drop_na2 = housing.drop("total_bedrooms", axis=1)

Cek hasilnya (variabel total_bedrooms sudah terhapus):

housing_drop_na2.isnull().sum() #Cek hasilnya
Opsi 2: Menghapus keseluruhan atribut yang memiliki missing values:

Opsi 2: Menghapus Keseluruhan Atribut yang Memiliki Missing Values. Sumber Gambar: Dokumentasi Pribadi.

  • Opsi ketiga:
#Opsi 3: Mengganti missing values dengan nilai tertentu (median):
median = housing["total_bedrooms"].median()
housing_drop_na3 = housing["total_bedrooms"].fillna(median)

Cek hasilnya (sudah tidak ada missing values pada variabel total_bedrooms):

housing_drop_na3.isnull().sum() #Cek hasilnya
Opsi 3: Mengganti missing values dengan nilai tertentu (median):

Opsi 3: Mengganti Missing Values dengan Nilai Tertentu (Median)

Kita akan memilih opsi ketiga (mengisi missing values dengan nilai mediannya), agar data kita tidak terbuang percuma seperti apabila kita memilih opsi pertama ataupun opsi kedua.

Jika kita memilih opsi ketiga, kita akan menghitung nilai median pada dataset training, lalu menggunakannya untuk mengisi missing values pada data training, dan jangan lupa untuk menyimpan nilai median yang telah kita hitung. Kita akan membutuhkannya untuk mengisi missing values pada test set ketika kita ingin mengevaluasi sistem kita, dan ketika sistem tersebut sudah live; untuk mengisi missing values pada data baru.

scikit-learn menyediakan class untuk menangani missing values dengan mudah, yaitu: Imputer. Dengan cara penggunaan sebagai berikut, buat imputer instance, dengan secara spesifik memilih strategi Imputer missing values, misalnya dengan nilai median:

from sklearn.preprocessing import Imputer

imputer = Imputer(strategy="median")

Catatan: Mulai scikit-learn versi 0.20, class sklearn.preprocessing.Imputer di atas diganti menjadi sklearn.impute.SimpleImputer.

Untuk mengetahui versi scikit-learn yang kita gunakan, jalankan perintah berikut:

#Cek versi scikit-learn, karena untuk sklearn >= 0.20, class yang akan digunakan berbeda
print(sklearn.__version__)

scikit-learn yang ter-install di laptop saya adalah versi:

versi scikit learn yang terinstall

Versi scikit-learn yang Ter-Install. Sumber Gambar: Dokumentasi Pribadi.

Atau, kita dapat membuat error handling, agar secara otomatis kita menggunakan class Imputer yang tepat sesuai versi scikit-learn yang ter-install di sistem kita, sebagai berikut:

try:
    from sklearn.impute import SimpleImputer # Scikit-Learn 0.20+
except ImportError:
    from sklearn.preprocessing import Imputer as SimpleImputer

imputer = SimpleImputer(strategy="median")

Karena imputer dengan menggunakan median hanya dapat berlaku pada atribut numerik, kita perlu meng-copy dataset; tanpa mengikutkan variabel ocean_proximity, atau dengan kata lain menghapus variabel-variabel non-numerik:

#Untuk mengisi missing values pada atribut numerik, singkirkan dulu atribut non-numerik
housing_num = housing.drop("ocean_proximity", axis=1)

Selanjutnya, kita akan fitting imputer instance dengan data training kita menggunakan method fit():

imputer.fit(housing_num)

Respon di console/Jupyter Notebook:

output Imputer

Output Imputer. Sumber Gambar: Dokumentasi Pribadi.

Imputer sudah menghitung nilai median dari atribut numerik, dan menyimpan hasilnya pada variabel instance statistics_. Hanya variabel total_bedrooms yang memiliki missing values, namun kita tidak tahu apakah masih ada missing values atau tidak pada dataset baru apabila sistem kita sudah live, sehingga akan lebih aman apabila kita menerapkan imputer pada semua atribut numerik:

imputer.statistics_

Respon di console:

Penerapan Imputer Median

Penerapan Imputer Median. Sumber Gambar: Dokumentasi Pribadi.

Bandingkan hasilnya dengan hasil perhitungan median untuk setiap atribut numerik dengan cara manual:

housing_num.median().values

Respon di console:

Hasil Impute Median untuk Setiap Atribut Numerik.

Hasil Impute Median untuk Setiap Atribut Numerik. Sumber Gambar: Dokumentasi Pribadi.

Selanjutnya kita dapat menggunakan imputer yang telah kita “latih” dengan strategi imputer median, untuk mengisi missing values pada training set:

X = imputer.transform(housing_num)

Hasilnya adalah array Numpy biasa yang berisikan features yang telah ditransformasikan. Jika kita ingin menggabungkannya dengan dataset kita dalam bentuk DataFrame Pandas, gunakan perintah berikut:

housing_tr = pd.DataFrame(X, columns=housing_num.columns)

Panggil housing_tr, apabila penasaran dengan hasilnya:

#Jika penasaran dengan hasilnya:
housing_tr

Hasil di Jupyter Notebook:

Semua Missing Values pada Atribut Numerik Sudah Terisi

Semua Missing Values pada Atribut Numerik Sudah Terisi. Sumber Gambar: Dokumentasi Pribadi.

 

2. Meng-Handle Data Teks dan Variabel Kategori (Categorical Attributes)

Sebelumnya, kita memilih untuk menghapus variabel kategori ocean_proximity, karena ia merupakan variabel teks, sehingga kita tidak bisa menghitung mediannya. Namun, ada solusi lain untuk menangani ini, yaitu dengan mengubah variabel teks menjadi angka sehingga algoritma Machine Learning kita tetap dapat bekerja pada variabel tersebut (catatan: mayoritas algoritma Machine Learning bekerja pada variabel numerik).

Sebelumnya, kita review kembali variabel kategori yang kita punya:

housing_cat = housing["ocean_proximity"]
housing_cat.head(10)

Berikut hasilnya:

variabelKategoriOceanProximity

Variabel Kategori (ocean_proximity). Sumber Gambar: Dokumentasi Pribadi.

Untuk mengubah label teks di atas menjadi angka, scikit-learn telah menyediakan transformer untuk tugas tersebut, yaitu LabelEncoder:

from sklearn.preprocessing import LabelEncoder

encoder = LabelEncoder()
housing_cat = housing["ocean_proximity"]
housing_cat_encoded = encoder.fit_transform(housing_cat)
housing_cat_encoded

Hasilnya:

labelEncoder

Output LabelEncoder. Sumber Gambar: Dokumentasi Pribadi.

Dengan hasil yang sekarang, memungkinkan kita untuk menggunakan data numerik di atas pada algoritma-algoritma Machine Learning.

Catatan: Untuk scikit-learn versi > 0.20, terdapat alternatif untuk LabelEncoder atau juga method Pandas’ Series.factorize() untuk encode atribut teks/kategori menjadi integer. Yaitu class OrdinalEncoder, yang didesain untuk meng-handle fitur input X (bukan label y) dan bekerja dengan baik dengan pipelines.

Alternatif lainnya untuk scikit-learn versi < 0.20, yaitu import OrdinalEncoder dari future_encoders.py:

try:
    from sklearn.preprocessing import OrdinalEncoder
except ImportError:
    from future_encoders import OrdinalEncoder #Untuk scikit-Learn < 0.20

Kita dapat melihat pemetaan yang dilakukan dari variabel teks menjadi variabel numerik seperti di atas, dengan perintah berikut:

print(encoder.classes_)

Hasilnya sebagai berikut:

encoder_classes_

Hasil encoder.classes_. Sumber Gambar: Dokumentasi Pribadi.

Encoder di atas mempelajari data menggunakan classes_attribute, dengan pemetaan “<1H OCEAN” menjadi 0, “INLAND” menjadi 1, dst.

Catatan: Kita juga dapat mendapatkan hasil yang sama menggunakan OrdinalEncoder() dan ordinal_encoder.categories_:

ordinal_encoder = OrdinalEncoder()
housing_cat_encoded = ordinal_encoder.fit_transform(housing_cat)
housing_cat_encoded[:10]
OrdinalEncoder()

Hasil OrdinalEncoder(). Sumber Gambar: Dokumentasi Pribadi.

ordinal_encoder.categories_
ordinal_encoderCategories

Hasil ordinal_encoder.categories_. Sumber Gambar: Dokumentasi Pribadi.

Representasi kategori menjadi variabel numerik seperti di atas memiliki satu kelemahan; yaitu algoritma Machine Learning akan mengasumsikan dua nilai yang berdekatan memiliki tingkat kemiripan yang lebih tinggi, dibandingkan dengan dua nilai yang jaraknya lebih jauh. Sebagai contoh, “<1H OCEAN” (0) akan dianggap serupa dengan “INLAND” (1), padahal “<1 H OCEAN” (0) lebih menyerupai “NEAR OCEAN” (4).

Untuk memperbaiki issue tersebut, solusi yang umum digunakan adalah membuat binary attribute per kategori: Misalnya, atribut akan bernilai 1 apabila kategorinya “<1H OCEAN” (dan bernilai 0, apabila bukan), lalu atribut lainnya akan bernilai 1 apabila kategorinya “INLAND” (dan bernilai 0, apabila bukan), dan begitu terus selanjutnya.

Metode di atas dikenal sebagai “one-hot encoding”, karena hanya satu atribut yang akab bernilai 1 apabila sesuai (hot), dan jika tidak memenuhi suatu kategori, nilainya akan 0 (cold).

scikit-learn menyediakan OneHotEncoder untuk mengubah nilai kategoris (categorical value) numerik atau integer menjadi one-hot vectors.

Selanjutnya kita akan melakukan encoding kategori menjadi one-hot vectors. fit_transform() meminta inputan dalam bentuk array 2D, namun housing_cat_encoded merupakan array 1D, sehingga kita harus melakukan reshaping terlebih dahulu (Function Numpy’s reshape() memungkinkan 1 dimensi menjadi -1 yang artinya “unspecified”: Nilainya di-infer dari panjang suatu array dan dimensi yang tersisa):

from sklearn.preprocessing import OneHotEncoder

encoder = OneHotEncoder()
housing_cat_1hot = encoder.fit_transform(housing_cat_encoded.reshape(-1,1))
housing_cat_1hot

Outputnya:

output Hasil Reshaping

Output Hasil Reshaping. Sumber Gambar: Dokumentasi Pribadi.

Output di atas merupakan sparse matrix SciPy, bukan array Numpy. Ini akan sangat berguna ketika kita memiliki atribut kategori yang memiliki ribuan kategori. Setelah menerapkan one-hot encoding, kita akan mendapatkan hasil berupa matriks dengan ribuan kolom, dan matriksnya akan dipenuhi dengan nilai “0”, kecuali satu baris saja yang akan memiliki nilai “1”.

Menghabiskan banyak sekali resource memory hanya untuk menyimpan angka nol tentu saja sangat mubazir dan tidak bijak, sehingga dibandingkan menggunakan sparse matrix hanya untuk menyimpan lokasi elemen tidak nol; akan lebih baik jika kita menggunakan array 2 dimensi biasa, dan untuk mengkonversikannya menjadi (dense) NumPy array sebagai berikut, kita bisa menggunakan method toarray():

housing_cat_1hot.toarray()

Output di console:

konversi Menjadi Dense NumPy Array

Konversi Sparse Matrix menjadi Dense NumPy Array. Sumber Gambar: Dokumentasi Pribadi.

Dan sebenarnya kita dapat mengaplikasikan kedua metode sekaligus (transformasi kategori teks menjadi kategori integer atau bilangan bulat, lalu dari bilangan bulat ke vector one-hot) menggunakan class LabelBinarizer, sebagai berikut:

from sklearn.preprocessing import LabelBinarizer

encoder = LabelBinarizer()
housing_cat_1hot = encoder.fit_transform(housing_cat)
housing_cat_1hot

Output di console:

outputLabelBinarizer

Output Label Binarizer. Sumber Gambar: Dokumentasi Pribadi.

LabelBinarizer() akan secara default menghasilkan output berupa dense NumPy array. Jika kita tetap menginginkan output berupa sparse matrix, kita bisa memasukkan parameter passing_sparse_output=True pada LabelBinarizer().

See you on the next post, semoga bermanfaat. Enjoy Machine Learning! 🙂

 

3. Bonus (Penampakan Penuh dari Hasil Analisis dengan Jupyter Notebook)

 

References & Further Reading

Geron A. (2017), Hands-On Machine Learning with Scikit-Learn and Tensorflow, O’Reilly Media.

Hauck T. (2014): scikit-learn Cookbook, Packt Publishing.

 

Sumber Gambar

https://pixabay.com/en/money-home-coin-investment-2724235/ oleh nattanan23.

Follow and like us:

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *