본문 바로가기
공부기록/데이터사이언스

랜덤 포레스트 실습 with 데싸노트

by tankwoong 2024. 3. 27.
반응형

랜덤포레스트(Random Forest) 모델은 독립적인 트리를 여러 개 만들어서 결정 트리의 단점인 오버피팅 문제를 완화시켜 주는 발전된 형태의 트리 모델이다. 여러 모델을 활용하기 때문에 앙상블이라고 부른다.  

종속변수가 연속형 데이터와 범주형 데이터인 경우 모두에서 사용할 수 있고, 아웃라이어가 문제가 되는 경우 랜덤 포레스트를 사용할 수 있다.

문제정의:

자동차 모델명, 연식, 마일리지, 성능 등을 통해 중고차 가격을 예측한다.

라이브러리 및 데이터 불러오기, 데이터 확인하기

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

file_url='https://media.githubusercontent.com/media/musthave-ML10/data_source/main/car.csv'
data = pd.read_csv(file_url)

필요한 라이브러리를 불러온다.

data.head()

몇개의 결측치가 보이고, 숫자형 데이터여야 될 것 같은 부분에 object형인 것을 볼 수 있다.

round(data.describe(),2)

selling_price에서 min부분과 max부분에 outlier가 보이지만 트리모델을 사용하므로 아웃라이어를 별도로 처리하지 않는다.

그래프로도 확인할 수 있다.

 

engine을 보면 숫자+CC형태로 되어있으므로 engine을 둘로 나눠준다.

engine을 나눴음에도 object임을 알 수 있다.

data['engine'] = pd.to_numeric(data['engine'])
data['engine'] = data['engine'].astype('float32')

둘 중 하나의 명령어를 통해 type을 바꿔준다.

data['engine_unit'].unique()
array(['CC', nan], dtype=object)

engine_unit을 확인하였을 때 CC와 nan만 있으므로 삭제해 주겠다.

data.drop('engine_unit', axis=1, inplace=True)
data[['max_power', 'max_power_unit']] = data['max_power'].str.split(expand=True)

maxpower도 동일한 방식으로 처리해 주겠다.

data['max_power'] = data['max_power'].astype('float32')
ValueError: could not convert string to float: 'bhp'

max_power를 float로 바꾸려고 하니깐 bhp라는 것 때문에 바꿀 수 없다고 나온다. 숫자 사이에 bhp라는 문자가 들어간 것이다.

확인해 보니 데이터가 있음을 확인할 수 있다.

방법1 : data['max_power'] = data['max_power'].replace('bhp', None)
def isFloat(value):
    try:
        num = float(value)
        return num
    except ValueError:
        return np.NaN
        
        
 data['max_power'] = data['max_power'].apply(isFloat)
 
 2번째 방법

함수 생성 후 적용해 주기 

max_power_unit이 NaN인 값에서 값이 NaN인 것을 확인했으므로 이 칼럼을 없애도록 하겠다.

data.drop('max_power_unit', axis=1, inplace=True)
data[['mileage', 'mileage_unit']] = data['mileage'].str.split(expand=True)

mileage_unit에 따라 Diesel, Pestrol인지 LPG, CNG인지 나뉘는 것으로 보인다.

data['fuel'].unique()
array(['Diesel', 'Petrol', 'LPG', 'CNG'], dtype=object)

확인해 보면 추론이 맞는 것을 확인할 수 있다.

def mile(x):
    if x['fuel'] == 'Petrol':
        return x['mileage']/80.43
    elif x['fuel'] == 'Diesel':
        return x['mileage']/73.56
    elif x['fuel'] == 'LPG':
        return x['mileage']/40.85
    else:
        return x['mileage']/44.23

1달러당 주행거리를 구하는 함수를 만들어준다. 이후 이 함수를 적용해 준다.

data.drop('mileage_unit', axis=1,inplace=True)

그리고 이제 mileage_unit은 제거해 준다.

 다음으로 torque를 확인한다.

data['torque'].head()
0              190Nm@ 2000rpm
1         250Nm@ 1500-2500rpm
2       12.7@ 2,700(kgm@ rpm)
3    22.4 kgm at 1750-2750rpm
4       11.5@ 4,500(kgm@ rpm)
Name: torque, dtype: object

rpm은 공통적으로 kgm이거나 Nm인 부분으로 나뉜다. 이 부분을 기준으로 나눠주겠다.

data['torque'] = data['torque'].str.upper()

먼저 대문자로 통일해 준다.

def torque_unit(x):
    if 'NM' in str(x):
        return 'NM'
    elif 'KGM' in str(x):
        return 'kgm'
        
data['torque_unit'] = data['torque'].apply(torque_unit)

이후 NM과 kgm으로 나눠준다.

data['torque_unit'].unique()
array(['NM', 'kgm', None], dtype=object)

나눈 후 확인해 보면 잘 나뉜 것을 볼 수 있고  결측치를 확인해 보겠다.

 

data[data['torque_unit'].isna()]['torque'].unique()
array([nan, '250@ 1250-5000RPM', '510@ 1600-2400', '110(11.2)@ 4800',
       '210 / 1900'], dtype=object)

 

확인해 보면 숫자가 250/510/110이므로 NM으로 바꿔주겠다.

data['torque_unit'].fillna('Nm', inplace=True)

앞의 숫자를 기준으로 나눠야 하므로 함수를 하나 만들어 주겠다.

def split_num(x):
    x = str(x)
    for i, j in enumerate(x):
        if j not in '0123456789.':
            cut = i
            break
    return x[:cut]
data['torque'] = data['torque'].apply(split_num)
data['torque'] = data['torque'].astype('float64')

ValueError: could not convert string to float: ''

적용해 주고 타입을 바꾸려고 하는데 ''이 있으므로 NaN으로 바꿔주고 다시 적용한다.

data['torque'] = data['torque'].replace('',np.NaN)
data['torque'] = data['torque'].astype('float64')

바꿔준 후 단위를 바꿔줘야 한다. NM=kgm*9.8066이므로 이에 대한 함수를 하나 만들어주겠다.

def torque_trans(x):
    if x['torque_unit'] == 'kgm':
        return x['torque']*9.8066
    else:
        return x['torque']
data['torque'] = data.apply(torque_trans, axis=1)
data.drop('torque_unit', axis=1, inplace=True)

적용해 주고, 이제 torque_unit은 쓸모없으므로 삭제해 준다.

 

이제 name을 확인해 보자.

보면 너무 길기 때문에 앞에 회사이름으로만 추려주겠다.

data['name'] = data['name'].str.split(expand=True)[0]
data['name'].unique()
data['name'].unique()
array(['Maruti', 'Skoda', 'Honda', 'Hyundai', 'Toyota', 'Ford', 'Renault',
       'Mahindra', 'Tata', 'Chevrolet', 'Fiat', 'Datsun', 'Jeep',
       'Mercedes-Benz', 'Mitsubishi', 'Audi', 'Volkswagen', 'BMW',
       'Nissan', 'Lexus', 'Jaguar', 'Land', 'MG', 'Volvo', 'Daewoo',
       'Kia', 'Force', 'Ambassador', 'Ashok', 'Isuzu', 'Opel', 'Peugeot'],
      dtype=object)
data['name'] = data['name'].replace('Land','Land Rover').
data.isna().mean()
name             0.000000
year             0.000000
selling_price    0.000000
km_driven        0.000000
fuel             0.000000
seller_type      0.000000
transmission     0.000000
owner            0.000000
mileage          0.027190
engine           0.027190
max_power        0.026575
torque           0.027313
seats            0.027190
dtype: float64

결측치를 확인해 준다. 해당 부분은 2% 정도 결측치가 있는데 대부분 비슷하므로, 큰 영향이 없으므로 지워주겠다. 

(이건 분석가의 판단)

data.dropna(inplace=True)
data = pd.get_dummies(data, columns = ['name', 'fuel', 'seller_type', 'transmission','owner'], drop_first=True)

data를 이제 더미화 해준다.

모델링 및 평가

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(data.drop('selling_price', axis=1), data['selling_price'], test_size=0.2, random_state=100)
from sklearn.ensemble import RandomForestRegressor
model = RandomForestRegressor(random_state=100)
model.fit(X_train, y_train)
train_pred = model.predict(X_train)
test_pred = model.predict(X_test)

비율이 나눠주고, 모델을 훈련시킨 후 예측값의 모델을 만들어준다.

from sklearn.metrics import mean_squared_error
print('train_rmse', mean_squared_error(y_train, train_pred)**0.5, 'test_rmse:', mean_squared_error(y_test, test_pred)**0.5)
train_rmse 53531.41548125947 test_rmse: 131855.18391308116
from sklearn.tree import plot_tree
plt.figure(figsize=(30,15))
plot_tree(model.estimators_[10], max_depth = 3, fontsize=15,  feature_names = X_train.columns)

트리 형태도 확인해 볼 수 있다. 

round(pd.Series(model.feature_importances_, index=X_train.columns), 2).sort_values(ascending=False)

영향도도 확인해볼 수 있다.

k-폴드 교차검증 

from sklearn.model_selection import KFold

KFold를 불러오고, index번호가 안 맞으므로 index설정을 다시 해준다.

kf = KFold(n_splits=5)

데이터를 5개로 분할한다.

X = data.drop('selling_price', axis=1)
y = data['selling_price']

독립변수와 종속변수를 할당한다.

train_rmse_total = []
test_rmse_total = []
for train_index, test_index in kf.split(X):
    X_train, X_test = X.loc[train_index], X.loc[test_index]
    y_train, y_test = y[train_index], y[test_index]
    model = RandomForestRegressor(n_estimators = 300, max_depth=50, min_samples_split = 5, min_samples_leaf = 1, n_jobs= -1, random_state=100)
    model.fit(X_train, y_train)
    train_pred = model.predict(X_train)
    test_pred = model.predict(X_test)

    train_rmse = mean_squared_error(y_train, train_pred)**0.5
    test_rmse = mean_squared_error(y_test, test_pred,squared=False)
    train_rmse_total.append(train_rmse)
    test_rmse_total.append(test_rmse)

5번을 설정해 줬으므로 일반 모델보다 시간이 더 걸리지만 교차검증을 통해 여러 번 측정하므로 더 신뢰로운 결과를 얻을 수 있다.

 

반응형