Cara Menangani Data Numerik dan Kategorikal menggunakan Python (Handling Numerical and Categorical Data using Python)

Assalamu‘alaikum wr. wb.

Halo gais! Data Numerik merupakan Data yang berupa Angka/Bilangan, sedangkan Data Kategorikal adalah Data yang berbentuk Kategori seperti Kumpulan Kategori dan setiap nilai mewakili beberapa Kategori, data Kategorik disebut juga Data Kualitatif yang berbentuk tidak beraturan. Untuk itulah, kita bisa mengolah Data tersebut ke dalam Machine Learning.

Handling Numerical and Categorical Data using Python


DATA NUMERIK (NUMERICAL DATA)

Sumber : Voxco.com (Blog)Oreilly.com, dan Geeksforgeeks.org

Data Numerik mengacu pada data yang berbentuk angka, bukan dalam bentuk bahasa atau deskriptif. Sering disebut sebagai data kuantitatif, data numerik dikumpulkan dalam bentuk angka dan berbeda dari segala bentuk jenis data angka karena kemampuannya untuk dihitung secara statistik dan aritmetika.

Contohnya Anda memiliki total jumlah karyawan. Anda menghitung jumlah karyawan laki-laki dan menguranginya dari jumlah total karyawan untuk mendapatkan jumlah karyawan perempuan. Karakteristik data numerik untuk dimanipulasi secara aritmetika membuatnya sangat cocok untuk analisis data statistik.

A. Jenis-jenis Data Numerik

Dua bentuk data numerik yang akan Anda lihat adalah data diskrit dan data kontinu. Kedua varian ini secara eksplisit digunakan untuk keperluan statistik dan penelitian serta terbukti memberikan data terbaik melalui metode penelitian.

Mari kita lihat lebih dalam perbedaannya satu sama lain :

1. Data Diskrit

Data Diskrit digunakan untuk mewakili item yang dapat dihitung. Ini dapat berbentuk numerik maupun kategorikal dan mengelompokkannya ke dalam daftar. Daftar ini bisa berupa hingga tak terhingga.

Data Diskrit pada dasarnya mengambil angka yang dapat dihitung seperti 1, 2, 3, 4, 5, dan seterusnya. Dalam kasus tak terhingga, angka-angka ini akan terus berlanjut.

Contoh : menghitung gula dari sebuah toples adalah hitungan yang dapat dihitung secara terbatas. Tetapi menghitung gula dari seluruh dunia adalah hitungan yang dapat dihitung secara tak terhingga.

2. Data Kontinu

Seperti namanya, bentuk ini memiliki data dalam bentuk interval atau sering disebut rentang. Data numerik kontinu mewakili pengukuran dan intervalnya jatuh pada garis bilangan. Oleh karena itu, ini tidak melibatkan penghitungan item.

Contoh: dalam ujian sekolah, siswa yang mendapatkan nilai 80%-100% masuk ke dalam kategori distinction, 60%-80% mendapat first class, dan di bawah 60% mendapat second class.

Data kontinu dibagi lagi menjadi 2 kategori, yaitu Interval dan Rasio.

B. Mengubah Skala Fitur

Penyekalaan ulang adalah tugas prapemrosesan umum dalam Machine Learning. Banyak dari algoritma yang dijelaskan nanti dalam buku ini akan mengasumsikan bahwa semua fitur berada pada skala yang sama, biasanya dari 0 hingga 1 atau -1 hingga 1. Ada beberapa teknik penyekalaan ulang, tetapi salah satu yang paling sederhana disebut penyekalaan min–maks. Penyekalaan min–maks menggunakan nilai minimum dan maksimum suatu fitur untuk menyesuaikan ulang nilai-nilai menjadi dalam suatu rentang. Secara khusus, min–maks menghitung :

Di mana x adalah vektor fitur, xi adalah elemen individual dari fitur x, dan x'i adalah elemen yang diubah ulang. Dalam contoh kita, kita bisa melihat dari array yang dihasilkan bahwa fitur tersebut telah berhasil diubah ulang menjadi antara 0 dan 1 :

array([[ 0.        ],
      [ 0.28571429],
      [ 0.35714286],
      [ 0.42857143],
      [ 1.        ]])

MinMaxScaler dari scikit-learn menawarkan dua opsi untuk menyesuaikan ulang suatu fitur. Salah satu opsi adalah menggunakan fit untuk menghitung nilai minimum dan maksimum dari fitur, kemudian menggunakan transform untuk menyesuaikan ulang fitur tersebut. Opsi kedua adalah menggunakan fit_transform untuk melakukan kedua operasi sekaligus. Tidak ada perbedaan matematis antara kedua opsi tersebut, tetapi terkadang ada manfaat praktis untuk menjaga operasi terpisah karena memungkinkan kita untuk menerapkan transformasi yang sama pada set data yang berbeda.

Gunakan MinMaxScaler scikit-learn untuk mengubah skala array fitur :

# Load libraries
import numpy as np
from sklearn import preprocessing

# Create feature
feature = np.array([[-500.5],
                    [-100.1],
                    [0],
                    [100.1],
                    [900.9]])

# Create scaler
minmax_scale = preprocessing.MinMaxScaler(feature_range=(0, 1))

# Scale feature
scaled_feature = minmax_scale.fit_transform(feature)

# Show feature
scaled_feature

Output :

array([[ 0.        ],
      [ 0.28571429],
      [ 0.35714286],
      [ 0.42857143],
      [ 1.        ]])

C. Standarisasi Fitur

Alternatif umum untuk penskalaan min-maks yang dibahas dalam Resep 4.1 adalah penskalaan ulang fitur agar mendekati standar terdistribusi normal. Untuk mencapai hal ini, kami menggunakan standardisasi untuk mentransformasikan data sedemikian rupa sehingga memiliki mean, x̄, sebesar 0 dan simpangan baku, σ, sebesar 1. Secara khusus, setiap elemen dalam fitur ditransformasikan sehingga :

Di mana x’i adalah bentuk yang telah distandarkan dari xi. Fitur yang telah diubah mewakili jumlah deviasi standar nilai asli dari nilai rata-rata fitur (juga disebut sebagai skor z dalam statistik).

Standarisasi adalah metode penskalaan umum untuk pra-pemrosesan pembelajaran mesin dan dalam pengalaman saya, lebih sering digunakan daripada penskalaan min-max. Namun, hal ini tergantung pada algoritma pembelajaran. Sebagai contoh, analisis komponen utama seringkali lebih baik menggunakan standarisasi, sementara penskalaan min-max sering disarankan untuk jaringan saraf (kedua algoritma dibahas lebih lanjut dalam buku ini). Sebagai aturan umum, saya akan merekomendasikan untuk beralih ke standarisasi kecuali jika Anda memiliki alasan khusus untuk menggunakan alternatif.

Kita dapat melihat efek standarisasi dengan melihat rata-rata dan deviasi standar keluaran solusi kita :

# Print mean and standard deviation
print("Mean:", round(standardized.mean()))
print("Standard deviation:", standardized.std())

Mean: 0.0
Standard deviation: 1.0

Jika data kami memiliki outlier yang signifikan, hal ini dapat berdampak negatif terhadap standardisasi kami dengan memengaruhi mean dan varians fitur. Dalam skenario ini, sebaiknya ubah skala fitur menggunakan median dan rentang kuartil. Di scikit-learn, kami melakukan ini menggunakan metode RobustScaler :

# Create scaler
robust_scaler = preprocessing.RobustScaler()

# Transform feature
robust_scaler.fit_transform(x)

array([[ -1.87387612],
       [ -0.875     ],
       [  0.        ],
       [  0.125     ],
       [ 10.61488511]])

D. Normalisasi Pengamatan

Banyak metode penskalaan ulang (misalnya penskalaan min-maks dan standardisasi) yang beroperasi pada fitur; namun, kami juga dapat mengubah skala pengamatan individual. Normalizer mengubah skala nilai pada observasi individual menjadi norma satuan (jumlah panjangnya adalah 1). Jenis penskalaan ulang ini sering digunakan ketika kita memiliki banyak fitur yang setara (misalnya, klasifikasi teks ketika setiap kata atau kelompok n-kata merupakan sebuah fitur).

1. Bentuk Euclidean

Normalizer menyediakan tiga pilihan norma dengan norma Euclidean (sering disebut L2) sebagai argumen default :

Dimana x adalah observasi individu dan xn adalah nilai observasi untuk fitur ke-n.

# Transform feature matrix
features_l2_norm = Normalizer(norm="l2").transform(features)

# Show feature matrix
features_l2_norm

array([[ 0.70710678,  0.70710678],
       [ 0.30782029,  0.95144452],
       [ 0.07405353,  0.99725427],
       [ 0.04733062,  0.99887928],
       [ 0.95709822,  0.28976368]])

2. Bentuk Manhattan

Sebagai alternatif, kita dapat menentukan Norma Manhattan (L1) :

Secara praktis, perhatikan bahwa norm='l1' mengubah skala nilai observasi sehingga jumlahnya menjadi 1, yang terkadang merupakan kualitas yang diinginkan :

# Print sum
print("Sum of the first observation\'s values:",
   features_l1_norm[0, 0] + features_l1_norm[0, 1])

Sum of the first observation's values: 1.0

E. Menghasilkan Fitur Polinomial dan Interaksi

Fitur polinomial sering dibuat ketika kita ingin memasukkan gagasan bahwa ada hubungan nonlinier antara fitur dan target. Sebagai contoh, kita mungkin curiga bahwa efek usia terhadap probabilitas memiliki kondisi medis utama tidak konstan seiring waktu, tetapi meningkat seiring bertambahnya usia. Kita dapat mengkodekan efek yang tidak konstan itu dalam suatu fitur, x, dengan menghasilkan bentuk-bentuk orde lebih tinggi dari fitur tersebut (X2X3, dll.).

Selain itu, seringkali kita menghadapi situasi di mana efek satu fitur tergantung pada fitur lainnya. Contoh sederhana adalah jika kita mencoba memprediksi apakah kopi kita manis dan kita memiliki dua fitur: 1) apakah kopi tersebut diaduk dan 2) apakah kita menambahkan gula. Secara individu, setiap fitur tidak memprediksi kelebihan manis kopi, tetapi kombinasi dari efek mereka melakukannya. Artinya, kopi hanya akan manis jika kopi tersebut memiliki gula dan diaduk. Efek dari setiap fitur terhadap target (kelebihan manis) saling bergantung. Kita dapat mengkodekan hubungan itu dengan menyertakan fitur interaksi yang merupakan hasil kali dari fitur individu.

Meskipun beberapa orang memilih untuk membuat fitur polinomial dan interaksi secara manual, scikit-learn menawarkan metode bawaan :

# Load libraries
import numpy as np
from sklearn.preprocessing import PolynomialFeatures

# Create feature matrix
features = np.array([[2, 3],
                     [2, 3],
                     [2, 3]])

# Create PolynomialFeatures object
polynomial_interaction = PolynomialFeatures(degree=2, include_bias=False)

# Create polynomial features
polynomial_interaction.fit_transform(features)

array([[ 2.,  3.,  4.,  6.,  9.],
       [ 2.,  3.,  4.,  6.,  9.],
       [ 2.,  3.,  4.,  6.,  9.]])

Parameter derajat menentukan derajat maksimum Polinomial. Misalnya, derajat=2 akan membuat fitur baru dipangkatkan kedua :

Selanjutnya, secara default PolynomialFeatures menyertakan fitur interaksi :

Selanjutnya, secara default PolynomialFeatures menyertakan fitur interaksi :

Kita dapat membatasi fitur yang dibuat hanya pada fitur interaksi dengan menyetel interaksi_hanya ke True :

interaction = PolynomialFeatures(degree=2,
              interaction_only=True, include_bias=False)

interaction.fit_transform(features)

array([[ 2.,  3.,  6.],
       [ 2.,  3.,  6.],
       [ 2.,  3.,  6.]])

F. Fitur Transformasi

Seringkali kita ingin melakukan transformasi kustom pada satu atau lebih fitur. Sebagai contoh, kita mungkin ingin membuat sebuah fitur yang merupakan logaritma alami dari nilai-nilai fitur yang berbeda. Hal ini dapat dilakukan dengan membuat sebuah fungsi dan kemudian memetakkannya ke fitur menggunakan FunctionTransformer dari scikit-learn atau apply dari pandas. Dalam solusi yang dibuat, kita membuat fungsi yang sangat sederhana, add_ten, yang menambahkan 10 pada setiap input, tetapi tidak ada alasan kita tidak dapat mendefinisikan fungsi yang jauh lebih kompleks.

Di scikit-learn, gunakan FunctionTransformer untuk menerapkan fungsi pada satu set fitur :

# Load libraries
import numpy as np
from sklearn.preprocessing import FunctionTransformer

# Create feature matrix
features = np.array([[2, 3],
                     [2, 3],
                     [2, 3]])

# Define a simple function
def add_ten(x):
    return x + 10

# Create transformer
ten_transformer = FunctionTransformer(add_ten)

# Transform feature matrix
ten_transformer.transform(features)

array([[ 2.,  3.,  6.],
       [ 2.,  3.,  6.],
       [ 2.,  3.,  6.]])

Kita dapat membuat transformasi yang sama di panda menggunakan apply :

# Load library
import pandas as pd

# Create DataFrame
df = pd.DataFrame(features, columns=["feature_1", "feature_2"])

# Apply function
df.apply(add_ten)

G. Mendeteksi Pencilan (Detecting Outliers)

Mendeteksi outlier sayangnya lebih bersifat seni daripada ilmu. Namun, metode umum adalah mengasumsikan bahwa data terdistribusi secara normal dan berdasarkan asumsi tersebut "menggambar" sebuah elips di sekitar data, mengklasifikasikan setiap observasi di dalam elips sebagai inlier (diberi label 1) dan setiap observasi di luar elips sebagai outlier (diberi label -1) :

# Load libraries
import numpy as np
from sklearn.covariance import EllipticEnvelope
from sklearn.datasets import make_blobs

# Create simulated data
features, _ = make_blobs(n_samples = 10,
                         n_features = 2,
                         centers = 1,
                         random_state = 1)

# Replace the first observation's values with extreme values
features[0,0] = 10000
features[0,1] = 10000

# Create detector
outlier_detector = EllipticEnvelope(contamination=.1)

# Fit detector
outlier_detector.fit(features)

# Predict outliers
outlier_detector.predict(features)

array([-1,  1,  1,  1,  1,  1,  1,  1,  1,  1])

Keterbatasan utama dari pendekatan ini adalah kebutuhan untuk menentukan parameter kontaminasi, yaitu proporsi observasi yang merupakan outlier—nilai yang tidak kita ketahui. Bayangkan kontaminasi sebagai perkiraan kebersihan data kita. Jika kita berharap data kita memiliki sedikit outlier, kita dapat mengatur kontaminasi menjadi sesuatu yang kecil. Namun, jika kami yakin bahwa data tersebut kemungkinan besar memiliki outlier, kami dapat menyetelnya ke nilai yang lebih tinggi.

Daripada melihat observasi secara keseluruhan, kita dapat melihat fitur individual dan mengidentifikasi nilai ekstrim pada fitur tersebut menggunakan rentang interkuartil (IQR) :

# Create one feature
feature = features[:,0]

# Create a function to return index of outliers
def indicies_of_outliers(x):
    q1, q3 = np.percentile(x, [25, 75])
    iqr = q3 - q1
    lower_bound = q1 - (iqr * 1.5)
    upper_bound = q3 + (iqr * 1.5)
    return np.where((x > upper_bound) | (x < lower_bound))

# Run function
indicies_of_outliers(feature)

(array([0]),)

IQR adalah selisih antara kuartil pertama dan ketiga dari sekumpulan data. Anda dapat menganggap IQR sebagai penyebaran sebagian besar data, dengan outlier sebagai observasi yang jauh dari konsentrasi data utama. Pencilan umumnya didefinisikan sebagai nilai apa pun yang 1,5 IQR lebih kecil dari kuartil pertama atau 1,5 IQR lebih besar dari kuartil ketiga.

Tidak ada teknik terbaik untuk mendeteksi outlier. Sebaliknya, kami memiliki kumpulan teknik dengan kelebihan dan kekurangannya masing-masing. Strategi terbaik kami sering kali adalah mencoba berbagai teknik (misalnya, deteksi berbasis EllipticEnvelope dan IQR) dan melihat hasilnya secara keseluruhan.

H. Menangani Pencilan (Handling Outliers)

Sama seperti mendeteksi outlier, tidak ada aturan yang baku untuk menanganinya. Bagaimana kita menanganinya seharusnya didasarkan pada dua aspek. Pertama, kita harus mempertimbangkan apa yang membuatnya menjadi outlier. Jika kita yakin bahwa itu adalah kesalahan dalam data seperti dari sensor rusak atau nilai yang salah kode, maka kita mungkin akan menghapus observasi tersebut atau menggantikan nilai outlier dengan NaN karena kita tidak bisa mempercayai nilai-nilai tersebut. Namun, jika kita percaya bahwa outlier adalah nilai ekstrem yang sah (misalnya, rumah [mansion] dengan 200 kamar mandi), maka menandainya sebagai outlier atau mengubah nilai mereka mungkin lebih tepat.

Kedua, bagaimana kita menangani outlier seharusnya didasarkan pada tujuan machine learning kita. Misalnya, jika kita ingin memprediksi harga rumah berdasarkan fitur rumah, kita mungkin dengan wajar berasumsi bahwa harga untuk mansion dengan lebih dari 100 kamar mandi dipengaruhi oleh dinamika yang berbeda dari pada rumah keluarga biasa. Selanjutnya, jika kita melatih model untuk digunakan sebagai bagian dari aplikasi web pinjaman rumah online, kita mungkin berasumsi bahwa pengguna potensial kita tidak akan mencakup miliarder yang mencari rumah mewah.

Jadi apa yang seharusnya kita lakukan jika kita memiliki outlier? Pikirkan mengapa mereka menjadi outlier, tetapkan tujuan akhir untuk data, dan, yang paling penting, ingatlah bahwa tidak membuat keputusan untuk menangani outlier juga merupakan keputusan dengan implikasi.

Satu poin tambahan: jika Anda memiliki outlier, standarisasi mungkin tidak sesuai karena rata-rata dan varians mungkin sangat dipengaruhi oleh outlier. Dalam hal ini, gunakan metode penskalaan ulang yang lebih tahan terhadap outlier seperti RobustScaler.

Biasanya kami memiliki tiga strategi yang dapat kami gunakan untuk menangani outlier. Pertama, kita bisa membuangnya :

# Load library
import pandas as pd

# Create DataFrame
houses = pd.DataFrame()
houses['Price'] = [534433, 392333, 293222, 4322032]
houses['Bathrooms'] = [2, 3.5, 2, 116]
houses['Square_Feet'] = [1500, 2500, 1500, 48000]

# Filter observations
houses[houses['Bathrooms'] < 20]

Output :

Kedua, kita dapat menandainya sebagai outlier dan memasukkannya sebagai fitur :

# Load library
import numpy as np

# Create feature based on boolean condition
houses["Outlier"] = np.where(houses["Bathrooms"] < 20, 0, 1)

# Show data
houses

Output :

Terakhir, kita dapat mengubah fitur untuk mengurangi efek outlier :

# Log feature
houses["Log_Of_Square_Feet"] = [np.log(x) for x in houses["Square_Feet"]]

# Show data
houses

Output :

I. Fitur Diskritisasi (Discretizating Features)

Diskritisasi dapat menjadi strategi yang bermanfaat ketika kita memiliki alasan untuk percaya bahwa fitur numerik seharusnya berperilaku lebih seperti fitur kategorikal. Misalnya, kita mungkin percaya bahwa ada sedikit perbedaan dalam kebiasaan pengeluaran antara yang berusia 19 dan 20 tahun, tetapi perbedaan yang signifikan antara yang berusia 20 dan 21 tahun (usia di Amerika Serikat ketika anak muda dapat mengonsumsi alkohol). Dalam contoh tersebut, dapat berguna untuk membagi individu dalam data kita menjadi mereka yang dapat mengonsumsi alkohol dan mereka yang tidak bisa. Demikian pula, dalam kasus lain mungkin berguna untuk mendiskritisasi data kita menjadi tiga atau lebih bin.

Dalam solusi, kita melihat dua metode diskritisasi—Binarizer dari scikit-learn untuk dua bin dan digitize dari NumPy untuk tiga atau lebih bin—namun, kita juga dapat menggunakan digitize untuk binarisasi fitur seperti Binarizer dengan hanya menentukan satu ambang :

# Bin feature
np.digitize(age, bins=[18])

array([[0],
       [0],
       [1],
       [1],
       [1]])

Bergantung pada bagaimana kita ingin memecah data, ada dua teknik yang bisa kita gunakan. Pertama, kita dapat melakukan binerisasi fitur berdasarkan beberapa ambang batas :

# Load libraries
import numpy as np
from sklearn.preprocessing import Binarizer

# Create feature
age = np.array([[6],
                [12],
                [20],
                [36],
                [65]])

# Create binarizer
binarizer = Binarizer(18)

# Transform feature
binarizer.fit_transform(age)

array([[0],
       [0],
       [1],
       [1],
       [1]])

Kedua, kita dapat membagi fitur numerik berdasarkan beberapa ambang batas :

# Bin feature
np.digitize(age, bins=[20,30,64])

array([[0],
       [0],
       [1],
       [2],
       [3]])

Perhatikan bahwa argumen untuk parameter bins menunjukkan tepi kiri setiap bin. Misalnya, argumen 20 tidak menyertakan elemen dengan nilai 20, hanya dua nilai yang lebih kecil dari 20. Kita dapat mengubah perilaku ini dengan menyetel parameter ke True :

# Bin feature
np.digitize(age, bins=[20,30,64], right=True)

array([[0],
       [0],
       [0],
       [2],
       [3]])

J. Pengelompokan Observasi Menggunakan Clustering

Secara khusus, kita menggunakan algoritma pembelajaran tanpa pengawasan seperti k-means untuk mengelompokkan observasi ke dalam kelompok. Hasil akhirnya adalah fitur kategorikal dengan observasi serupa menjadi anggota dari kelompok yang sama.

Jangan khawatir jika Anda tidak memahami semuanya sekarang: simpanlah ide bahwa pengelompokan dapat digunakan dalam pra-pemrosesan.

Jika Anda tahu bahwa Anda memiliki k kelompok, Anda dapat menggunakan pengelompokan k-means untuk mengelompokkan observasi yang serupa dan menghasilkan fitur baru yang berisi keanggotaan kelompok setiap observasi :

# Load libraries
import pandas as pd
from sklearn.datasets import make_blobs
from sklearn.cluster import KMeans

# Make simulated feature matrix
features, _ = make_blobs(n_samples = 50,
                         n_features = 2,
                         centers = 3,
                         random_state = 1)

# Create DataFrame
dataframe = pd.DataFrame(features, columns=["feature_1", "feature_2"])

# Make k-means clusterer
clusterer = KMeans(3, random_state=0)

# Fit clusterer
clusterer.fit(features)

# Predict values
dataframe["group"] = clusterer.predict(features)

# View first few observations
dataframe.head(5)

Output :

K. Menghapus Pengamatan dengan Nilai yang Hilang

Sebagian besar algoritma pembelajaran mesin tidak dapat menangani nilai yang hilang dalam larik target dan fitur. Oleh karena itu, kita tidak dapat mengabaikan nilai yang hilang dalam data kita dan harus menangani masalah tersebut selama pra-pemrosesan.

Solusi paling sederhana adalah menghapus setiap observasi yang mengandung satu atau lebih nilai yang hilang, tugas yang dapat dengan cepat dan mudah dilakukan menggunakan NumPy atau pandas.

Namun demikian, kita seharusnya sangat enggan untuk menghapus observasi dengan nilai yang hilang. Menghapusnya adalah pilihan drastis, karena algoritma kita kehilangan akses ke informasi yang terkandung dalam nilai non-hilang dari observasi tersebut.

Sama pentingnya, tergantung pada penyebab nilai yang hilang, menghapus observasi dapat memasukkan bias ke dalam data kita. Ada tiga jenis data yang hilang :

1. Missing Completely At Random (MCAR)

Kemungkinan bahwa suatu nilai hilang independen dari segala sesuatu. Sebagai contoh, responden survei melempar dadu sebelum menjawab pertanyaan: jika dia mendapatkan angka enam, dia melewatkan pertanyaan tersebut.

2. Missing At Random (MAR)

Kemungkinan bahwa suatu nilai hilang tidak sepenuhnya acak, tetapi bergantung pada informasi yang terkandung dalam fitur lain. Sebagai contoh, survei menanyakan tentang identitas gender dan gaji tahunan, dan wanita lebih mungkin melewatkan pertanyaan gaji; bagaimanapun, ketidakrespon mereka hanya bergantung pada informasi yang telah kita tangkap dalam fitur identitas gender kita.

3. Missing Not At Random (MNAR)

Kemungkinan bahwa suatu nilai hilang tidak acak dan bergantung pada informasi yang tidak tertangkap dalam fitur kita. Sebagai contoh, survei menanyakan tentang identitas gender dan wanita lebih mungkin melewatkan pertanyaan gaji, dan kita tidak memiliki fitur identitas gender dalam data kita.

Terkadang dapat diterima untuk menghapus observasi jika mereka adalah MCAR atau MAR. Namun, jika nilainya MNAR, fakta bahwa suatu nilai hilang adalah informasi itu sendiri. Menghapus observasi MNAR dapat menyuntikkan bias ke dalam data kita karena kita menghapus observasi yang dihasilkan oleh beberapa efek sistematis yang tidak teramati.

Menghapus pengamatan dengan nilai yang hilang mudah dilakukan dengan baris NumPy yang cerdas :

# Load library
import numpy as np

# Create feature matrix
features = np.array([[1.1, 11.1],
                     [2.2, 22.2],
                     [3.3, 33.3],
                     [4.4, 44.4],
                     [np.nan, 55]])

# Keep only observations that are not (denoted by ~) missing
features[~np.isnan(features).any(axis=1)]

array([[  1.1,  11.1],
       [  2.2,  22.2],
       [  3.3,  33.3],
       [  4.4,  44.4]])

Alternatifnya, kita dapat menghilangkan observasi yang hilang menggunakan pandas :

# Load library
import pandas as pd

# Load data
dataframe = pd.DataFrame(features, columns=["feature_1", "feature_2"])

# Remove observations with missing values
dataframe.dropna()

Output :

L. Mengabaikan Nilai yang Hilang

Ada dua strategi utama untuk menggantikan data yang hilang dengan nilai pengganti, masing-masing memiliki kelebihan dan kelemahan. Pertama, kita dapat menggunakan pembelajaran mesin untuk memprediksi nilai dari data yang hilang. Untuk melakukannya, kita memperlakukan fitur dengan nilai yang hilang sebagai vektor target dan menggunakan subset fitur yang tersisa untuk memprediksi nilai yang hilang. Meskipun kita dapat menggunakan berbagai algoritma pembelajaran mesin untuk mengganti nilai, pilihan populer adalah KNN. Penjelasan singkatnya adalah bahwa algoritma ini menggunakan k observasi terdekat (sesuai dengan beberapa metrik jarak) untuk memprediksi nilai yang hilang. Dalam solusi kami, kami memprediksi nilai yang hilang menggunakan lima observasi terdekat.

Kekurangan dari KNN adalah bahwa untuk mengetahui observasi mana yang paling dekat dengan nilai yang hilang, itu perlu menghitung jarak antara nilai yang hilang dan setiap observasi. Ini wajar dalam dataset yang lebih kecil, tetapi dengan cepat menjadi masalah jika sebuah dataset memiliki jutaan observasi.

Strategi alternatif dan lebih dapat diskalakan adalah mengisi semua nilai yang hilang dengan beberapa nilai rata-rata. Sebagai contoh, dalam solusi kami, kami menggunakan scikit-learn untuk mengisi nilai yang hilang dengan nilai rata-rata fitur. Nilai yang diimputasi seringkali tidak seberapa dekat dengan nilai sebenarnya seperti ketika kita menggunakan KNN, tetapi kita dapat dengan mudah menyesuaikan pengisian rata-rata dengan data yang berisi jutaan observasi.

Jika kita menggunakan imputasi, ide bagus untuk membuat fitur biner yang menunjukkan apakah atau tidak observasi berisi nilai yang diimputasi.

Jika jumlah data Anda sedikit, prediksi nilai yang hilang menggunakan K-Nearest Neighbour (KNN) :

# Load libraries
import numpy as np
from fancyimpute import KNN
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_blobs

# Make a simulated feature matrix
features, _ = make_blobs(n_samples = 1000,
                         n_features = 2,
                         random_state = 1)

# Standardize the features
scaler = StandardScaler()
standardized_features = scaler.fit_transform(features)

# Replace the first feature's first value with a missing value
true_value = standardized_features[0,0]
standardized_features[0,0] = np.nan

# Predict the missing values in the feature matrix
features_knn_imputed = KNN(k=5, verbose=0).fit_transform(standardized_features)

# Compare true and imputed values
print("True Value:", true_value)
print("Imputed Value:", features_knn_imputed[0,0])

True Value: 0.8730186114
Imputed Value: 1.09553327131

Sebagai alternatif, kita dapat menggunakan modul Imputer scikit-learn untuk mengisi nilai yang hilang dengan nilai rata-rata, median, atau nilai paling sering dari fitur tersebut. Namun, kami biasanya mendapatkan hasil yang lebih buruk daripada KNN :

# Load library
from sklearn.preprocessing import Imputer

# Create imputer
mean_imputer = Imputer(strategy="mean", axis=0)

# Impute values
features_mean_imputed = mean_imputer.fit_transform(features)

# Compare true and imputed values
print("True Value:", true_value)
print("Imputed Value:", features_mean_imputed[0,0])

True Value: 0.8730186114
Imputed Value: -3.05837272461


DATA KATEGORIK (CATEGORICAL DATA)

Sumber : Datacamp.com dan Geeksforgeeks.org

Data yang dapat dikategorikan tetapi tidak memiliki hierarki atau urutan bawaan disebut sebagai data kategorikal. Dengan kata lain, tidak ada koneksi matematis antara kategori-kategori tersebut. Jenis kelamin seseorang (pria/wanita), warna mata (biru, hijau, coklat, dll.), jenis kendaraan yang mereka kendarai (Sedan, SUV, Truk, dll.), atau jenis buah yang mereka konsumsi (apel, pisang, jeruk, dll.) adalah contoh dari data kategorikal.

Dalam tutorial ini, kami akan menguraikan metode penanganan dan preprocessing untuk data kategorikal. Sebelum membahas pentingnya mempersiapkan data kategorikal untuk model machine learning, kami akan menentukan terlebih dahulu data kategorikal dan jenis-jenisnya. Selain itu, kami akan melihat beberapa metode encoding, metode analisis dan visualisasi data kategorikal dalam Python, dan ide-ide lebih lanjut seperti data kategorikal dengan kardinalitas tinggi dan berbagai metode Encoding.

A. Kumpulan Data Kategoris (Categorical Dataset)

Informasi direpresentasikan menggunakan dua bentuk data yang berbeda: data kategorikal dan data numerik. Data yang dapat dikategorikan atau dikelompokkan bersama disebut sebagai data kategorikal. Pria dan wanita termasuk dalam kategori gender, warna merah, hijau, dan biru termasuk dalam kategori warna, dan kategori negara mungkin mencakup AS, Kanada, Meksiko, dll.

Data numerik merujuk pada data yang dapat diungkapkan sebagai angka. Contoh data numerik meliputi tinggi, berat, dan suhu.

Secara sederhana, data kategorikal adalah informasi yang dapat dimasukkan ke dalam kategori, sedangkan data numerik adalah informasi yang dapat diungkapkan sebagai angka. Karena sebagian besar algoritma machine learning dibuat untuk beroperasi dengan data numerik, data kategorikal dihandle secara berbeda dari data numerik dalam bidang ini. Sebelum data kategorikal dapat digunakan sebagai input untuk model machine learning, harus diubah terlebih dahulu menjadi data numerik. Proses mengonversi data kategorikal menjadi representasi numerik dikenal sebagai encoding.

Terdapat 2 (Dua) Jenis Data Kategorikal yaitu Nominal dan Ordinal.

1. Data Nominal

Data Nominal adalah Data Kategorikal yang dapat dibagi menjadi kelompok, tetapi kelompok-kelompok ini tidak memiliki hierarki atau urutan intrinsik. Contoh data nominal termasuk merek-merek (Coca-Cola, Pepsi, Sprite), Variasi Topping Pizza (Pepperoni, Jamur, Bawang), dan warna rambut (Pirang, Coklat, Hitam, dll.).

2. Data Ordinal

Di sisi lain, data ordinal menggambarkan informasi yang dapat dikategorikan dan memiliki urutan atau peringkat yang jelas. Tingkat pendidikan (SMA, Sarjana/S1, Magister/S2), tingkat kepuasan kerja (sangat puas, puas, netral, tidak puas, sangat tidak puas), dan rating bintang (1 Bintang, 2 Bintang, 3 Bintang, 4 Bintang, 5 Bintang) adalah beberapa contoh data ordinal.

Dengan memberikan nilai numerik pada setiap kategori yang mencerminkan urutan atau peringkatnya, data ordinal dapat diubah menjadi data numerik dan digunakan dalam machine learning. Ini dapat berguna untuk algoritma yang peka terhadap ukuran data masukan.

Pembedaan antara data nominal dan ordinal tidak selalu jelas dalam praktiknya dan dapat bervariasi tergantung pada kasus penggunaan tertentu. Misalnya, sementara beberapa orang mungkin melihat "rating bintang" sebagai data ordinal, yang lain mungkin melihatnya sebagai data nominal. Hal paling penting adalah menyadari sifat data Anda dan memilih strategi encoding yang paling baik menangkap hubungan dalam data Anda.

B. Memahami Tipe Data di Pandas

Pandas, sebuah perpustakaan sumber terbuka Python yang banyak digunakan, digunakan untuk analisis dan manipulasi data. Ini memiliki kemampuan yang kuat untuk menangani data terstruktur, termasuk data frame dan seri yang dapat menangani data tabular dengan baris dan kolom yang diberi label.

Pandas juga menyediakan beberapa fungsi untuk membaca dan menulis berbagai jenis file (csv, parquet, basis data, dll.). Ketika Anda membaca file menggunakan pandas, setiap kolom diberikan tipe data berdasarkan inferensi. Berikut adalah semua tipe data yang mungkin dapat diberikan pandas :

  • Numerik : Ini termasuk bilangan bulat dan angka desimal. Data numerik biasanya digunakan untuk analisis kuantitatif dan operasi matematika.
  • String : Tipe data ini digunakan untuk mewakili data tekstual seperti nama, alamat, dan deskripsi.
  • Boolean : Tipe data ini hanya dapat memiliki dua nilai mungkin: Benar atau Salah. Data boolean sering digunakan untuk operasi logika dan penyaringan.
  • Datetime : Tipe data ini digunakan untuk mewakili tanggal dan waktu. Pandas memiliki alat yang kuat untuk memanipulasi data datetime.
  • Kategorikal : Tipe data ini mewakili data yang mengambil sejumlah nilai terbatas. Data kategorikal sering digunakan untuk pengelompokan dan agregasi data.
  • Objek : Tipe data ini adalah penangkap untuk data yang tidak masuk ke dalam kategori lainnya. Ini dapat mencakup berbagai jenis data, seperti daftar, kamus, dan objek lainnya.

Data yang tidak masuk ke dalam jenis data lainnya, termasuk string, jenis campuran, atau objek lainnya, direpresentasikan oleh jenis data kategori dan objek dalam pandas. Namun, keduanya memiliki beberapa perbedaan signifikan yang memengaruhi seberapa baik mereka berfungsi dan berperforma.

1. Jenis Data Kategorikal

Jenis data kategorikal dibuat untuk informasi yang hanya memiliki beberapa nilai mungkin, seperti kategori atau label. Secara internal, data kategorikal direpresentasikan sebagai kumpulan angka, yang dapat mempercepat beberapa operasi dan menghemat memori dibandingkan dengan jenis data objek yang sesuai. Selain itu, data kategorikal dapat diatur secara logis dan memfasilitasi prosedur pengelompokan dan agregasi yang efektif.

2. Jenis Data Objek

Di sisi lain, jenis data objek berfungsi sebagai tempat penyimpanan untuk informasi yang tidak masuk ke dalam jenis data lainnya. Daftar, kamus, dan objek lainnya hanyalah beberapa contoh dari banyak jenis data yang dapat disertakan. Meskipun data objek memiliki fleksibilitas yang besar, ia juga dapat lebih lambat dan mengonsumsi lebih banyak memori dibandingkan dengan data kategorikal dengan ukuran yang sama, dan tidak dapat menjalani beberapa operasi khusus yang mungkin dilakukan dengan data kategori.

Secara umum, Anda mungkin ingin mempertimbangkan untuk menggunakan jenis data kategorikal jika data Anda memiliki sedikit nilai mungkin dan Anda berniat melakukan banyak pengelompokan atau agregasi. Jenis data objek biasanya merupakan pilihan yang aman dalam kasus lainnya. Namun, jenis data yang ideal pada akhirnya bergantung pada kasus penggunaan unik Anda dan properti data Anda.

Mari kita lihat contohnya dengan membaca file CSV dari GitHub.

# read csv using pandas
data = pd.read_csv('https://raw.githubusercontent.com/pycaret/pycaret/master/datasets/diamond.csv')

# check the head of dataframe
data.head()

Output :

Bisakah Anda mengidentifikasi kolom mana yang bersifat kategorikal vs. numerik? Semua kolom dalam contoh ini bersifat kategoris kecuali Carat Weight dan Price. Mari kita lihat apakah kita benar tentang hal ini dengan memeriksa tipe data default.

# check the data types
data.dtypes

Output :

Carat Weight    float64
Cut              object
Color            object
Clarity          object
Polish           object
Symmetry         object
Report           object
Price             int64
dtype: object

Perhatikan bagaimana Harga ditetapkan dengan tipe int64, Berat Karat sebagai float64, dan kolom lainnya adalah objek, persis seperti yang kita harapkan.

C. Menganalisis Fitur Kategoris dengan Python

Ada beberapa fungsi di pandas, pustaka analisis data populer dengan Python, yang memungkinkan Anda menganalisis tipe data kategorikal dalam kumpulan data Anda dengan cepat. Mari kita periksa satu per satu :

1. Value Counts

value_counts() adalah fungsi dalam perpustakaan pandas yang mengembalikan frekuensi setiap nilai unik dalam kolom data kategorikal. Fungsi ini berguna ketika Anda ingin mendapatkan pemahaman cepat tentang distribusi variabel kategorikal, seperti kategori-kategori paling umum dan frekuensinya.

# read csv using pandas
import pandas as pd
data = pd.read_csv('https://raw.githubusercontent.com/pycaret/pycaret/master/datasets/diamond.csv')

# check value counts of Cut column
data['Cut'].value_counts()

Output :

Cut
Ideal              2482
Very Good          2428
Good                708
Signature-Ideal     253
Fair                129
Name: count, dtype: int64

Jika Anda ingin memvisualisasikan distribusi, Anda dapat menggunakan Library plotly untuk menggambar plot batang interaktif.

import plotly.express as px

cut_counts = data['Cut'].value_counts()
fig = px.bar(x=cut_counts.index, y=cut_counts.values)
fig.show()

Output :

2. Group by

groupby() adalah sebuah fungsi dalam Pandas yang memungkinkan Anda untuk mengelompokkan data berdasarkan satu atau lebih kolom dan menerapkan fungsi agregat seperti jumlah, rata-rata, dan hitungan. Fungsi ini berguna ketika Anda ingin melakukan analisis yang lebih kompleks pada data kategorikal, seperti menghitung rata-rata variabel numerik untuk setiap kategori. Mari lihat contohnya :

# read csv using pandas
import pandas as pd

data = pd.read_csv('https://raw.githubusercontent.com/pycaret/pycaret/master/datasets/diamond.csv')

# Convert 'Cut' column to categorical
data['Cut'] = data['Cut'].astype('category')

# average carat weight and price by Cut
result = data.groupby(by='Cut').mean()

# Display the result
print(result)

Output :

Ini hanya akan mengembalikan data frame dengan kolom numerik saja. Parameter by di dalam metode groupby menentukan kolom di mana Anda ingin melakukan operasi pengelompokan, dan kemudian mean() di luar tanda kurung adalah metode agregasi.

Output dapat diartikan sebagai rata-rata harga berlian dengan potongan yang baik adalah 5.886, dan rata-rata beratnya adalah 1.05 dibandingkan dengan rata-rata harga 11.485 untuk berlian dengan potongan Very Good.

3. Cross tab

crosstab() adalah sebuah fungsi dalam pandas yang membuat tabel tabulasi silang, yang menunjukkan distribusi frekuensi dari dua atau lebih variabel kategorikal. Fungsi ini berguna ketika Anda ingin melihat hubungan antara dua atau lebih variabel kategorikal, seperti bagaimana frekuensi satu variabel terkait dengan variabel lain.

# read csv using pandas
import pandas as pd
data = pd.read_csv('https://raw.githubusercontent.com/pycaret/pycaret/master/datasets/diamond.csv')

# cross tab of Cut and Color
pd.crosstab(index=data['Cut'], columns=data['Color'])

Output :

Output dari fungsi crosstab dalam pandas adalah tabel yang menunjukkan distribusi frekuensi dari dua atau lebih variabel kategorikal. Setiap baris tabel mewakili kategori unik dalam salah satu variabel, dan setiap kolom mewakili kategori unik dalam variabel lainnya. Entri dalam tabel adalah hitungan frekuensi dari kombinasi kategori dalam dua variabel tersebut.

4. Pivot Table

pivot_table() adalah sebuah fungsi dalam Pandas yang membuat tabel pivot, yang mirip dengan tabel tabulasi silang namun dengan lebih banyak fleksibilitas. Fungsi ini berguna ketika Anda ingin menganalisis beberapa variabel kategorikal dan hubungan mereka dengan satu atau lebih variabel numerik. Tabel pivot memungkinkan Anda menggabungkan data dengan berbagai cara dan menampilkan hasilnya dalam bentuk yang ringkas.

# read csv using pandas
import pandas as pd
import numpy as np

data = pd.read_csv('https://raw.githubusercontent.com/pycaret/pycaret/master/datasets/diamond.csv')

# create pivot table
pd.pivot_table(data, values='Price', index='Cut', columns='Color', aggfunc=np.mean)

Output :

Tabel ini menunjukkan harga rata-rata setiap potongan berlian untuk setiap warna. Baris mewakili potongan berlian yang berbeda, kolom mewakili warna berlian yang berbeda, dan entri dalam tabel adalah harga rata-rata berlian.

Fungsi pivot_table berguna ketika Anda ingin merangkum dan membandingkan data numerik melintasi beberapa variabel dalam format tabel. Fungsi ini memungkinkan Anda menggabungkan data menggunakan berbagai fungsi (seperti mean, sum, count, dll.) dan mengorganisirkannya dalam format yang mudah dibaca dan dianalisis.

D. Mengkodekan Fitur Kategorikal dengan Python

Data kategorikal biasanya tidak dapat langsung diolah oleh algoritma machine learning, karena sebagian besar algoritma dirancang untuk beroperasi dengan data numerik saja. Oleh karena itu, sebelum fitur kategorikal dapat digunakan sebagai input untuk algoritma machine learning, mereka harus dienkoding sebagai nilai numerik.

Ada beberapa teknik untuk mengenkoding fitur kategorikal, termasuk one-hot encoding, ordinal encoding, dan target encoding. Pemilihan teknik enkoding tergantung pada karakteristik khusus data dan persyaratan dari algoritma machine learning yang digunakan.

1. One-hot encoding

One-hot encoding adalah proses representasi data kategorikal sebagai serangkaian nilai biner, di mana setiap kategori dipetakan ke nilai biner unik. Dalam representasi ini, hanya satu bit yang diatur ke 1, dan sisanya diatur ke 0, oleh karena itu disebut "one hot." Ini umumnya digunakan dalam machine learning untuk mengubah data kategorikal menjadi format yang dapat diolah oleh algoritma.

2. Pandas categorical to Numeric

Cara untuk mencapai ini di pandas adalah dengan menggunakan metode pd.get_dummies(). Ini adalah fungsi dalam perpustakaan Pandas yang dapat digunakan untuk melakukan one-hot encoding pada variabel kategorikal dalam DataFrame. Ini mengambil DataFrame dan mengembalikan DataFrame baru dengan kolom biner untuk setiap kategori. Berikut adalah contoh penggunaannya!

Misalkan kita memiliki data frame dengan kolom "fruit" yang berisi data kategorikal :

import pandas as pd

# generate df with 1 col and 4 rows
data = {
    "fruit": ["apple", "banana", "orange", "apple"]
}

# show head
df = pd.DataFrame(data)
df.head()

Output :

import pandas as pd

# generate df with 1 col and 4 rows
data = {
    "fruit": ["apple", "banana", "orange", "apple"]
}

# apply get_dummies function
df_encoded = pd.get_dummies(df["fruit"])
df_encoded .head()

Output :


Meskipun pandas.get_dummies mudah digunakan, pendekatan yang lebih umum adalah menggunakan OneHotEncoder dari perpustakaan sklearn, terutama ketika Anda melakukan tugas machine learning. Perbedaan utamanya adalah pandas.get_dummies tidak dapat mempelajari encoding; itu hanya dapat melakukan one-hot encoding pada dataset yang Anda lewatkan sebagai input. Di sisi lain, sklearn.OneHotEncoder adalah kelas yang dapat disimpan dan digunakan untuk mentransformasi dataset lain yang masuk di masa depan.

import pandas as pd

# generate df with 1 col and 4 rows
data = {
    "fruit": ["apple", "banana", "orange", "apple"]
}

# one-hot-encode using sklearn
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder()
encoded_results = encoder.fit_transform(df).toarray()

print("\nOne-Hot Encoded Array:")
print(encoded_results)

Output :

One-Hot Encoded Array:
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]
 [1. 0. 0.]]

3. Label Encoding

Label Encoding adalah teknik untuk mengkodekan variabel kategorikal sebagai nilai numerik, dengan setiap kategori diberi nomor bulat unik. Misalnya, anggap kita memiliki variabel kategorikal "warna" dengan tiga kategori: "merah," "hijau," dan "biru." Kita dapat mengkodekan kategori-kategori ini menggunakan label encoding sebagai berikut (merah: 0, hijau: 1, biru: 2).

Label encoding dapat berguna untuk beberapa algoritma machine learning yang memerlukan input numerik, karena memungkinkan data kategorikal direpresentasikan dengan cara yang dapat dimengerti oleh algoritma. Namun, penting untuk diingat bahwa label encoding memperkenalkan penataan sembarang dari kategori-kategori tersebut, yang mungkin tidak selalu mencerminkan hubungan yang bermakna di antara mereka. Dalam beberapa kasus, hal ini dapat menyebabkan masalah dalam analisis, terutama jika penataannya diartikan sebagai memiliki semacam hubungan ordinal.

Pengkodean Data Label

4. Perbandingan One-hot dan Label Encoding

One-hot encoding dan label encoding keduanya adalah teknik untuk mengkodekan variabel kategorikal sebagai nilai numerik, namun keduanya memiliki properti yang berbeda dan sesuai untuk kasus penggunaan yang berbeda.

One-hot encoding merepresentasikan setiap kategori sebagai kolom biner, dengan 1 menunjukkan keberadaan kategori dan 0 menunjukkan ketidakhadirannya. Misalnya, anggap kita memiliki variabel kategorikal "warna" dengan tiga kategori: "merah," "hijau," dan "biru." One-hot encoding akan merepresentasikan variabel ini sebagai tiga kolom biner :

One-hot encoding sesuai ketika kategori-kategori tidak memiliki penataan atau hubungan intrinsik satu sama lain. Ini karena one-hot encoding memperlakukan setiap kategori sebagai entitas terpisah tanpa hubungan dengan kategori lainnya. One-hot encoding juga berguna ketika jumlah kategori relatif kecil, karena jumlah kolom dapat menjadi sulit diatasi untuk jumlah kategori yang sangat besar.

Label encoding, di sisi lain, merepresentasikan setiap kategori sebagai bilangan bulat unik. Misalnya, variabel "warna" dengan tiga kategori dapat di-label-encoded sebagai :

Label encoding sesuai ketika kategori-kategori memiliki penataan atau hubungan alami satu sama lain, seperti dalam kasus variabel ordinal seperti "kecil," "sedang," dan "besar." Dalam kasus ini, nilai integer yang diberikan pada kategori harus mencerminkan penataan kategori. Label encoding juga dapat berguna ketika jumlah kategori sangat besar, karena mengurangi dimensi data.

Secara umum, one-hot encoding lebih umum digunakan dalam aplikasi machine learning, karena lebih fleksibel dan menghindari masalah ambiguitas dan penataan sembarang yang dapat muncul dengan label encoding. Namun, label encoding dapat berguna dalam konteks tertentu di mana kategori memiliki penataan alami atau ketika berurusan dengan jumlah kategori yang sangat besar.


Itulah Cara Menangani Data Numerik dan Kategorikal menggunakan Python (Handling Numerical and Categorical Data using Python) dalam Machine Learning (ML). Mohon maaf apabila ada kesalahan apapun.

Terima Kasih 😄😘👌👍 :)

Wassalamu‘alaikum wr. wb. 

Post a Comment

Previous Post Next Post