오늘은 강의나 복습보다 다음주에 있을 과제 준비를 하며 공부를 하였습니다.

 

발표자료 만들기와 발표가 저의 파트이지만 발표를 제대로 준비하기 위해서 코드들이 어떻게 흘러가는지 흐름을 파악 하자는 생각으로 진행하였습니다.

 

  1. DATA LOAD
  2. DATA FEATURE 분석
  3. DATA 전처리
  4. 모델학습 (로지스틱, 랜덤 포레스트, XGBoost)
  5. 회고(진행하며 오류, 해결, 지식 등)

이 순서대로 학습을 진행하였고 소개해 볼까 합니다.

 

☑️ DATA  LOAD

DATA LOAD에는 2가지 방법이 있습니다.

  • import pandas as pd df = pd.read_csv(”titanic.csv”)
  • import seaborn as sns titanic = sns.load_dataset('titanic')

위 두가지의 차이점을 설명하겟습니다.

 

READ: Kaggle 또는 다른 곳에서 제공하는 타이타닉 데이터는 원본에 가까운 형태이다.

SEABORN: Seaborn에서 제공하는 타이타닉 데이터셋은 시각화에 적합한 형식으로 다듬어진다.

  • 정보 추가: Seaborn의 타이타닉 데이터셋은 시각화와 분석을 위해 추가된 정보(column) class, who, alive, deck
  • 컬럼 이름의 차이: 일부 컬럼은 시각화에 더 직관적이게 사용되도록 이름이 변경 pclass는 class라는 범주형 변수로 다시 변환
  • 범주형 데이터 변환: sex, alive와 같은 열들은 범주형(categorical) 데이터로 변경되었고, who, adult_male 등의 새로운 파생 변수가 추가되었습니다.

결론:

Seaborn에서 제공하는 타이타닉 데이터셋은 시각화와 머신러닝 분석을 위해 좀 더 가공된 버전입니다. 데이터 전처리가 일부 포함되어 있고, 추가적인 열이 생겼기 때문에 원본 타이타닉 데이터셋과 차이가 생깁니다.

 


☑️ DATA  FEATURE 분석

  • titanic.columns()

위 함수를 이용해서 타이타닉 데이터 셋의 columns를 가져와서 해석을 진행하였다.

 

 

위 함수를 사용해서 결측치, 데이터타입, 행/열의 수를 확인

 

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 15 columns):
 #   Column       Non-Null Count  Dtype
---  ------       --------------  -----
 0   survived     891 non-null    int64
 1   pclass       891 non-null    int64
 2   sex          891 non-null    object
 3   age          714 non-null    float64
 4   sibsp        891 non-null    int64
 5   parch        891 non-null    int64
 6   fare         891 non-null    float64
 7   embarked     889 non-null    object
 8   class        891 non-null    category
 9   who          891 non-null    object
 10  adult_male   891 non-null    bool
 11  deck         203 non-null    category
 12  embark_town  889 non-null    object
 13  alive        891 non-null    object
 14  alone        891 non-null    bool
dtypes: bool(2), category(2), float64(2), int64(4), object(5)
memory usage: 80.7+ KB

 

  • titanic.head()

위 함수를 사용하여 각 column의 1~5번 까지의 행을 가져와서 어떤 값들이 들어있는지 확인

  • titanic.describe()

위 함수를 사용하여 수치형으로 이루어진 데이터들의 count(행의 수), mean(평균), std(표준편차), min/25%/50%/75%/max(크기에 맞는 값)을 확인

  • print(titanic.isnull().sum())

위 함수를 사용하여 각 column의 결측치를 확인

survived         0
pclass           0
sex              0
age            177
sibsp            0
parch            0
fare             0
embarked         2
class            0
who              0
adult_male       0
deck           688
embark_town      2
alive            0
alone            0
dtype: int64

 


☑️ DATA  전처리

1. 결측값 대체

# 나이(age) 열의 결측값을 중앙값으로 채움
titanic['age'].fillna(titanic['age'].median(), inplace=True)

# 승선한 항구(embarked) 열의 결측값을 최빈값으로 채움
titanic['embarked'].fillna(titanic['embarked'].mode()[0], inplace=True)

# 승선 도시(embark_town) 열의 결측값을 'Unknown'으로 채움 
titanic['embark_town'].fillna('Unknown', inplace=True)

# 'deck' 열을 object 타입으로 변환
titanic['deck'] = titanic['deck'].astype(str)

# 결측값을 'Unknown'으로 채움
titanic['deck'].fillna('Unknown', inplace=True)

#결측치 제거 데이터 프레임
titanic_no_nan = titanic

#deck과 embark_town을 fillna(0)로 진행했을 때 TypeError
#해결하기 위해 dtype으로 각 데이터 타입을 확인
#숫자형 데이터'0'에서 문자형 데이터'Unknown'으로 대체
#각 타입을 astype(category)과 (object)로 타입 변화 추가해도 안됨
#이유를 찾아보니 원래 카테고리 타입인 데이터에는 새로운 카테고리 값을 추가할 때 에러가 발생
#category 타입 경우 새로운 카테고리를 추가하기 위해선 기존 카테고리 목록에 추가 되어있어야 가능 
#또는 타입을 변환을 진행할 때 astype(object)로 작성해서 오류
#astype(str)로 수정 하니 object타입으로 변환 가능

 

2. 인코딩

# 성별 인코딩: 'male'은 0, 'female'은 1로 매핑
titanic_no_nan['sex'] = titanic_no_nan['sex'].map(lambda x: 0 if x == 'male' else 1 )
# 생존 여부 인코딩: 'yes'는 0, 'no'는 1로 매핑
titanic_no_nan['alive'] = titanic_no_nan['alive'].map(lambda x: 0 if x == 'yes' else 1)
# 승선한 항구 인코딩: 'C'는 0, 'Q'는 1, 'S'(Southampton)은 2로 매핑
titanic_no_nan['embarked'] = titanic_no_nan['embarked'].map(lambda x: 0 if x == 'C' else 1 if x == 'Q' else 2)
# 승객 등급 인코딩: 'First'는 0, 'Second'는 1, 'Third'는 2로 매핑
titanic_no_nan['class'] = titanic_no_nan['class'].map(lambda x: 0 if x =='First' else 1 if x =='Second' else 2) 
# 성별에 따른 그룹화 인코딩: 'man'은 0, 나머지(여성)는 1로 매핑
titanic_no_nan['who'] = titanic_no_nan['who'].map(lambda x: 0 if x == 'man' else 1)
# 성인 남성 여부 인코딩: 'False'는 0, 'True'는 1로 매핑
titanic_no_nan['adult_male'] = titanic_no_nan['adult_male'].map(lambda x: 0 if x == 'False' else 1)
# 갑판 인코딩: 'A'는 0, 'B'는 1, 'C'는 2, 'D'는 3, 'E'는 4, 'F'는 5, 'G'는 6으로 매핑
titanic_no_nan['deck'] = titanic_no_nan['deck'].map(lambda x:  0 if x == 'A' else 1 if x == 'B' else 2 if x == 'C' else 3 if x == 'D' else 4 if x == 'E' else 5 if x == 'F' else 6)
# 승선 도시 인코딩: 'Cherbourg'는 1, 'Queenstown'은 2, 'Southampton'은 3, 결측값은 0으로 매핑
titanic_no_nan['embark_town'] = titanic_no_nan['embark_town'].map(lambda x: 1 if x == 'Cherbourg' else 2 if x == 'Queenstown' else 3 if x == 'Southampton' else 0)
# 혼자 탑승 여부 인코딩: 'False'는 0, 'True'는 1로 매핑
titanic_no_nan['alone'] = titanic_no_nan['alone'].map(lambda x: 0 if x == 'False' else 1)

#head함수에서 class의 데이터 타입이 category인것으로 확인
#타입 통일을 위해서 astype으로 int 적용해도 category로 나옴
#str을 적용해서 int64로 바뀐것 확인
titanic_no_nan['class'].astype(str)

# 인코딩된 데이터프레임을 titanic_enco 변수에 저장
titanic_enco = titanic_no_nan 

 

 

3. HEAD()로 인코딩 작동여부 확인

print(titanic_enco['sex'].head())
print(titanic_enco['alive'].head())
print(titanic_enco['embarked'].head())
print(titanic_enco['class'].head())
print(titanic_enco['who'].head())
print(titanic_enco['adult_male'].head())
print(titanic_enco['deck'].head())
print(titanic_enco['embark_town'].head())
print(titanic_enco['alone'].head())

 

4. 새로운 열 작성

#형제,자매, 부부, 부모, 자식의 수는 가족family_size로 묶을 수 있어 데이터 관리에 용이함
#본인을 포함 시켜야 하니 +1
titanic_enco['family_size'] = titanic_enco['sibsp'] + titanic_enco['parch'] + 1

 

5. 이상치 확인

#이상치를 확인 했지만 특별히 높은 값이 없어 그대로 진행
#모든 열의 이상치 확인

#25%보다 작거나 75%보다 크면 이상치로 처리
#quantile로 이상치의 기준점 설정
for column in titanic_enco.columns:
    Q1 = titanic_enco[column].quantile(0.25)
    Q3 = titanic_enco[column].quantile(0.75)
    IQR = Q3 - Q1

    # 이상치 범위 설정
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR

    #여기서 |는 OR의 논리연산자
    #C열의 값중 lower_bound보다 작거나 upper_bound보다 큰 수를 이상치로 간주해 outliers에 저장해서 확인
    outliers = titanic_enco[(titanic_enco[column] < lower_bound) | (titanic_enco[column] > upper_bound)]

    # 이상치가 있을 경우 출력
    # outliers 데이터프레임이 비어 있지 않은지 확인하는 조건문
    # empty 속성은 데이터프레임이 비어있으면 True, 데이터가 하나라도 있으면 False를 반환
    if not outliers.empty:
        print(f"Column: {column}")
        print(outliers[[column]])  # 해당 열의 이상치만 출력
        print()  # 줄바꿈
        
# 값이 출력 되었지만 양이 너무 적어보여 GPT에 제대로 작동 한지 검증해서 마지막 열에만 적용 된것을 확인
# 조건문을 활용한 출력 값 코드 이해 X

 

6. 중복값 확인

#모델 성능 개선을 위해 중복값 존재하는지 확인 후 제거
print(titanic_enco.duplicated().sum())

#데이터 중복값 제거 후 titanic_no_duplicates 변수에 저장
titanic_no_duplicates = titanic_enco.drop_duplicates()

#출력 140

 


 

☑️ 모델학습

📚  로지스틱 회귀 모델

 

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix

data = titanic_no_duplicates

data = data[['survived', 'pclass', 'sex', 'age', 'sibsp', 'parch', 'fare', 'embarked', 'family_size']].dropna()

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

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 데이터 스케일링
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

# 모델 생성 및 학습
model = LogisticRegression()
model.fit(X_train, y_train)

# 예측
# model.predict(X_test) 학습된 모델을 사용하여 테스트데이터 X_test에 대한 예측값을 계산.예측 결과는 y_pred에 저장
y_pred = model.predict(X_test)

print(f"Accuracy: {accuracy_score(y_test, y_pred)}")
print(f"Classification Report:\n{classification_report(y_test, y_pred)}")
print(f"Confusion Matrix:\n{confusion_matrix(y_test, y_pred)}")

# accuracy_score(y_test, y_pred) 실제값 y_test와 예측 값 y_pred를 비교하여 정확도 계산
# classification_report(y_test, y_pred) 모델의 성능을 요약하여 출력(정밀도, 재현율,F1점수, 지원)
# confusion_matrix(y_test, y_pred) 혼동 행렬을 생성하여 모델의 성능을 시각적으로 나타낸다.
#Precision (정밀도): 모델이 양성으로 예측한 것 중 실제 양성의 비율입니다.
#Recall (재현율): 실제 양성 샘플 중에서 모델이 올바르게 예측한 비율입니다.
#F1-Score: 정밀도와 재현율의 조화 평균으로, 두 지표의 균형을 잡아줍니다.(높을 수 록 성능이 균형있게 수행 된 것이다.)
#평균 Avg 나온 숫자들이 비슷할 수록 좋다.
# 혼동행렬: TP, FN, FP, TN
#TP (True Positives): 올바르게 긍정으로 예측된 샘플 수
#FP (False Positives): 잘못 긍정으로 예측된 샘플 수
#TN (True Negatives): 올바르게 부정으로 예측된 샘플 수
#FN (False Negatives): 잘못 부정으로 예측된 샘플 수


📚  랜덤포레스트 모델

from sklearn.ensemble import BaggingRegressor
from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error

data = titanic_no_duplicates

data = data[['survived', 'pclass', 'sex', 'age', 'sibsp', 'parch', 'fare', 'embarked', 'family_size']].dropna()

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

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 데이터 스케일링
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

rf_model = RandomForestRegressor(n_estimators=100, random_state=42)
# 모델 학습
rf_model.fit(X_train_scaled, y_train)

# 예측
y_pred_rf = rf_model.predict(X_test_scaled)

# 평가
mse_rf = mean_squared_error(y_test, y_pred_rf)
print(f'랜덤 포레스트 모델의 MSE: {mse_rf}')

# 훈련 데이터에서 예측
y_train_pred = xgb_model.predict(X_train_scaled)

# 훈련 데이터의 MSE 계산
mse_train = mean_squared_error(y_train, y_train_pred)
print(f'훈련 데이터에 대한 MSE: {mse_train}')


#MSE는 회귀모델에 적합한 평가모델이기 때문에 좋지 못한 성능으로 나온다.
# 0 또는 1로 나뉘는 이진 분류는, MSE가 0.25는 예측이 실제 값에서 평균적으로 0.5 (반쯤 맞고 반쯤 틀림) 정도
#과적합: 훈련 데이터에서 너무 성능이 좋고 테스트 데이터에서 성능이 떨어진다면 과적합의 징후
#0.06 정도 차이로 과적합은 없는듯

 


📚  XGBoost모델

import pandas as pd
import numpy as np
import seaborn as sns
from sklearn.ensemble import BaggingRegressor
from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
import xgboost as xgb
from sklearn.metrics import mean_squared_error

data = titanic_no_duplicates

data = data[['survived', 'pclass', 'sex', 'age', 'sibsp', 'parch', 'fare', 'embarked', 'family_size']].dropna()

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

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 데이터 스케일링
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

xgb_model = xgb.XGBRegressor(n_estimators=100, learning_rate=0.1, max_depth=3, random_state=42)

# 모델 학습
xgb_model.fit(X_train_scaled, y_train)

# 예측
y_pred_xgb = xgb_model.predict(X_test_scaled)

# 평가
mse_xgb = mean_squared_error(y_test, y_pred_xgb)
print(f'XGBoost 모델의 MSE: {mse_xgb}')

# 훈련 데이터에서 예측
y_train_pred = xgb_model.predict(X_train_scaled)

# 훈련 데이터의 MSE 계산
mse_train = mean_squared_error(y_train, y_train_pred)
print(f'훈련 데이터에 대한 MSE: {mse_train}')

#과적합: 훈련 데이터에서 너무 성능이 좋고 테스트 데이터에서 성능이 떨어진다면 과적합의 징후
#0.06 정도 차이로 과적합은 없는듯
# 0 또는 1로 나뉘는 이진 분류는, MSE가 0.25는 예측이 실제 값에서 평균적으로 0.5 (반쯤 맞고 반쯤 틀림) 정도

 


☑️ 회고

결측치 대체 및 제거

titanic['age'].fillna(titanic['age'].median(), inplace=True)

titanic['embarked'].fillna(titanic['embarked'].mode()[0], inplace=True)

titanic['embark_town'].fillna('Unknown', inplace=True)

titanic['deck'].fillna('Unknown', inplace=True)


#deck과 embark_town을 fillna(0)로 진행했을 때 TypeError
#해결하기 위해 dtype으로 각 데이터 타입을 확인
#숫자형 데이터'0'에서 문자형 데이터'Unknown'으로 대체
#각 타입을 astype(category)과 (object)로 타입 변화 추가해도 안됨
#이유를 찾아보니 원래 카테고리 타입인 데이터에는 새로운 카테고리 값을 추가할 때 에러가 발생
#category 타입 경우 새로운 카테고리를 추가하기 위해선 기존 카테고리 목록에 추가 되어있어야 가능 
#또는 타입을 변환을 진행할 때 astype(object)로 작성해서 오류
#astype(str)로 수정 하니 object타입으로 변환 가능

 

결론 =>

1. 카테고리 데이터 타입일 경우 결측치가 바뀌지 않아 데이터타입 변환을 해야한다.

2. 문자열 데이터에 대체값으로 숫자형 데이터를 넣으면 오류가 난다.

3. 카테고리 타입 변환시 str로 변환하면 object로 표시 된다.

 

인코딩

titanic_no_nan['sex'] = titanic_no_nan['sex'].map({'male': 0, 'female': 1})
titanic_no_nan['alive'] = titanic_no_nan['alive'].map({'no': 1, 'yes': 0})
titanic_no_nan['embarked'] = titanic_no_nan['embarked'].map({'C': 0, 'Q': 1, 'S': 2})
titanic_no_nan['class'] = titanic_no_nan['class'].map({'First': 0, 'Second': 1, 'Third': 2})
titanic_no_nan['who'] = titanic_no_nan['who'].map({'man': 0, 'woman': 1})
titanic_no_nan['adult_male'] = titanic_no_nan['adult_male'].map({'True': 1, 'False': 0})
titanic_no_nan['deck'] = titanic_no_nan['deck'].map({'A': 1, 'B': 2, 'C': 3, 'D': 4, 'E': 5, 'F': 6, 'G': 7, 'nan': 0})
titanic_no_nan['embark_town'] = titanic_no_nan['embark_town'].map({'Cherbourg': 1, 'Queenstown': 2, 'Southampton': 3, 'Unknown': 0})
titanic_no_nan['alone'] = titanic_no_nan['alone'].map({'False': 0, 'True': 1})

#map만 사용하였을때 nan값으로 처리 되어서 lambda함수를 같이 사용해야지 된다는 피드백 받음
#그래서 lambda함수를 사용하면 왜 되는건지 찾아봄
#map만 사용했을 땐 사전의 키와 값을 직접적으로 연결하여 변환하기에 간단한 매핑 가능
#lambda함수를 사용하게 되면 복잡한 변환 로직 사용 가능하며 어떤 형식이든 유연히 처리

 

결론 =>

1. map함수는 간단한 맴핑에만 사용해야한다.

2. map(lambda())함수를 이용하면 복잡한 로직에도 유연하게 작동한다.

3. apply(lambda())도 맴핑에 사용 가능하다.

4. lambda함수 사용 방법을 익혔다.

 

 

이상치 확인

Q1 = titanic_enco.quantile(0.25)
Q3 = titanic_enco.quantile(0.75)
IQR = Q3 - Q1 

lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

outliers = titanic_enco[(titanic_enco < lower_bound) | (titanic_enco > upper_bound)]
print(outliers)

#데이터프레임 전체를 한번에 확인해서 출력값에 nan값이 많이나오는 것을 확인
#컬럼별로 하나씩 확인하던 중 간단하게 작성하는 방법 검색해서 해결

 

결론 =>

이상치 확인 할 column이 많은 경우 for 함수식을 활용해 간단하게 적용 시킬 수 있다.

 

이상치확인 출력

    if not outliers.empty:
        print(f"Column: {column}")
        print(outliers[[column]])  # 해당 열의 이상치만 출력
        print()  # 줄바꿈

아직 이해하지는 못했지만 코드를 배워서 만족(내일 튜터님에게 물어볼 예정)

+ Recent posts