Home DACON 해외 축구 선수 이적료 예측하기 - LightGBM
Post
Cancel

DACON 해외 축구 선수 이적료 예측하기 - LightGBM

DACON 해외 축구 선수 이적료 예측

Regression - LightGBM

참고 노트북

데이터 셋

DACON

이상치를 제거하면 성능이 올라갈꺼 같은데 어떻게 처리해야할지 잘 모르겠다..
추가적으로 시도하고 정리해본 것들

사용 라이브러리

1
2
3
4
5
6
7
8
9
10
11
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import koreanize_matplotlib
import glob

from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
from lightgbm import LGBMRegressor
from lightgbm import plot_importance
1
2
3
4
5
6
7
8
9
10
def reg_score(y_true, y_pred):
    MSE = mean_squared_error(y_true, y_pred)
    RMSE = np.sqrt(mean_squared_error(y_true,y_pred))
    MAE = np.mean( np.abs((y_true - y_pred) / y_true) )
    NMAE =mean_absolute_error(y_true, y_pred)/ np.mean( np.abs(y_true) )
    MAPE = np.mean( np.abs((y_true - y_pred) / y_true) ) *100
    R2 = r2_score(y_true, y_pred)
    
    print(f"MSE: {np.round(MSE, 3)}\nRMSE: {np.round(RMSE, 3)}\nMAE: {np.round(MAE, 3)}\nNMAE: {np.round(NMAE, 3)}\nMAPE: {np.round(MAPE, 3)}\nR2: {np.round(R2, 3)}")

Data Load

1
2
path = glob.glob("data/*")
path
1
['data\\FIFA_test.csv', 'data\\FIFA_train.csv', 'data\\submission.csv']
1
2
3
4
5
train = pd.read_csv(path[1])
test = pd.read_csv(path[0])
submit = pd.read_csv(path[2])

train.shape, test.shape, submit.shape
1
((8932, 12), (3828, 11), (3828, 2))

기본 정보

1
2
display(train.sample(3))
display(test.sample(3))
idnameagecontinentcontract_untilpositionprefer_footreputationstat_overallstat_potentialstat_skill_movesvalue
49968634A. Baclet32europe2019STleft1.066662.0525000.0
18623150S. Sam30europe2019MFleft2.072723.02900000.0
639411456Paulo Vítor29south america2021GKright1.063641.0290000.0
idnameagecontinentcontract_untilpositionprefer_footreputationstat_overallstat_potentialstat_skill_moves
14615641Y. Toyokawa23asia2020STright1.069743.0
371116128M. Ndione18europe2020STright1.054642.0
295112353M. Gutiérrez26south america2018MFright1.062632.0

(출처: 데이콘)

  • id : 선수 고유의 아이디
  • name : 이름
  • age : 나이
  • continent : 선수들의 국적이 포함되어 있는 대륙입니다
  • contract_until : 선수의 계약기간이 언제까지인지 나타내어 줍니다
  • position : 선수가 선호하는 포지션입니다. ex) 공격수, 수비수 등
  • prefer_foot : 선수가 선호하는 발입니다. ex) 오른발
  • reputation : 선수가 유명한 정도입니다. ex) 높은 수치일 수록 유명한 선수
  • stat_overall : 선수의 현재 능력치 입니다.
  • stat_potential : 선수가 경험 및 노력을 통해 발전할 수 있는 정도입니다.
  • stat_skill_moves : 선수의 개인기 능력치 입니다.
  • value : FIFA가 선정한 선수의 이적 시장 가격 (단위 : 유로) 입니다

결측치 확인

1
2
3
4
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(15, 5))

_ = sns.heatmap(train.isnull(), ax=ax[0]).set_title("Train")
_ = sns.heatmap(test.isnull(), ax=ax[1]).set_title("Test")

png

데이터 확인

이산형과 연속형 데이터 나누기

1
_ = train.hist(bins=50, figsize=(12, 8))

png

1
train.info()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 8932 entries, 0 to 8931
Data columns (total 12 columns):
 #   Column            Non-Null Count  Dtype  
---  ------            --------------  -----  
 0   id                8932 non-null   int64  
 1   name              8932 non-null   object 
 2   age               8932 non-null   int64  
 3   continent         8932 non-null   object 
 4   contract_until    8932 non-null   object 
 5   position          8932 non-null   object 
 6   prefer_foot       8932 non-null   object 
 7   reputation        8932 non-null   float64
 8   stat_overall      8932 non-null   int64  
 9   stat_potential    8932 non-null   int64  
 10  stat_skill_moves  8932 non-null   float64
 11  value             8932 non-null   float64
dtypes: float64(3), int64(4), object(5)
memory usage: 837.5+ KB
1
2
3
4
# 연속형
nums = ["age", "stat_overall", "stat_potential"]
# 이산형
noms = ["continent", "position", "prefer_foot", "reputation", "stat_skill_moves"]

value 처리

value의 경우 너무 한쪽으로 치우쳐 있음
log를 이용해 처리해줌

1
train["value(log scale)"] = np.log1p(train["value"])
1
2
3
4
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(12, 5))

_ = sns.kdeplot(data=train, x="value", shade=True, ax=ax[0]).set_title("value")
_ = sns.kdeplot(data=train, x="value(log scale)", shade=True, ax=ax[1]).set_title("value (log scale)")

png

contract_until 처리

1
2
train["contract_until"] = pd.to_datetime(train["contract_until"]).dt.year
test["contract_until"] = pd.to_datetime(train["contract_until"]).dt.year

연속형 데이터

1
2
3
4
fig, ax = plt.subplots(nrows=1, ncols=len(nums), figsize=(20, 8))

for col, ax in zip(nums, ax):
    _ = sns.histplot(data=train, x=col, kde=True, ax=ax)

png

이산형 데이터

1
2
3
4
fig, ax = plt.subplots(nrows=1, ncols=len(noms), figsize=(20, 8))

for col, ax in zip(noms, ax):
    _ = sns.countplot(data=train, x=col, ax=ax)

png

기술 통계량

이상치를 찾아봄

1
train.describe()
idagecontract_untilreputationstat_overallstat_potentialstat_skill_movesvaluevalue(log scale)
count8932.0000008932.0000008932.0000008932.0000008932.0000008932.0000008932.0000008.932000e+038932.000000
mean7966.77575025.2091362020.1943571.13087867.09113371.9972012.4017022.778673e+0613.793901
std4844.4285214.6355151.2669010.4237926.8549105.9881470.7760485.840982e+061.401709
min0.00000016.0000002018.0000001.00000047.00000048.0000001.0000001.000000e+049.210440
25%3751.75000021.0000002019.0000001.00000063.00000068.0000002.0000003.750000e+0512.834684
50%7696.50000025.0000002020.0000001.00000067.00000072.0000002.0000008.250000e+0513.623140
75%12082.25000028.0000002021.0000001.00000072.00000076.0000003.0000002.600000e+0614.771022
max16948.00000040.0000002026.0000005.00000094.00000094.0000005.0000001.105000e+0818.520526
1
train.describe(include="object")
namecontinentpositionprefer_foot
count8932893289328932
unique8932542
topL. MessieuropeMFright
freq1532234286837
1
2
3
4
fig, ax = plt.subplots(nrows=1, ncols=len(nums), figsize=(15, 5))

for col, ax in zip(nums, ax):
    _ = sns.boxplot(data=train, x=col, ax=ax)

png

이상치가 존재함

상관 계수

1
2
plt.figure(figsize=(12, 8))
_ = sns.heatmap(train.corr(), cmap="coolwarm", annot=True, mask=np.triu(np.ones_like(train.corr()))).set_title("Corr")

png

value에 대한 stat_overall (현재 능력치)stat_potential (성장 가능성)의 관계성이 비슷함
의미하는 바도 비슷함으로, 파생 변수를 만들어 하나의 특성을 사용하면 좋을꺼 같다는 생각을 함

id - value (유명도 별)

1
_ = sns.lmplot(data=train, x="id", y="value", col="reputation", hue="reputation")

png

1
_ = sns.lmplot(data=train, x="id", y="value(log scale)", col="reputation", hue="reputation")

png

age - value (유명도별)

1
_ = sns.lmplot(data=train, x="age", y="value", col="reputation", hue="reputation")

png

stat_overall & stat_potential

1
_ = sns.scatterplot(data=train, x="stat_overall", y="stat_potential", hue="reputation")

png

1
_ = sns.lmplot(data=train, x="stat_overall", y="value", col="reputation", hue="reputation")

png

1
_ = sns.lmplot(data=train, x="stat_overall", y="value(log scale)", col="reputation", hue="reputation")

png

1
_ = sns.lmplot(data=train, x="stat_potential", y="value", col="reputation", hue="reputation")

png

1
_ = sns.lmplot(data=train, x="stat_potential", y="value(log scale)", col="reputation", hue="reputation")

png

하나의 변수로 만들어 사용하는 것이 확실히 더 좋아보임

stat_skill_moves - value

1
_ = sns.scatterplot(data=train, x="stat_skill_moves", y="value", hue="reputation")

png

파생변수 만들기

1
2
train["stat"] = (train["stat_overall"] + train["stat_potential"])/2
test["stat"] = (test["stat_overall"] + test["stat_potential"])/2
1
2
3
fig, ax = plt.subplots(ncols=2, figsize=(10, 5))
_ = sns.boxplot(data=train, x="stat", ax=ax[0])
_ = sns.pointplot(data=train, x="stat", y="value", ci=None, ax=ax[1])

png

1
2
plt.figure(figsize=(12, 8))
_ = sns.heatmap(train.corr(), cmap="coolwarm", annot=True, mask=np.triu(np.ones_like(train.corr()))).set_title("Corr")

png

필요없는 행 버리기

1
2
3
4
5
train.drop(columns=["id", "name", "stat_overall", "stat_potential", "value", "continent"], axis=1, inplace=True)
test.drop(columns=["id", "name", "stat_overall", "stat_potential", "continent"], axis=1, inplace=True)

# train.drop(columns=["name", "stat", "value"], axis=1, inplace=True)
# test.drop(columns=["name", "stat"], axis=1, inplace=True)

범수형 데이터 -> 수치형 데이터

1
to_dummy = ["position", "prefer_foot"] #"continent"
1
2
3
4
5
train_dummy = []
test_dummy = []
for col in to_dummy:
    train_dummy.append(pd.get_dummies(train[col], drop_first=True))
    test_dummy.append(pd.get_dummies(test[col], drop_first=True))
1
2
3
4
5
6
7
train.drop(columns=to_dummy, inplace=True)
temp = pd.concat(train_dummy, axis=1)
train = pd.concat([train, temp], axis=1)

test.drop(columns=to_dummy, inplace=True)
temp = pd.concat(test_dummy, axis=1)
test = pd.concat([test, temp], axis=1)

Train - LightGBM

1
2
3
label = "value(log scale)"
feature_names = train.columns.tolist()
feature_names.remove(label)
1
2
3
X_train, X_test, y_train, y_test = train_test_split(train[feature_names], train[label], test_size=0.2, random_state=42)

print(f"X_train: {X_train.shape}\ny_train: {y_train.shape}\nX_test: {X_test.shape}\ny_test: {y_test.shape}")
1
2
3
4
X_train: (7145, 9)
y_train: (7145,)
X_test: (1787, 9)
y_test: (1787,)
1
2
3
model_lgbm = LGBMRegressor(n_estimators=400, learning_rate=0.1)

model_lgbm.fit(X_train, y_train)
1
LGBMRegressor(n_estimators=400)
1
2
3
y_pred = model_lgbm.predict(X_test)

reg_score(y_test, y_pred)
1
2
3
4
5
6
MSE: 0.018
RMSE: 0.136
MAE: 0.007
NMAE: 0.006
MAPE: 0.663
R2: 0.991
1
_ = plot_importance(model_lgbm)

png

제출

1
submit["value"] = np.expm1(model_lgbm.predict(test))
1
submit.to_csv("submit_lgbm_rmse_136.csv", index=False)

제출 당시 186등이었다.

This post is licensed under CC BY 4.0 by the author.

22년 6월 4주차 주간 회고

Kaggle Santander Customer 기본 모델(XGBoost, LightGBM)

Comments powered by Disqus.