랜덤포레스트(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번을 설정해 줬으므로 일반 모델보다 시간이 더 걸리지만 교차검증을 통해 여러 번 측정하므로 더 신뢰로운 결과를 얻을 수 있다.