오늘은 강의나 복습보다 다음주에 있을 과제 준비를 하며 공부를 하였습니다.
발표자료 만들기와 발표가 저의 파트이지만 발표를 제대로 준비하기 위해서 코드들이 어떻게 흘러가는지 흐름을 파악 하자는 생각으로 진행하였습니다.
- DATA LOAD
- DATA FEATURE 분석
- DATA 전처리
- 모델학습 (로지스틱, 랜덤 포레스트, XGBoost)
- 회고(진행하며 오류, 해결, 지식 등)
이 순서대로 학습을 진행하였고 소개해 볼까 합니다.
☑️ 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() # 줄바꿈
아직 이해하지는 못했지만 코드를 배워서 만족(내일 튜터님에게 물어볼 예정)
'TIL' 카테고리의 다른 글
내일배움캠프 21일차 TIL + 문제은행, 기초수학 특강 (1) | 2024.10.25 |
---|---|
내일배움캠프 20일차 TIL + Netfilx리뷰 전처리 (1) | 2024.10.24 |
내일배움캠프 18일차 TIL + 노션, GITHUB, 머신러닝 코드 분석 (2) | 2024.10.22 |
내일배움캠프 17일 차 TIL + 머신러닝 코드 (4) | 2024.10.21 |
내일배움캠프 15일차 TIL + 딥러닝 (신경망, 어텐션, 자연어 처리) (3) | 2024.10.18 |