컬럼 설명
## 컬럼 설명
- brand 브랜드
- ford, vw(Volkswagen), vauxhall(Opel, GM), merc(benz), bmw, audi, toyota, skoda, hyundi
- vw, audi, skoda은 VAG(Volkswagen Aktiengesellschaft)그룹에 속함
- model 모델
- year 연식
- 연식이 최근일 수록(숫자가 클 수록) 높은 가격
- 가격에 많은 영향 미침
- transmission 변속기
- Manual : 높은 연료 효율성, 조작감, 낮은가격
- Automatic : 낮은 연료 효율성, 높은 가격
- Semi-Automatic : 수동 자동 모두 동일
- Other : 기어박스 대신 전기모터로 동력 전달
- hyundi Ioniq 검색 결과 CVT 사용
- 무단변속기 - CVT(Continuously Variable Transmission)
- 변속기가 기어 대신, 벨트, 체인을 사용해 기어 비율 변환
- 변속이 부드럽고 연료 효율성 높임
- mileage 주행거리
- 주행거리가 짧을 수록(숫자가 작을 수록)좋음
- 보통 40만키로 탈 수 있음
- 1년에 2만키로 사용
- fuelType
- 가솔린(휘발유)>>디젤(경유)>>하이브리드(가솔린 + 전기모터)
- Petrol (가솔린, 휘발류) : 가장 일반적임, 가정용 차량
- Diesel(경유) : 연비 좋음 EX) Suv, 트럭, 화물차, 티볼리, 쏘렌토, 스포티지
- Hybrid : 전기모터와 내연기관(가솔린 OR 디젤)을 함께 사용
- Other : LPG, LNG 등
- Electric : 배터리 구동, EX) 플러그인 하이브리드 OR 전기차
- tax 차량 등록세
- 차량 등록시 부과되는 세금
- 차량의 구매가격에 기반해 계산
- 차량 구매가격의 일정 비율 곱한 후 일정 금액 감안해 계산
- 이유 : 최초 등록일이 미지인 경우가 많아서 등록일과 상관없이 구매가격 기반함
- -> 신차보다 등록세 저렴함
- 높은 배기량(연료사이즈 큼), 높은 연료 소비량(연비 낮음)
- -> 높은 등록세
- 반면에 환경 친화적인 차량(연료사이즈 작음, 연비 높음)은 보조금, 등록세 감면
- mpg 연비(Miles Per Gallon) : 구간별
- 연료 효울, 즉 연료 1리터 당 주행 가능한 거리(단위: km/l)
- 계산 방법: 주행거리/소모 연료량
- 연비가 높을수록 연료를 더 효율적으로 사용
- 연비가 클 수록 일반적인 소비자가 선호함
- 높음 : 하이브리드, 전기차
- 낮음 : 고급 스포츠카, 럭셔리카
- engineSize
- 배기량 cc 표시
- 차량의 성능, 속도, 연비에 큰 영향
- 엔진 크기가 큼
- 배기량이 큼 = 더 높은 출력과 성능 = 많은 부품 높은 기술력 = 대형 suv, 픽업트럭 등 큰 차 = 연료를 많이 소비해 연비 낮을 가능성 큼
- -> 엔진 크기가 크면 연비가 낮음(연비와 반비례 관계)
- 엔진 크기가 작으면 연비가 큼
- 엔진 크키가 작을수록 일반적인 소비자가 선호함
- 일반적으로 가정용 차량 엔진 크기: 1,500CC~2,000CC 정도
- 중형 세단, SUV 등 대형 차량 엔진 크기: 2,000cc 이상
- price = 판매 가격, 목표 예측값
EDA
가정 : 차량 등록세를 면제받는 차는 환경친화적이다.
- tax가 0이 3435개임
- fuelType이 친환경적일수록 tax가 낮음
- 친환경적 단계 디젤, 휘발유 -> Other -> 하이브리드 -> 전기차
train['tax'].value_counts()
train[train['tax'] == 0]['fuelType'].value_counts()
# 연료 타입별 전체 개수
total_counts = train['fuelType'].value_counts()
# 연료 타입별 TAX가 0인 개수
zero_tax_counts = train[train['tax'] == 0]['fuelType'].value_counts()
# 연료 타입별 비율
ratios = zero_tax_counts / total_counts
# 출력
print(ratios)
Diesel 0.058726
Electric 0.500000
Hybrid 0.395877
Other 0.238095
Petrol 0.037905
Name: fuelType, dtype: float64
import matplotlib.pyplot as plt
# 막대 그래프 그리기
plt.bar(ratios.index, ratios.values)
plt.title('Fuel Type Ratio with Tax = 0')
plt.xlabel('Fuel Type')
plt.ylabel('Ratio')
plt.show()
재현'미션 : 대학생은 가격이 싸고 연비가 좋은 포드를 살 것이다.
- 대학생이라 돈이 없음
- 그래서 낮은 가격의 차를 선호함
- 가격이 싼 브랜드 : 포드, 현대, 토요타, 복스홀
- 가격과 관련된 칼럼은 연식과 엔진사이즈
- 연식 : 4개의 브랜드 평균 확인
- 엔진사이즈 : very small, small, median, big으로 카테고리를 나눈 후 very small의 비율 확인
- 연식은 모두 유의미한 차이가 없음
- 엔진사이즈의 very small의 비율이 가장 높은 브랜드인 ford 선택
train.groupby('brand')['price'].describe()
import matplotlib.pyplot as plt
# 브랜드별 가격 분포 히스토그램 그리기
fig, axes = plt.subplots(figsize=(20,10), nrows=3, ncols=3)
for i, brand in enumerate(train['brand'].unique()):
train[train['brand']==brand]['price'].plot(kind='hist', ax=axes[i//3, i%3], bins=20, title=brand)
plt.tight_layout()
plt.show()
# year
brands = ['ford', 'hyundi', 'toyota', 'vauxhall']
train[train['brand'].isin(brands)].groupby('brand')['year'].describe()
import seaborn as sns
import matplotlib.pyplot as plt
mean_years = train[train['brand'].isin(brands)].groupby('brand')['year'].mean()
sns.barplot(x=mean_years.index, y=mean_years.values, palette='Set2')
plt.ylim(2016, 2017)
plt.xlabel('Brand')
plt.ylabel('Mean year')
plt.title('Mean year by brand')
plt.show()
# 엔진사이즈 0.25, 0,5, 0,75로 곱한 값으로 분류 -> very samll, small, middle, big
train_filtered = train[train['brand'].isin(brands)].copy()
def get_category(engineSize):
cat = ''
if engineSize <= 1.2 : cat = 'very small'
elif engineSize <= 1.6 : cat = 'small'
elif engineSize <= 2.0 : cat = 'Middle'
else : cat = 'big'
return cat
train_filtered['engineSize_category'] = train_filtered['engineSize'].apply(lambda x : get_category(x))
train_filtered['engineSize_cat'] = train_filtered['engineSize'].apply(lambda x : get_category(x))
# 색상 리스트 정의
colors = {'hyundi': 'deepskyblue', 'vauxhall': 'forestgreen', 'toyota': 'mediumorchid', 'ford': 'tomato'}
# 시각화
plt.figure(figsize=(10,8)) # 그래프 크기 조정
group_names = ['very small', 'small','Middle','big'] # 이름 순으로 정렬
sns.barplot(x='engineSize_cat', y='price', hue='brand', data=train_filtered, order=group_names, ci=None, palette=colors) # 그래프 그리기
train_filtered['engineSize_cat'] = train_filtered['engineSize'].apply(lambda x: get_category(x))
count_by_brand = train_filtered.groupby(['brand'])['engineSize_cat'].count()
counts = train_filtered.groupby(['brand', 'engineSize_cat']).size().unstack(fill_value=0)
total_counts = count_by_brand
ratios = counts[['very small', 'small']] / total_counts[:, np.newaxis]
ratio_melt = pd.melt(ratios.reset_index(), id_vars='brand', var_name='engineSize_cat', value_name='ratio')
# 시각화
colors = {'hyundi': 'deepskyblue', 'vauxhall': 'forestgreen', 'toyota': 'mediumorchid', 'ford': 'tomato'}
plt.figure(figsize=(10,8))
group_names = ['very small', 'small']
sns.barplot(x='engineSize_cat', y='ratio', hue='brand', data=ratio_melt, order=group_names,
palette=colors)
전처리
결측치 처리
- mileage 삭제
- tax,중앙값 대체 mpg 평균 대체
# mileage 778개 결측치 비율 1.33%
# tax 5112개 결측치 비율 8.72%
# mpg 5592개 결측치 비율 9.53%
train.isna().sum()
mileage 삭제
- 주행거리는 왜 없을까?
- 가정 1: 주행을 하지 않음 -> 입력하지 않아서 0
- 가정 2 : 계기판이 고장나서 얼마나 주행했는지 모름
- 가정 3 : 주행거리가 너무 높아서 가격이 떨어질 것을 예측하고 입력하지 않음
- 가정 1~3경우에 해당하면 가격을 잘 예측할 수 없을 것이라고 판단
- mileage가 결측치인 778개로 1.33%으로 매우 낮은 비율
train = train.dropna(subset=['mileage'])
# mileage 결측치 확인
train['mileage'].isna().sum()
tax 중앙값 mpg 평균값 대체
- tax와 mpg의 결측치 비율은 각각 8.72%, 9.53%
- 높은 비율임으로 대체
# tax 중앙값 대체
train.loc[:, 'tax'] = train['tax'].fillna(train['tax'].median())
# mpg 평균값 대체
train.loc[:, 'mpg'] = train['mpg'].fillna(train['mpg'].mean())
# 전체 결측치 확인
train.isna().sum()
이상치 처리
- year가 1970인 행 제거
- engineSize가 0이고 fuelType이 Petrol, Diesel, Hybrid인 행 제거
- brand가 bmw이고 model이 i3인 행 제거
# IQR 1.5 넘는 값을 이상치로 가정 시 박스플롯 그리기
# year, price, tax 칼럼은 있는 가격이라고 판단해서 이상치 제거 x
import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style="whitegrid")
# 각 컬럼별 boxplot 그리기
numeric_vars = ['year', 'mileage', 'tax', 'mpg', 'engineSize', 'price']
fig, ax = plt.subplots(nrows=2, ncols=3, figsize=(15, 10), constrained_layout=True)
for i, col in enumerate(numeric_vars):
sns.boxplot(y=col, data=train, ax=ax[i//3, i%3])
# 이상치 비율 계산
for i, col in enumerate(numeric_vars):
outliers = train[train[col] > train[col].quantile(0.75) + 1.5*(train[col].quantile(0.75)-train[col].quantile(0.25))]
num_outliers = len(outliers)
outlier_ratio = round(num_outliers / len(train) * 100, 2)
print(f"Variable: {col}")
print(f"Number of outliers: {num_outliers}")
print(f"Outlier ratio: {outlier_ratio}%")
print()
year가 1970인 행 제거
# 90년대 데이터 총 8개
# 70년 2개가 있고 그 이후 96년까지가 비어있음
train['year'].value_counts()
# year가 1970인 데이터 확인
# vauxhall Zafira : production 1999 ~ 2019
# merc Mclass : production 1997 ~2015
# 생산연도가 1970년보다 더 빠르기 때문에 오류로 판정 -> 두 행 삭제
train[train['year'] == 1970]
# year가 1970인 행 삭제
train = train[train['year'] != 1970]
# 이상치 처리 확인
train[train['year'] == 1970]
engineSize가 0이고 fuelType이 Petrol, Diesel, Hybrid인 행 제거
# 디젤, 가솔린, 하이브리드는 내연기관이기 때문에 엔진크기가 0일 수가 없음
# 158개
train[(train['engineSize'] == 0)&(train['fuelType'].isin(['Petrol', 'Diesel', 'Hybrid']))]
# 엔진사이즈 0이고 연료타입이 디젤, 가솔린, 하이브리드 삭제
train = train[~((train['engineSize'] == 0) & (train['fuelType'].isin(['Petrol', 'Diesel', 'Hybrid'])))]
# 이상치 처리 확인
train[(train['engineSize'] == 0)]['fuelType'].value_counts()
brand가 bmw이고 model이 i3인 행 제거
# 일반 자동차의 연료 경제성 측정 방법 : 마일 당 갤런(MPG)
# = 자동차가 갤런의 연료를 사용하여 주행할 수 있는 xmz거리
# 전기차의 연료 경제성 측정 방법: 마일 당 주행 가능한 거리(MPC)
# = 전기충전 한 번으로 주행 가능한 거리
# 전기차는 MPG를 표시할 때 EPA(미국환경보호국)에서 제공하는 전기차 연비를 가상적인 가솔린 연비로 환산
# 일반적인 bmw i3의 mpg : 139mpg(59.1km/ℓ)
# 전기차 또는 하이브리드 기종은 mpg가 200 이상일 수 있음
# 하지만 mpg가 470인 값은 이상치로 판단
train = train[train['model'] != ' i3']
# 이상치 처리 확인
train[train['model'] == ' i3']
인코딩
# train_x와 달리 분석에 활용하지 않는 ID 데이터를 제거합니다.
test_X = test.drop('id', axis = 1)
from sklearn.preprocessing import LabelEncoder
object_features = ['brand', 'model', 'transmission', 'fuelType']
for feature in object_features:
le = LabelEncoder()
le = le.fit(train[feature])
train[feature] = le.transform(train[feature])
for label in np.unique(test_X[feature]):
if label not in le.classes_:
print(label)
le.classes_ = np.append(le.classes_, label)
test_X[feature] = le.transform(test_X[feature])
모델링
모델 학습/예측/평가: RandomForest
# 독립변수로 설정할 train_x에서는 종속변수를 제거
X_train = train.drop(['price'], axis = 1)
# train_y 변수를 종속변수로 사용하기 위해 price 데이터를 지정
y_train = train['price']
# 학습 데이터와 테스트 데이터로 분할
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X_train, y_train, test_size=0.3, random_state=42)
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error
from sklearn.metrics import r2_score
# 모델 학습
model = RandomForestRegressor()
model.fit(X_train, y_train)
preds_train = model.predict(X_test)
print("RMSE :", np.sqrt(mean_squared_error(y_test, preds_train)))
print("R2 :", r2_score(y_test, preds_train))
교차검증 및 하이퍼파라미터튜닝: GridSearchCV
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import mean_squared_error, r2_score
# 모델 정의
model = RandomForestRegressor(random_state=42)
# 하이퍼 파라미터 튜닝을 위한 후보 값들
param_grid = {'n_estimators': [100, 200, 300],
'max_depth': [10, 20, 30],
'max_features': ['sqrt', 'log2']}
# 그리드 서치를 이용한 교차 검증 수행
grid_search = GridSearchCV(model, param_grid, cv=5, scoring='neg_mean_squared_error')
grid_search.fit(X_train, y_train)
# 최적의 하이퍼 파라미터 값 출력
print('Best parameters: ', grid_search.best_params_)
# 최적의 하이퍼 파라미터로 모델 훈련
best_model = grid_search.best_estimator_
best_model.fit(X_train, y_train)
# 예측값 생성
y_pred = best_model.predict(X_test)
# 성능 평가
print("RMSE :", np.sqrt(mean_squared_error(y_test, y_pred)))
print("R2 :", r2_score(y_test, y_pred))
추론 & 제출
test_X
# tax 평균값 대체
test_X['tax'].fillna(test_X['tax'].median(), inplace=True)
# mpg 평균값 대체
test_X['mpg'].fillna(test_X['mpg'].mean(), inplace=True)
preds = best_model.predict(test_X)
# 제출
submission = pd.read_csv('/content/sample_submission.csv')
submission['price'] = preds
submission.to_csv('./submit_최종.csv', index = False)
결과
<최적의 모델>
1. Random forest
<그 밖의 모델>
- Ensemble1
- Ensembel2
- Decision tree
- XGBoost
- Gradient
한계
1. 낮은 성능
- 랜덤포레스트, 디시젼트리, 선형회귀, Xgb, knn, lightGBM 등 모델을 돌리고 6번의 중간평가를 받음
- 그러나 처음 시도했던 랜덤포레스트가 가장 높았음
- 초반에 전처리 처리에 많은 시간을 소요해서 중간평가의 기회를 많이 놓쳐서 어떤 모델이 가장 성능이 높을지 빨리 파악하지 못함
2. 모델에 대한 이해도 부족
- 데이터 특징, 도메인 지식을 바탕으로 모델 선정 후 모델링을 하지 않고
무작정 다양한 모델로 모델링을 시도함
3. 기술적 한계로 창의성 발휘 부족
- 초기 계획은 엔진사이즈로 소형, 대형, 중형과 같이 차종을 구별한 후 이를 예측하는 모델이었음
-하지만 군집과 회귀 모델을 사용시 복잡도가 증가하고 성능 향상을 위한 요소가 다양하짐 따라서 시행하지 못함
의의
1. 도메인 지식의 활용
- 이상치 처리 시 IQR 1.5가 넘는 값이 아닌 서칭을 바탕으로 도메인 지식을 활용해 처리함
2. 모델에 대한 이해도 향상
- 랜덤포레스트의 성능을 높이기 위해 스케일링과 결측치 처리를 달리 했으나 성능이 높아지지 않음 --> 이미 복잡한 모델이기 때문에 모델 성능에 큰 영향을 주지 않음
회고
데이터분석 초보자 3명이 팀이 구성되어서 진행이 오래 걸렸다.
그래도 다들 열심히 해서 결국 완성했다.
하지만 모델 성능이 결국 너무 안 좋아서 아쉽다.
모델을 무작정 돌리는 것 보다 체계를 갖추고 돌려야할 거 같다.
인코딩, 스캐일링, 알고리즘에 대한 공부가 더더더더욱 필요한 것을 느꼈다.
부스팅, 배깅, 그리드, 그리디 등등 진짜.. 더 공부해야함을 느꼈다..
전처리
처음에 데이터 칼럼에 대해서 이해를 할 때 많은 의견과 생각이 오간다.
이 논의를 잘 정리하고 논리의 틀을 갖추는 것이 중요할 거 같다.
미뤄서 채택하는 거 보다는 좀 더 고민하고 생각해서 '왜'에 대한 해답을 명확하게 내리는 것이 필요하다.
결측치 자체에 대한 이해를 더 하기 위해 노력해야 한다.
빅분기 공부할 때 코랩 쓴 경험이나 코딩이 프젝에서 도움이 됐다.
불린타입 데이터에 대해서 알게된 이후 원하는 칼럼의 행을 뽑을 수 있다.
그룹핑도 하고 요약통계량이나 기초통계를 원하는 대로 추출가능하다.
계속 공부한다면 훨씬 더 성장할 수 있을 거 같아서 기대된다.
꾸준히 영상을 듣고 공부해야겠다!
한 명이 총대매고 멘토에게 자주 질문하면서 모르는 것을 확인했던 것이 프젝 완성에 큰 도움이 됐다.
모르는 것을 부끄러워 하지 않고 먼저 공부한 선배들에게 질문하는 태도를 본받아야겠다.
나의 역할
오토엠엘로 모델링을 해봤다. - 근데 안 쓰임
팀원이 코딩에 익숙하지 않아서 추출해서 데이터 확인시키고 논리를 더 다지기 위한 노력을 했다.
이상치 처리를 위해서 도메인 지식을 공부했다.
미국의 위키피디아를 검색하거나 mpg와 mpc에 대해서 공부했다.
코랩 취합했다.