IT/Data 분석

[게 나이 예측] EDA - 1

송시 2023. 6. 5. 00:02
728x90

2023.06.03 - [IT/Data 분석] - [게 나이 예측] 선형회귀 베이스라인

 

[게 나이 예측] 선형회귀 베이스라인

데이터 안에서 게의 나이를 예측 하는 kaggle 의 playground 에 참여해보았다. https://www.kaggle.com/competitions/playground-series-s3e16 Regression with a Crab Age Dataset | Kaggle www.kaggle.com 총 8개의 독립변수[피처]를 토

songsiaix.tistory.com

앞서 단순한 선형회귀 베이스라인을 설정하고 돌렸을 때 1.44 정도의 MAE 점수가 나왔다.

 

EDA 를 성의없게 했기에 이번에는 EDA 를 통해 변수를 분석해보려 한다.

 

plt.figure(figsize=(12,10))
plt.subplot(2,1,1)
sns.boxplot(train_df,x='Age',y='Height')
plt.subplot(2,1,2)
sns.boxplot(train_df,x='Age',y='Length')

Age를 그룹화 하여 outlier 이상치가 매우 많은 것을 알 수 있다.

 

Height 가 1 을 넘어서기도 하고 Length도 이상치가 많아 보인다.

 

대체적으로 이상치가 많을 것으로 예상이 된다.

 

Age 에서는 특별히 28 나이의 게의 자료는 없는 것으로 보인다.

 

일단 이상치를 제거하는 것을 목표로 해본다.

 

Age 는 test_df 에 존재하지 않는 값이기 때문에 train_df 로만 이상치를 제거하고 이걸 토대로 fit & predict 를 해보도록 한다.

 

앞으로 사용할 함수의 사용을 위해 컬럼의 이름을 변경한다.

 

train_df.rename(columns={'Shucked Weight':'SW','Viscera Weight':'VW','Shell Weight':'SHW'},inplace=True)

결측치는 boxplot 에서 사용하는 IQR 를 계산하여 처리하도록 함수를 만들어보았다.

column 변수에 Shell Weight 와 같이 공백이 들어가면 처리하지 못하는 문제를 해결하기 위해 위에서 rename 으로 SHW 로 변경하였던 것이다.

 

def outlier_drop(df_drop,column):
    Q1 = df_drop.groupby('Age')[column].quantile(.25)
    Q3 = df_drop.groupby('Age')[column].quantile(.75)
    IQR = Q3-Q1
    index_count=0
    
    for i in range(1,30):
        if 28 == i:
            pass
        else:
            lowout=0
            highout=0
            lowout = Q1[i] - 1.5 * IQR[i]
            highout = Q3[i] + 1.5 * IQR[i]
            indexs = df_drop.query(f'Age == {i} and ({column} < {lowout} or {column} > {highout})').index.to_list()
            if len(indexs) != 0:
                index_count += len(indexs)
                df_drop.drop(index=indexs,inplace=True)
    print(column,df_drop.shape,index_count)
    return df_drop

 

train_df_drop = train_df.copy()

for i in train_df_drop.columns.difference(['id','Age','Sex']):
    outlier_drop(train_df_drop,i)

Diameter (72473, 10) 1578
Height (71598, 10) 875
Length (71161, 10) 437
SHW (70177, 10) 984
SW (69472, 10) 705
VW (68969, 10) 503
Weight (68825, 10) 144

train_df_drop 에서 이상치를 제거하면서 행도 함께 줄고 있는 것을 알 수 있다.

 

plt.figure(figsize=(12,10))
plt.subplot(2,1,1)
sns.boxplot(train_df_drop,x='Age',y='Height')
plt.subplot(2,1,2)
sns.boxplot(train_df_drop,x='Age',y='Length')

깔끔하게 모두 제거 될 줄 알았는데 이상치가 더 남아 있다.

 

한번 더 이상치를 제거 해본다.

 

for i in train_df_drop.columns.difference(['id','Age','Sex']):
    outlier_drop(train_df_drop,i)

Diameter (68667, 10) 157
Height (68417, 10) 250
Length (68299, 10) 118
SHW (68069, 10) 230
SW (67924, 10) 145
VW (67843, 10) 81
Weight (67779, 10) 64

plt.figure(figsize=(12,10))
plt.subplot(2,1,1)
sns.boxplot(train_df_drop,x='Age',y='Height')
plt.subplot(2,1,2)
sns.boxplot(train_df_drop,x='Age',y='Length')

이상치가 아주 조금 남아 있지만 많이 호전된 것 같다.

 

그런데 지우면서 느낀건데 이상치가 과연 무적권 지워야하는 대상일까?

 

너무 말도 안되는 값 예를 들어 height 가 0 이라거나 height 가 1 이상이라던가 하는 확실히 이상한 애들이 아니고서야 

이상치는 무적권 지워야하는 대상이 아닐 것 같다는 생각이 느낌적으로 든다.

 

이제 까지 제거한 정도로만 해서 모델을 학습시켜본다.

 

Y=train_df_drop['Age']
X=train_df_drop.drop(columns=['id','Age'])
X=pd.get_dummies(X)
print(X.shape,Y.shape,X.info())

<class 'pandas.core.frame.DataFrame'>
Int64Index: 67779 entries, 0 to 74050
Data columns (total 10 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   Length    67779 non-null  float64
 1   Diameter  67779 non-null  float64
 2   Height    67779 non-null  float64
 3   Weight    67779 non-null  float64
 4   SW        67779 non-null  float64
 5   VW        67779 non-null  float64
 6   SHW       67779 non-null  float64
 7   Sex_F     67779 non-null  uint8  
 8   Sex_I     67779 non-null  uint8  
 9   Sex_M     67779 non-null  uint8  
dtypes: float64(7), uint8(3)
memory usage: 4.3 MB
(67779, 10) (67779,) None

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error
x_train,x_test,y_train,y_test = train_test_split(X,Y,random_state=42,test_size=0.2,shuffle=True)
model_LR = LinearRegression()
model_LR.fit(x_train,y_train)
y_pred = model_LR.predict(x_test).astype(int)
print(mean_absolute_error(y_test,y_pred))

1.3360873413986427

MAE 점수가 무려 1.33 까지 줄일 수 있었다.

 

기쁜 마음을 갖고 test_df 데이터를 학습시킨 후 제출해보았다.

 

test_df_drop = test_df.copy()
test_df_drop.rename(columns={'Shucked Weight':'SW','Viscera Weight':'VW','Shell Weight':'SHW'},inplace=True)
test_df_drop.drop(columns='id',inplace=True)
print(test_df_drop.info())

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 49368 entries, 0 to 49367
Data columns (total 8 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   Sex       49368 non-null  object 
 1   Length    49368 non-null  float64
 2   Diameter  49368 non-null  float64
 3   Height    49368 non-null  float64
 4   Weight    49368 non-null  float64
 5   SW        49368 non-null  float64
 6   VW        49368 non-null  float64
 7   SHW       49368 non-null  float64
dtypes: float64(7), object(1)
memory usage: 3.0+ MB
None

test_df_drop = pd.get_dummies(test_df_drop)
print(test_df_drop.info())
y_pred_test = model_LR.predict(test_df_drop).astype(int)
print(y_pred_test[:5])

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 49368 entries, 0 to 49367
Data columns (total 10 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   Length    49368 non-null  float64
 1   Diameter  49368 non-null  float64
 2   Height    49368 non-null  float64
 3   Weight    49368 non-null  float64
 4   SW        49368 non-null  float64
 5   VW        49368 non-null  float64
 6   SHW       49368 non-null  float64
 7   Sex_F     49368 non-null  uint8  
 8   Sex_I     49368 non-null  uint8  
 9   Sex_M     49368 non-null  uint8  
dtypes: float64(7), uint8(3)
memory usage: 2.8 MB
None
[ 7  7 10  9  7]

 

test_df['Age'] = y_pred_test
test_df_sub = test_df[['id','Age']]
test_df_sub.set_index('id',inplace=True)
print(test_df_sub.head())
test_df_sub.to_csv('submission.csv')

       Age
id        
74051    7
74052    7
74053   10
74054    9
74055    7

 

나는 놀라지 않을 수 없었다. 

 

train_df 로 1.33 의 MAE 를 보였던 모델이 정작 test_df 에 적용하였을 때 지난번 베이스라인 모델만 사용했던 때보다 0.02 더 상승했다.

 

더 안좋은 모델을 만들어 냈다는 의미다.

 

여기에는 기록하지 않았는데, 

 

처음에 all_data 형태로 train_df, test_df 를 concat 해서 글을 작성하기 전에 이상치를 제거했었다.

 

그런데 그래프에 여전히 이상치가 많이 남아있는것이 아닌가?

 

내가 이상치를 제거하는 방식은 Age x 축으로 해서 제거하는 방법이 였는데 생각해보니 test_df 에는 Age 컬럼이 존재하지 않는다.

 

다른 말로 train_df 는 이상치를 제거한 모델이 MAE 가 좋은 점수를 낼 수 있었다고 하더라도

 

test_df 에 있는 이상치 들에는 적합하지 않은 모델이 되는 셈이다. 이런걸 다른 말로 오버피팅 이라고도 할 수 있을 것 같다.

 

좋은 것을 배웠다.

 

그런데 문득 이런생각도 든다. all_data 를 통해서 이상치를 잘 제거 했다고 쳐도 결국에는 test_df 를 예측 할때에는 이상치가 있는 상태에서 예측을 해야 한다.

 

아무래도 이상치 제거는 정답이 아닐 수도 있겠다는 생각이 든다.

 

다음에는 feet , oz 의 단위가 다른 독립변수들을 스케일 하여 테스트를 해봐야겠다.

728x90