"""
# House Price Prediction

![house](https://www.listenmoneymatters.com/wp-content/uploads/2018/04/LMM-Cover-Images-2.jpg)
"""
"""
### by: [Al Fath Terry](https://www.instagram.com/al.fath.terry/)

Halo teman-teman, bagaimana kabar kalian semua? saya harap kalian semua baik-baik saja ya. 
 
Dikesempatan kali ini saya ingin mencoba untuk melakukan modeling kasus regresi, dengan goal utamanya adalah memprediksikan harga rumah.

Algoritma yang akan saya gunakan adalah Random Forest Regression (RF).

Sedikit penjelasan mengenai RF, kalau kalian familiar dengan algoritma Decision Tree (DT), pada dasarnya RF hanyalah algorima DT yang di Bootstrap Aggregating (Bagging), kita melakukan Bagging untuk melemahkan algoritma RF, karena RF memiliki sifat cenderung overfit.

- RF memiliki 4 parameter yaitu:
    1. N Estimators/Trees : Jumlah DT yang ingin digunakan.
    1. Max Depth : Kedalaman percabangan DT. 
    1. Minimal Samples Leaf: standar minimal yang digunakan untuk mencegah DT melakukan percabangan.
    1. Max Features : Mau berapa persen Fitur yang dipakai ditiap percabangan.
    
Saya tidak akan menjelaskan secara detil mengenai algoritmanya karena bukan itu fokus utama saya, yang ingin saya tekankan adalah gambaran workflow cara kerjanya mulai dari import data sampai submit.

- Workflow yang akan kita lakukan:
    1. Import packages dan data
    1. EDA
    1. Features Engineering
    1. Modeling
    1. Evaluasi
    1. Save model
    1. Submit

Yuk mari langsung ke pembahasannya.
"""
"""
# Import Packages
"""
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(style='whitegrid', font='Arial')

from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer

from scipy import stats
from scipy.stats import norm, skew

!pip install jcopml
from jcopml.pipeline import num_pipe, cat_pipe
from jcopml.utils import save_model, load_model
from jcopml.feature_importance import mean_score_decrease

pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
"""
# Import Data
"""
"""
### Data Train
"""
df_train = pd.read_csv("../input/house-prices-advanced-regression-techniques/train.csv")
df_train.head()
df_train.shape
"""
### Data Test
"""
df_test = pd.read_csv("../input/house-prices-advanced-regression-techniques/test.csv")
df_test.head()
df_test.shape
"""
### Gabungan Data Train dan Test
"""
df = pd.concat((df_train, df_test)).reset_index(drop=True)
df.head()
"""
### Simpan kolom 'Id'

Simpan kolom 'Id', lalu drop kolom tersebut. 

Kolom 'Id' tidak akan kita pakai saat modeling, maka dari itu kita drop. Namun kolom 'Id' akan dipakai saat submit nanti, jadi kolom 'Id' harus disimpan kedalam variabel terlebih dahulu.
"""
#save ID
train_id = df_train['Id']
test_id = df_test['Id']

#drop ID
df_train.drop("Id", axis = 1, inplace = True)
df_test.drop("Id", axis = 1, inplace = True)
df.drop("Id", axis = 1, inplace = True)
df.shape
"""
Kita memiliki 79 fitur, dan 1 target(SalePrice).
"""
"""
# EDA (Exploratory Data Analysis)
"""
"""
### 1. Missing Value

Plot Missing Value beserta Persentasenya. 

Data yang akan kita gunakan adalah data gabungan, kita ingin memastikan bahwa data testnya juga bebas dari missing value.
"""
total = df.isnull().sum().sort_values(ascending=False)
percent = (df.isnull().sum()/df.isnull().count()).sort_values(ascending=False)
missing_data = pd.concat([total, percent], axis=1, keys=['Total', 'Percent'])
missing_data = missing_data[missing_data['Percent'] > 0]
missing_data = missing_data.drop(['SalePrice'], axis=0)
missing_data
f, ax = plt.subplots(figsize=(10, 7))
plt.xticks(rotation='90')
sns.barplot(y=missing_data.loc['PoolQC':'BsmtHalfBath'].index, 
            x=missing_data.loc['PoolQC':'BsmtHalfBath']['Percent'], color='b');
plt.ylabel('Fitur', fontsize=15,);
plt.xlabel('Persen', fontsize=15);
plt.title('20 Fitur dengan missing value terbanyak', fontsize=15);
"""
Ternyata ada banyak sekali fitur yang memiliki missing value, haruskah kita drop fitur-fitur tersebut? atau impute? Untuk menjawabnya, kita harus lakukan analisa terlebih dahulu.

Pertama, kita analisa fitur yang memiliki missing value diatas 15%, fitur-fitur tersebut adalah:

1. PoolQC: Pool quality. NA = No Pool.
2. MiscFeature: Miscellaneous feature not covered in other categories
3. Alley: Type of alley access. NA = No alley access
4. Fence: Fence quality. NA = None. (tidak memiliki fitur seperti elevator, lapangan tennis, dll)
5. FireplaceQu: Fireplace quality. NA = No Fireplace

*note: Untuk keterangan yang lebih jelas kalian bisa membacanya di data deskripsi.* 

Penyebab absennya data terbilang cukup masuk akal, karena pada umumnya rumah memang tidak disediakan atribut ekslusif seperti itu. 

Jadi Untuk kelima fitur diatas (dan fitur lain yang memiliki sifat yang sama), kita akan impute dengan 'NONE', atau 0 untuk numeric. **'NONE' dan 0 menandakan tidak adanya atribut eksklusif yang ada dirumah itu.**

Dan sisanya kita akan aplikasikan seperti ini:

1. Fitur **LotFrontage**, karena bersifat numeric: impute dengan **median**.

2. Fitur **GarageType**, GarageFinish, GarageQual dan GarageCond: impute dengan **'NONE'**.

3. Fitur **GarageYrBlt**, GarageArea dan GarageCars: impute dengan **0**.

4. Fitur **BsmtFinSF1**, BsmtFinSF2, BsmtUnfSF, TotalBsmtSF, BsmtFullBath dan BsmtHalfBath: impute dengan **0**.

5. Fitur **BsmtQual, BsmtCond, BsmtExposure, BsmtFinType1 dan BsmtFinType2**: impute dengan **'NONE'**

6. Fitur **MasVnrArea dan MasVnrType**: impute **0** untuk Area, dan **'NONE'** untuk yang Type.

7. Fitur **MSZoning**: impute dengan **'RL'** (most frequent)

8. Fitur **Utilities** bisa kita **Drop** karena kurangmya variansi. (hanya satu yang memiliki 'NoSeWa')

9. Fitur **Funtional**: impute dengan **most frequent**.

10. Fitur **Electrical**: impute dengan **most frequent**.

11. Fitur **KitchenQual**: impute dengan **most frequent**.

12. Fitur **Exterior1st dan Exterior2nd**: impute dengan **'NONE'**

13. Fitur **GarageCars**: impute dengan **most frequent**.

14. Fitur **GarageArea**: impute dengan **mean**.

15. Fitur **TotalBsmtSF**: impute dengan **most Frquent**.
"""
"""
### 2. Analisa SalePrice (Target Variabel)
"""
"""
Kita awali dengan melihat gambaran deskriptif target.
"""
df_train['SalePrice'].describe()
"""
Sofar.. semuanya terlihat normal. Coba kita buat Histogram.
"""
sns.distplot(df_train['SalePrice'], fit=norm);
"""
Ternyata target menunjukkan sedikit Skew Positive. Nantinya kita akan transform target menjadi distribusi mormal menggunakan Box-Cox. 

Kita transform karena distribusi normal memudahkan model untuk menemukan global minimum (dasar dari loss plane).
"""
"""
### 3. Correlation Matrix

Correlation Matrix digunakan untuk menunjukkan korelasi koefisien antar variabel. 

Mendekati 1, artinya korelasi positif. Mendekati -1, artinya korelasi Negatif. mendekati 0, artinya tidak memiliki korelasi apapun.

Metode yang digunakan dalam menghitung korelasinya adalah Pearson Coefficient Correlation.

Untuk menghindari Multicollinearity, kita akan drop variabel yang memiliki tingkat korelasi yang tinggi.
"""
corrmat = df_train.corr(method='pearson')
f, ax = plt.subplots(figsize=(12,12))
sns.heatmap(corrmat , square=True, cmap='RdYlBu');
"""
Disini kita mendapati beberapa fitur yang berkorelasi tinggi. 

1. Yang pertama adalah korelasi antara 'TotalBsmtSF' dengan '1stFlrSF'.
    - TotalBsmtSF: Total square feet of basement area.
    - 1stFlrSF: First Floor square feet.

Kemungkinan besar, korelasi ini terjadi karena total luas ruangan basement dan first floor itu relatif sama dikebanyakan rumah, jadi mereka berbagi sifat yang sama, yaitu luas ruangan. Untuk kasus seperti ini, kita akan drop salah satu fitur yang memiliki korelasi lebih rendah dengan SalePrice untuk menghindari Multicollinearity.

Beberapa kasus yang serupa:

2. 'GarageYrBlt' dengan 'YearBuilt'
    - GarageYrBlt: Year garage was built.
    - YearBuilt: Original construction date.
    

3. 'TotRmsAbvGrd' dengan 'GrLivArea'
    - TotRmsAbvGrd: Total rooms above grade (does not include bathrooms).
    - GrLivArea: Above grade (ground) living area square feet.
    

4. 'GaraceCars' dengan 'GarageArea'
    - GarageCars: Size of garage in car capacity
    - GarageArea: Size of garage in square feet
"""
"""
###  4. Correlation Matrix 'SalePrice'
"""
f, ax = plt.subplots(figsize=(10, 10))
k = 11 #Top k variabel yang berkorelasi dengan SalePrice
cols = corrmat.nlargest(k, 'SalePrice')['SalePrice'].index
cm = np.corrcoef(df_train[cols].values.T)
sns.set(font_scale=1.25)
hm = sns.heatmap(cm, cbar=True, annot=True, fmt='.2f', square=True, annot_kws={'size': 12}, 
                 yticklabels=cols.values, xticklabels=cols.values, cmap='RdYlBu')
plt.show()
"""
10 Fitur diatas adalah fitur yang paling berkorelasi dengan SalePrice.

Sekarang saatnya kita seleksi fitur.

1. 'TotalBsmtSF' vs '1stFlrSF': keduanya memiliki korelasi yang sama dengan SalePrice, jadi kita akan pilih salah satunya dan yang akan kita drop adalah: '1stFlrSF'

2. 'GarageYrBlt' vs 'YearBuilt': Kita akan buang 'GarageYrBlt', karena fitur itu memiliki korelasi yang lebih rendah dengan SalePrice. (bahkan tidak masuk top 10).

3. 'TotRmsAbvGrd' vs 'GrLivArea': Kita akan buang 'TotRmsAbvGrd'

4. 'GaraceCars' vs 'GarageArea': Kita akan buang 'GarageArea'
"""
"""
### 5. Pair Plot (deteksi outliers)
"""
"""
Kita manfaatkan paiplot untuk memberikan gambaran visual mengenai kemungkinan adanya outliers.

***Note: cara 'koboi' seperti ini tidak dianjurkan, sebaiknya gunakan IQR atau metode lainnya untuk mendeteksi outliers***
"""
sns.set(style='whitegrid')
cols = ['SalePrice', 'OverallQual', 'GrLivArea', 'GarageCars', 'TotalBsmtSF', 'FullBath']
sns.pairplot(df_train[cols], size = 2.5)
plt.show();
"""
Kita menemukan Outliers di:

- Saleprice/GrLivArea
- SalePrice/TotalBsmtSF
"""
sns.set(style='whitegrid')
fig = plt.figure()
axes = fig.add_axes([0.1, 0.1, 0.8, 0.8])
sns.scatterplot(x=df_train['GrLivArea'], y=df_train['SalePrice']);

axes2 = fig.add_axes([1.1, 0.1, 0.8, 0.8])
sns.scatterplot(x=df_train['TotalBsmtSF'], y=df_train['SalePrice']);
"""
Terlihat di fitur GrLivArea kedua titik dikanan bawah adalah Outliers, kita akan membuangnya.

Begitu juga dengan 'TotalBsmtSF', dikanan bawah terlihat satu titik yang merupakan outlier, dan kita juga akan membuang point tersebut.
"""
"""
# Features Engineering
"""
"""
### 1. Hapus Outliers
"""
"""
- Outliers 'GrLivArea': dua point kanan bawah.
- Outliers 'TotalBsmtSF': satu pon kanan bawah.
"""
sns.set(style='whitegrid')
fig = plt.figure()
axes = fig.add_axes([0.1, 0.1, 0.8, 0.8])
sns.scatterplot(x=df_train['GrLivArea'], y=df_train['SalePrice']);

axes2 = fig.add_axes([1.1, 0.1, 0.8, 0.8])
sns.scatterplot(x=df_train['TotalBsmtSF'], y=df_train['SalePrice']);
"""
Hapus outliers.
"""
df_train.sort_values(by = 'GrLivArea', ascending = False)[:2]
df_train.drop(df_train.index[1298], inplace=True)
df_train.drop(df_train.index[523], inplace=True)

df_train.sort_values(by = 'TotalBsmtSF', ascending = False)[:1]
df_train.drop(df_train.index[1298], inplace=True)
"""
Hasil: 
"""
sns.set(style='whitegrid')
fig = plt.figure()
axes = fig.add_axes([0.1, 0.1, 0.8, 0.8])
sns.scatterplot(x=df_train['GrLivArea'], y=df_train['SalePrice'], color='g');

axes2 = fig.add_axes([1.1, 0.1, 0.8, 0.8])
sns.scatterplot(x=df_train['TotalBsmtSF'], y=df_train['SalePrice'], color='g');
"""
Done, outliers sudah hilang.
"""
"""
### 2. Impute Missing Values
"""
"""
Kita buat function untuk melakukan impute sekaligus membuang fitur penyebab Multicollinearity.
"""
def imputer(df):
    col_name = df.columns
    for col_name in df:
        df["PoolQC"] = df["PoolQC"].fillna("None")
        df["MiscFeature"] = df["MiscFeature"].fillna("None")
        df["Alley"] = df["Alley"].fillna("None")
        df["Fence"] = df["Fence"].fillna("None")
        df["FireplaceQu"] = df["FireplaceQu"].fillna("None")

        df['LotFrontage'] = df['LotFrontage'].fillna(df['LotFrontage'].median())

        for col in ('GarageType', 'GarageFinish', 'GarageQual', 'GarageCond'):
                        df[col] = df[col].fillna('None')

        for col in ('GarageYrBlt', 'GarageArea', 'GarageCars'):
                        df[col] = df[col].fillna(0)

        for col in ('BsmtFinSF1', 'BsmtFinSF2', 'BsmtUnfSF','TotalBsmtSF', 'BsmtFullBath', 'BsmtHalfBath'):
            df[col] = df[col].fillna(0)

        for col in ('BsmtQual', 'BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2'):
            df[col] = df[col].fillna('None')

        df["MasVnrType"] = df["MasVnrType"].fillna("None")
        df["MasVnrArea"] = df["MasVnrArea"].fillna(0)

        df['MSZoning'] = df['MSZoning'].fillna('RL')

        df["Functional"] = df["Functional"].fillna('Typ')

        df['Electrical'] = df['Electrical'].fillna('SBrkr')

        df['KitchenQual'] = df['KitchenQual'].fillna('TA')

        df['Exterior1st'] = df['Exterior1st'].fillna('VinylSd')
        df['Exterior2nd'] = df['Exterior2nd'].fillna('VinylSd')

        df['SaleType'] = df['SaleType'].fillna('WD')

        df['MSSubClass'] = df['MSSubClass'].fillna("None")

        df.drop(columns=['Utilities','1stFlrSF','GarageYrBlt','TotRmsAbvGrd','GarageArea'],inplace=True)
        return df
"""
Impute data train dan test.
"""
df_train = imputer(df_train)
print('df_train shape = {}'.format(df_train.shape))
df_train.head()
df_test = imputer(df_test)
print('df_test shape = {}'.format(df_test.shape))
df_test.head()
"""
Cek apakah masih ada missing value.
"""
df_train.isnull().any().value_counts()
df_test.isnull().any().value_counts()
"""
Done. Data sudah bersih ya, tidak ada lagi missing value.
"""
"""
### 3. Transform SalePrice
"""
sns.distplot(df_train['SalePrice'], fit=norm);
"""
Target kita memiliki skew positif, dan kita ingin mengubah target menjadi berdistribusi normal.

Caranya, kita akan pakai transformer 'box-cox', lalu kita scaling menggunakan standard scaler.
"""
target_transformer = ColumnTransformer([('target', num_pipe(scaling='standard',transform='box-cox'),['SalePrice'])])
y_target = target_transformer.fit_transform(df_train)
y_target = pd.Series(y_target.flatten())
sns.distplot(y_target, fit=norm);
"""
Done, sekarang sudah berdistribusi normal targetnya.
"""
"""
### 4. Pisahkan data Numeric dan Categoric
"""
"""
Kita bisa melakukan sorting berdasarkan data typenya, namun kita harus berhati-hati, tidak semua yang terlihat numeric adalah data numeric, bisa saja itu adalah data numeric yang berbentuk categoric, kita harus melakukan sorting manual lagi nantinya.
"""
num = df_train.select_dtypes(exclude=['object'])
cat = df_train.select_dtypes(include=['object'])
"""
Data Numeric.
"""
num.columns
"""
Data Categoric.
"""
cat.columns
"""
### 5. Pisahkan data Categoric Ordinal dengan Nominal
"""
"""
Untuk seleksi fitur seperti ini, kita tidak bisa melakukannya secara otomatis dengan mesin, karena fitur-fitur seperti ini butuh sense dari manusia untuk memisahkannya.
"""
cat.head()
# Ordinal
cat_or = cat[['Street', 'Alley', 'LandContour', 'LandSlope', 'ExterQual', 'ExterCond',
            'Foundation', 'BsmtQual', 'BsmtCond', 'BsmtExposure', 'BsmtFinType1',
            'BsmtFinType2', 'HeatingQC','CentralAir', 'KitchenQual', 'Functional',
            'FireplaceQu','GarageFinish', 'GarageQual', 'GarageCond', 'PavedDrive',
            'PoolQC', 'Fence', 'SaleCondition' ]]

# Nominal
cat_nom = cat[['MSZoning', 'LotShape', 'LotConfig', 'Neighborhood','Condition1', 'Condition2',
             'BldgType', 'HouseStyle', 'RoofStyle', 'RoofMatl', 'Exterior1st', 'Exterior2nd',
             'MasVnrType', 'Heating', 'Electrical', 'GarageType', 'MiscFeature', 'SaleType']]
"""
# Modeling
"""
"""
## **Always start with simple model**

Dikutip dari pernyataan Emmanuel Ameisen, Head of AI at Insight Data Science

>    **"The exact same approach of starting with a very simple model can be applied to machine learning engineering, and it usually proves very valuable. In fact, after seeing hundreds of projects go from ideation to finished products at Insight, we found that starting with a simple model as a baseline consistently led to a better end product."**
   
Tapi kenapa harus always start with simple model? alasannya:

1. simple model dibuat 10 kali lebih cepat, dan sudah menghasilkan 90% hasil yang kita ingikan.
1. Kedua, model yang kompleks tidak menjamin kenaikan tingkat score, padahal effort yang dikeluarkan bisa 10 kali lebih lama dari simple model.
1. Ketiga, bisa dijadikan sebagai baseline/benchmark.
1. keempat, mini EDA, simple model bisa menjadi sarana proses pengenalan data, sebelum masuk menyelami lebih jauh kondisi data.
"""
"""
### Dataset Splitting
"""
"""
Gunakan target yang sudah di Scaling dan Transform.
"""
X = df_train.drop(columns='SalePrice')
y = y_target #target yang sudah di scaling dan transform

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)
X_train.shape, X_test.shape, y_train.shape, y_test.shape
"""
### Preprocessor
"""
"""
Masukkan ke pipa Preprocessor.

- Untuk data Numeric, kita akan melakukan scaling dengan 'StandardScaler', transform 'yeo-johnson, dan impute pakai strategi mean.
- Untuk data Categoric Nominal, encode pakai 'one-hot'. 
- Untuk data categoric ordinal, encode pakai 'ordinal encoder'.

Kenapa pakai tuning seperti itu? alasannya karena think simple aja dulu, lakukan apa yang kira-kira bisa membuat model kita bagus, setelah itu baru kita lakukan evaluasi.
"""
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, PowerTransformer, OrdinalEncoder, OneHotEncoder
from sklearn.impute import SimpleImputer
num_pipe = Pipeline([
    ('imputer', SimpleImputer(strategy='mean')),
    ('scaler', StandardScaler()),
    ('transformer', PowerTransformer(method='yeo-johnson'))   
])

cat_ord_pipe = Pipeline([
    ('encoder', OrdinalEncoder())
])

cat_nom_pipe = Pipeline([
    ('encoder', OneHotEncoder(handle_unknown='ignore'))
])
preprocessor = ColumnTransformer([
    ('numeric1', num_pipe,  [
       'MSSubClass', 'LotFrontage', 'LotArea', 'OverallQual', 'OverallCond',
       'YearBuilt', 'YearRemodAdd', 'MasVnrArea', 'BsmtFinSF1', 'BsmtFinSF2',
       'BsmtUnfSF', 'TotalBsmtSF', '2ndFlrSF', 'LowQualFinSF', 'GrLivArea',
       'BsmtFullBath', 'BsmtHalfBath', 'FullBath', 'HalfBath', 'BedroomAbvGr',
       'KitchenAbvGr', 'Fireplaces', 'GarageCars', 'WoodDeckSF', 'OpenPorchSF',
       'EnclosedPorch', '3SsnPorch', 'ScreenPorch', 'PoolArea', 'MiscVal',
       'MoSold', 'YrSold']),
    
    ('categoric1', cat_ord_pipe , [
       'Street', 'Alley', 'LandContour', 'LandSlope', 'ExterQual', 'BsmtQual', 
       'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2','CentralAir', 'KitchenQual',
       'FireplaceQu','GarageFinish', 'GarageQual', 'PavedDrive', 'Fence', 'SaleCondition' ]),
    
    ('categoric2', cat_nom_pipe, [
       'MSZoning', 'LotShape', 'LotConfig', 'Neighborhood','Condition1', 'Condition2',
       'BldgType', 'HouseStyle', 'RoofStyle', 'RoofMatl', 'Exterior1st', 'Exterior2nd',
       'MasVnrType', 'Heating', 'Electrical', 'GarageType', 'MiscFeature', 'SaleType',
       'Foundation', 'HeatingQC','Functional','ExterCond','BsmtCond','GarageCond', 'PoolQC'])    
])
"""
Seperti yang sudah saya jelaskan di bagian intro, saya akan menggunakan algoritma Random Forest Regressor, lalu pakai Randomized Search sebagai Hyper Parameter.
"""
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import RandomizedSearchCV
pipeline = Pipeline([
    ('prep', preprocessor),
    ('algo', RandomForestRegressor(n_jobs=-1, random_state=42))
])

parameter = {'algo__n_estimators': np.arange(100,200),
 'algo__max_depth': np.arange(20,80),
 'algo__max_features': np.arange(0.1,1),
 'algo__min_samples_leaf': np.arange(1,20)}

model = RandomizedSearchCV(pipeline, parameter, cv=3, n_iter=50, n_jobs=-1, verbose=1, random_state=42)
model.fit(X_train, y_train)

print(model.best_params_)
print(model.score(X_train, y_train), model.best_score_, model.score(X_test, y_test))
"""
Hasilnya masih belum memuaskan ya, bagaimana kalau kita pakai target yang original, yuk kita coba.
"""
X = df_train.drop(columns='SalePrice')
y = df_train['SalePrice'] #target original

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)
X_train.shape, X_test.shape, y_train.shape, y_test.shape
model.fit(X_train, y_train)

print(model.best_params_)
print(model.score(X_train, y_train), model.best_score_, model.score(X_test, y_test))
"""
Hasilnya lebih bagus ternyata, jadi kita pakai target yang original saja ya.

Kira-kira apalagi yang mau dituning? fiturnya? modelnya? sebelum tuning lebih jauh, kita bisa memanfaatkan automl untuk mencari base model, dari situ kita akan tahu algoritma apa yang kira-kira cocok dengan data ini, yuk kita coba.
"""
"""
## AutoML
"""
"""
Kita gunakan automl yang ada di jcopml, gunakan AutoRegressor untuk kasus regresi.
"""
from jcopml.automl import AutoRegressor
X = df_train.drop(columns=["SalePrice"])
y = df_train['SalePrice']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)
X_train.shape, X_test.shape, y_train.shape, y_test.shape
"""
Automl yang ada di Jcopml hanya perlu memisahkan data numeric dan categoric.
"""
model = AutoRegressor([
    'MSSubClass', 'LotFrontage', 'LotArea', 'OverallQual', 'OverallCond',
    'YearBuilt', 'YearRemodAdd', 'MasVnrArea', 'BsmtFinSF1', 'BsmtFinSF2',
    'BsmtUnfSF', 'TotalBsmtSF', '2ndFlrSF', 'LowQualFinSF', 'GrLivArea',
    'BsmtFullBath', 'BsmtHalfBath', 'FullBath', 'HalfBath', 'BedroomAbvGr',
    'KitchenAbvGr', 'Fireplaces', 'GarageCars', 'WoodDeckSF', 'OpenPorchSF',
    'EnclosedPorch', '3SsnPorch', 'ScreenPorch', 'PoolArea', 'MiscVal',
    'MoSold', 'YrSold'], 
                      
    ['Street', 'Alley', 'LandContour', 'LandSlope', 'ExterQual', 'BsmtQual', 
     'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2','CentralAir', 'KitchenQual',
     'FireplaceQu','GarageFinish', 'GarageQual', 'PavedDrive', 'Fence', 
     'SaleCondition', 'MSZoning', 'LotShape', 'LotConfig', 'Neighborhood',
     'Condition1', 'Condition2', 'BldgType', 'HouseStyle', 'RoofStyle', 'RoofMatl', 
     'Exterior1st', 'Exterior2nd', 'MasVnrType', 'Heating', 'Electrical', 
     'GarageType', 'MiscFeature', 'SaleType', 'Foundation', 'HeatingQC','Functional',
     'ExterCond','BsmtCond','GarageCond', 'PoolQC'] )
"""
Lalu fit, auto ml akan mengeluarkan rekomendasi parameter yang kira-kira bisa menghasilkan model yang bagus.
"""
model.fit(X, y)
"""
Dan hasilnya mesin merekomendasikan XGBRegressor. Pertanyaannya, jika dibandingkan dengan algoritma lain, seberapa jauh perbedaannya? yuk kita coba plot perbandingannya.
"""
model.plot_results()
"""
Bisa dilihat, algoritma Random Forest kita tidak kalah jauh kok, mesin merekomendasikan XGBRegressor karena tidak terlalu overfit jika dibandingkan dua algoritma setelahnya. Namun perlu diingat, Automl ini tidak bisa dijadikan acuan pasti ya, karena ini hanya base model saja, tidak menutup kemungkinan ElasticNet dan RF bisa jadi lebih bagus jika dituning.

Sekarang kita coba automl khusus algoritma Random Forest, kira-kira parameter seperti apa yang akan direkomendasikan? seberapa jauh perbedaannya dengan XGBRegressor? kita coba ya.
"""
model.fit(X,y, algo='rf')
"""
Hasilnya memang overfit, tapi ingat, ini hanya base model saja, lagipula perbedaannya hanya 1-2% saja di tingkat akurasi data validation dan test. 
 
Idealnya untuk mencari algoritma terbaik kita harus bekerja sama, bentuk tim, lalu tiap individu diberi tugas untuk mengoptimalkan masing-masing algoritma, namun karena saya solo player (lol), jadi saya akan teruskan memakai Random Forest.

Sekarang kita coba untuk aplikasikan rekomendasi parameter dari automl

======================================================================================================================

Best Model Info

- algo                      | RandomForestRegressor
- algo__max_depth           | 43
- algo__max_features        | 0.4640573144099711
- algo__min_samples_leaf    | 1

- algo__n_estimators        | 123


Best Preprocessor Info 

- categorical_imputer       | SimpleImputer(add_indicator=False, strategy='most_frequent')
- categorical_encoder       | OneHotEncoder
- numerical_imputer         | KNNImputer(add_indicator=True, n_neighbors=5)

- numerical_scaler          | RobustScaler
"""
X = df_train.drop(columns=["SalePrice"])
y = df_train['SalePrice']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)
X_train.shape, X_test.shape, y_train.shape, y_test.shape
from sklearn.impute import KNNImputer
from sklearn.preprocessing import RobustScaler
num_pipe = Pipeline([
    ('imputer', KNNImputer(n_neighbors=5,add_indicator=True)),
    ('scaler', RobustScaler()),
    ('transformer', PowerTransformer(method='yeo-johnson'))   
])

cat_ord_pipe = Pipeline([
    ('encoder', OrdinalEncoder())
])

cat_nom_pipe = Pipeline([
    ('encoder', OneHotEncoder(handle_unknown='ignore'))
])
preprocessor = ColumnTransformer([
    ('numeric1', num_pipe,  [
       'MSSubClass', 'LotFrontage', 'LotArea', 'OverallQual', 'OverallCond',
       'YearBuilt', 'YearRemodAdd', 'MasVnrArea', 'BsmtFinSF1', 'BsmtFinSF2',
       'BsmtUnfSF', 'TotalBsmtSF', '2ndFlrSF', 'LowQualFinSF', 'GrLivArea',
       'BsmtFullBath', 'BsmtHalfBath', 'FullBath', 'HalfBath', 'BedroomAbvGr',
       'KitchenAbvGr', 'Fireplaces', 'GarageCars', 'WoodDeckSF', 'OpenPorchSF',
       'EnclosedPorch', '3SsnPorch', 'ScreenPorch', 'PoolArea', 'MiscVal',
       'MoSold', 'YrSold']),
    
    ('categoric1', cat_ord_pipe , [
       'Street', 'Alley', 'LandContour', 'LandSlope', 'ExterQual', 'BsmtQual', 
       'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2','CentralAir', 'KitchenQual',
       'FireplaceQu','GarageFinish', 'GarageQual', 'PavedDrive', 'Fence', 'SaleCondition' ]),
    
    ('categoric2', cat_nom_pipe, [
       'MSZoning', 'LotShape', 'LotConfig', 'Neighborhood','Condition1', 'Condition2',
       'BldgType', 'HouseStyle', 'RoofStyle', 'RoofMatl', 'Exterior1st', 'Exterior2nd',
       'MasVnrType', 'Heating', 'Electrical', 'GarageType', 'MiscFeature', 'SaleType',
       'Foundation', 'HeatingQC','Functional','ExterCond','BsmtCond','GarageCond', 'PoolQC'])    
])
pipeline = Pipeline([
    ('prep', preprocessor),
    ('algo', RandomForestRegressor(n_jobs=-1, random_state=42))
])

parameter = {'algo__n_estimators': [123],
 'algo__max_depth': [43],
 'algo__max_features': [0.4640573144099711],
 'algo__min_samples_leaf': [1]}

model = RandomizedSearchCV(pipeline, parameter, cv=3, n_iter=50, n_jobs=-1, verbose=1, random_state=42)
model.fit(X_train, y_train)

print(model.best_params_)
print(model.score(X_train, y_train), model.best_score_, model.score(X_test, y_test))
"""
Oh ya, seharusnya algoritma tree base seperti RF ini tidak perlu discaling ya, karena scaling tidak membantu RF, scaling akan berguna untuk algoritma non tree base seperti SVM, Linear Regression, dll.

kita coba bandingkan base model dengan hasil automl.

- base model:
    - train: 0.9825567617534368
    - val:   0.8691723628403284
    - test:  0.858116250180361
    
    

- Automl RF:
    - train: 0.9845873568062422
    - val:    0.8819071038468227
    - test:   0.9102346150696684
    
    
- Automl XGBRegressor:
    - Train: 0.9817541030994398
    - Valid: 0.9030264447822179
    - Test : 0.9191114621057876

Parameter yang diberikan automl terbukti berhasil meningkatkan akurasi data val dan test ya, good job mesin!.

Next kita bisa lakukan tuning algoritma RF nya lagi, feature engineering (binning, tambah fitur, dll), atau tuning hyper parameter memakai grid search atau bayesian search. Ada banyak hal yang bisa kita coba, dan itulah tugas Data SCientist, eksperimen!

Untuk sementara, saya akan biarkan seperti ini ya.

Ohya, jika kalian mau tuning algoritma RF, berikut saya berikan sense-nya:

- increase n_estimators => more averaging => var turun => mengurangi overfit
- increase max_depth => more decision => var meningkat => menambah overfit
- increase min_samples_split => prevent splitting => less depth => mengurangi overfit
- increase max_features => get better decision => less bias => menambah overfit
- increase min_impurity_decrease => prevent splitting => less depth => mengurangi overfit
- increase min_samples_leaf => prevent splitting => less depth => mengurangi overfit
"""
"""
## Save Model
"""
from jcopml.utils import save_model
save_model(model, "House Price - Advanced Regression Techniques.pkl")
"""
## Submit
"""
df_test.head()
predicted_prices = model.predict(df_test)
my_submission = pd.DataFrame({'Id': test_id, 'SalePrice': predicted_prices})

my_submission.to_csv('submission.csv', index=False)
"""
Ini adalah data submission kita:
"""
my_submission.head()
"""
Mungkin itu saja dari saya, jika ada pertanyaan atau masukan, bisa kalian tulis di kolom komentar atau boleh japri di ig saya:

## [Al Fath Terry](https://www.instagram.com/al.fath.terry/)

Dan mohon di upvote jika dirasa bermanfaat.

Terimakasih :)
"""