데이터분석 Day2 - pandas, matplotlib, seaborn, 타이타닉 생존율 예측

데이터 시각화

import pandas as pd

path = './학군연습.xlsx'
school_df = pd.read_excel( path )

import matplotlib.pyplot as plt
import seaborn as sns

# 시각화 기본 폼
plt.figure()  # 시작점

# 핵심적인 데이터 시각화
# 다양한 옵션들

plt.show()   # 종료, 마침표

# 시각화의 내용은 ChatGPT를 활용하여 구체화가 가능함

 

데이터 시각화 목표

  • 우리가 데이터를 분석할 때, 이 데이터를 이해하기 위해서 시각화가 필요함
  • 현미경
  • 데이터 종류에 따라서 어떻게 시각화하여 데이터를 살펴볼지 정리!

한글 폰트 설정

import matplotlib.pyplot as plt
from matplotlib import font_manager, rc

font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
rc('font', family=font_name)
plt.rcParams['axes.unicode_minus'] = False

 

데이터 종류

  • 연속된 숫자, 카테고리
  • 데이터를 활용할 수 있는지 구분을 할 수 있어야함
    • ex) school_df['name']은 사용하기 어려운 데이터
  • 종류의 구분이 어려운 칼럼의 경우, unique()로 확인해본다.
    • ex) tip_df['size']의 경우, 숫자형 데이터이나, 카테고리형임
tip_df = sns.load_dataset('tips')
tip_df['size'].unique()

 

연속된 숫자 데이터 - sns.histplot()

  • 데이터의 분포(어디에 몰려있고, 어떻게 퍼져있는지)
  • 히스토그램 이용!
  • 가운데 솟아있고 좌우로 퍼짐
  • 한쪽으로 솟아있고, 한쪽으로 퍼짐
# tip 데이터
plt.figure(figsize=(5,3))

sns.histplot( tip_df, x='total_bill', bins=25 )

plt.show()
# school 데이터
plt.figure(figsize=(5,3))

sns.histplot( school_df, x='grads', bins=35 )

plt.show()
# 광역시 vs 비광역시 졸업생 분포가 다를 것이다
# '서울특별시', '부산광역시', '대구광역시', '인천광역시', '광주광역시', '대전광역시', '울산광역시', '경기도'

def is_bigCity(x):
	big_list = ['서울특별시', '부산광역시', '대구광역시', '인천광역시', '광주광역시', '대전광역시', '울산광역시', '경기도']
	
	if x in big_list:
		return True
	else :
		return False
		
school_df['is_bigCity'] = school_df['sido'].apply( is_bigCity )

# 광역시만 가져와서 분포를 그려보자!
big_city = school_df[ school_df['is_bigCity'] ]
small_city = school_df[ ~school_df['is_bigCity'] ]

# 그래프 그리기
plt.figure(figsize=(5,3))

# hue - 그래프의 카테고리 분류
sns.histplot(school_df, x='grads', bins=25, hue='is_bigCity')

plt.show()

카테고리 데이터 - sns.countplot()

  • 카테고리 별 데이터의 개수
  • sns의 countplot을 이용
# tip 데이터
plt.figure(figsize=(5,3))

sns.countplot( tip_df, x='day', hue='time' )

plt.show()
plt.figure(figsize=(10,3))

sns.countplot( school_df, x='sido' )
plt.xticks( rotation=90 )

plt.show()

카테고리와 연속된 숫자의 관계 - sns.boxplot()

  • 카테고리 별 숫자 데이터의 분포를확인
  • sns의 boxplot, violinplot
# boxplot
plt.figure(figsize=(5,3))

sns.boxplot( tip_df, x='day', y='tip', hue='time' )

plt.show()
# violinplot
plt.figure(figsize=(5,3))

sns.violinplot(tip_df, x='day', y='tip', hue='time')

plt.show()

숫자와 숫자의 관계 - sns.scatterplot()

  • 2차원 평면 위에 두 데이터가 만나는 지점마다 점을 찍어서 데이터가 어떻게 퍼져있는지 살펴봄
  • 산포도(scatter)
# corr
# -1 ~ 1
tip_df.corr(numeric_only=True)
plt.figure(figsize=(5,3))

sns.scatterplot( tip_df, x='total_bill', y='tip', hue='sex' )
# 회귀선
sns.regplot( tip_df, x='total_bill', y='tip', scatter=False, color='orange')

plt.show()

머신러닝

머신러닝 입문

  • 사이킷런
  • 사용법
    • 모델 생성
    • 모델 훈련
    • 예측, 검증
iris_df = sns.load_dataset('iris')
# 자동으로 히스토그램과 산포도 출력(독립변수가 숫자형일 때 유용)
sns.pairplot(iris_df, hue='species')

plt.show()

KNN( K nearest neighbors)

  • 새로운 데이터
  • 기존의 데이터와 거리를 다 계산
  • 거리로 정렬
  • 짧은거 K개 가져옴
  • 다수결
from sklearn.neighbors import KNeighborsClassifier

# 1. 모델 생성
knn = KNeighborsClassifier()

# 2. 모델 훈련
# 데이터를 feature // targer으로 나누어서 넣어줘야 함
# target : 1차원 (Series)
# feature : 2차원 (DataFrame)

y = iris_df['species'] # target
X = iris_df.iloc[ : , : -1 ]

knn.fit(X,y)

# 예측, 검증
knn.predict( [[5.0, 3.3, 1.5, 0.25]] ) # feature는 2차원으로

# 섞어서 훈련용 데이터 // 검증용 데이터
from sklearn.model_selection import train_test_split

train_s, valid_x, train_y, valid_y = train_test_split( X, y,
                                                       test_size=0.2,
                                                       random_state=3,
                                                       stratify=y)
        	# stratify : 훈련, 테스트의 카테고리 비율을 동일하게 맞춤
# 1. 모델 생성
knn = KNeighborClassifier()

# 2. 모델 훈련
knn.fit( train_x, train_y )

# 3. 검증
# knn.predict( valid_x ) == valid_y
knn.score( valid_x, valid_y )

하이퍼 파라미터 세팅

  • 모델 별로 사용자가 정해줘야 하는 파라미터
  • 데이터에 얼마나 강하게 피팅할 것인지 결정해 줌
    • 오버피팅과 언더피팅 사이의 적절한 모델을 선택해 줌

교차 검증

  • 교차 검증을 이용
    • 교차 검증 검증용 데이터를 최적의 하이퍼 파라미터 값을 찾는데 사용하기엔 데이터 양이 부족하므로, 훈련용 데이터 중 하이퍼 파라미터 검증을 위한 데이터를 임의로 구분한다
    • 예시) 만약 데이터가 100개 있다면, 교차 검증에서는 20개씩 5개의 그룹으로 나누어서 1번 그룹을 테스트, 나머지 80개로 훈련 -> 2번 그룹을 테스트, 나머지 80개로 훈련 이런식으로 5번 반복합니다. 그리고 각각의 테스트 결과를 평균 내어 성능을 평가합니다
# 예시
## knn
### k의 값이 무한대로 설정되어 최빈도값으로 잘못 예측하는 경우 - underfitting
### k의 값이 너무 최소 값으로 설정되어 예측이 잘못된 경우 - overfitting

from sklearn.model_selection import cross_val_score

# k -> 3, 13
knn_3 = KNeighborsClassifier(n_neighbors=3)
# cv 값은 전체 훈련 데이터를 몇 그룹으로 구분할지 설정하는 값
# : 현재 모델에서는 120개를 30개씩으로 그룹
cross_val_score( knn_3, train_x, train_y, cv=4 ).mean()

knn_13 = KNeighborsClassifier(n_neighbors=13)
cross_val_score( knn_13, train_x, train_y, cv=4 ).mean()

# K에 따라 검증 점수 실험
score_list = []
for k in range(1, 31):
	knn = KNeighborsClassifier( n_neighbors=k )
    score = cross_val_score( knn, train_x, train_y, cv=4 ).mean()
    score_list.append(score)
plt.figure(figsize=(5,3))

plt.plot( range(1,31), score_list )

plt.show()

 

(사진 첨부 필요)

 

위의 모델은 하이퍼 파라미터의 값을 설정하는 데 큰 의미가 없음 - 정확도가 0.9 이상이므로

-> 모델을 학습하는 데이터가 너무 완벽한 상태이기 때문

-> 1. 독립 변수가 모두 숫자형임

-> 2. 독립 변수의 데이터 단위가 비슷한 상태임(데이터의 정규화가 필요 없는 상태)

 

=> 모델의 학습 과정에서는 feature engineering이 가장 중요하나, 위 모델은 필요 없는 상태였기 때문에 정확도가 높게 나옴

정확도를 높이기 위해서는 feature engineering이 가장 중요

# 0. EDA
# 1. feature engineering(인코딩, 스케일링)
# 2. 모델 생성(하이퍼 파라미터 세팅)
# 3. 훈련
# 4. 예측, 검증

# 카테고리 변수의 숫자화
# 데이터의 정규화

실습 - 타이타닉 생존율 예측

path_train = r'C:\Users\user\Desktop\실습 데이터\캐글 타이타닉\train.csv'
path_test = r'C:\Users\user\Desktop\실습 데이터\캐글 타이타닉\test.csv'

train_df = pd.read_csv(path_train)
test_df = pd.read_csv(path_test)

train_df.describe()

EDA

# train
train_df.isna().sum() / len(train_df) * 100

# test
test_df.isna().sum() / len(test_df) * 100

결측 데이터 채우기

1. Embarked

# 필터링을 이용해서 결측 데이터 눈으로 확인
train_df[train_df['Embarked'].isna()]
# 카테고리형 데이터의 결측값을 채울 때 : 1. 최빈값 사용
plt.figure()

# sns.countplot(train_df, x='Embarked') # 최빈값 확인
# Embarked와 연관있는 데이터 확인, Fare 활용
sns.boxplot( train_df, x='Embarked', y='Fare', hue='Sex')
plt.ylim([0,120])
plt.axhline( 80, color='red' )

plt.show()
test_df[ test_df['Fare'].isna() ]

2. Fare

# 1. 숫자형 데이터의 경우, 결측값을 채울 때 평균값을 활용
# 2. 히스토그램으로 데이터의 분포를 확인 - 한쪽으로 치우친 경우 평균보단 중앙값을 활용
plt.figure(figsize=(5,3))
sns.histplot( test_df, x='Fare', bins=35 )
plt.show()

# 중앙값과 평균값
test_df['Fare'].median()
test_df['Fare'].mean()

# 세부적으로 결측값 채우기 분석
# pclass와 연관이 있는지 확인
plt.figure(figsize=(5,3))

sns.boxplot(test_df, x='Pclass', y='Fare', hue='Sex')
plt.ylim([0,100])

plt.show()

# 3클래스, 남성의 Fare 중간값
train_df.groupby( ['Pclass', 'Sex'] )['Fare'].median()
test_df.loc[ test_df['Fare'].isna(), 'Fare'] = 7.925

3. Age

# Age의 경우 가운데가 솟아있고 ㅑㅇㅇ쪽으로 균일하게 분포되므로, 평균값으로 결측값을 채움
plt.figure(figsize=(5,3))

sns.histplot(train_df, x='Age', bins=35)

plt.show()
# 세부적으로 확인하기 위해 Age와 연관이 있는 항목을 탐색 - Pclass와 Sex
plt.figure(figsize=(5,3))

sns.boxplot(train_df, x='Pclass', y='Age', hue='Sex')

plt.show()
# Pclass와 Sex 별에 따라 평균 나이를 계산
temp = train_df.groupby(['Pclass','Sex'])['Age'].mean()

# groupby 결과값의 인덱스 값으로 for문 실행
for val in temp.index:
	cond1 = train_df['Age'].isna()
    cond2 = train_df['Pclass'] == val[0]
    cond3 = train_df['Sex'] == val[1]
    train_df.loc[ cond1 & cond2 & cond3, 'Age' ] = temp[ val ]
    
    cond1 = test_df['Age'].isna()
    cond2 = test_df['Pclass'] == val[0]
    cond3 = test_df['Sex'] == val[1]
    test_df.loc[ cond1 & cond2 & cond3, 'Age' ] = temp[ val ]

Feature Engineering

  • 사람에 가까운 데이터를 컴퓨터가 이해하기 좋게 가공하기
  • 카테고리도 아니고 숫자도 아닌 것들에서 데이터를 추출
  • 인코딩(문자->숫자)
  • 스케일링

1. Name

# split
# 'Braund, Mr. Owen Harris'.split(', ')[1].split('.')[0]
def get_name(x):
	return x.split(', ')[1].split('.')[0]
    
train_df['Name2'] = train_df['Name'].apply(get_name)

train_df['Name2'].unique()
plt.figure(figsize=(12,4))

sns.countplot( train_df, x='Name2', hue='Survived')
plt.ylim([0,10])

plt.show()
# 일반남자, 여자, 신분높은남자, 직업인들
# 'Mr' => 'Mr'
# 'Mrs', 'Miss', 'Mme', 'Ms', 'Lady', 'Mlle' => 'Woman'
# 'Master' => 'Master'
# 나머지 => 'Etc'

def get_nameCategory(x):
	if x == 'Mr':
		return 'Mr'
	elif x == 'Master':
		return 'Master'
	elif x in ['Mrs', 'Miss', 'Mme', 'Ms', 'Lady', 'Mlle']:
		return 'Woman'
	else:
		return 'Etc'
		
# train_df['Name2'].unique()

train_df['Name_c'] = train_df['Name2'].apply(get_nameCategory)
train_df['Name_c'].unique()

# test 셋에도 똑같이 적용
test_df['Name2'] = test_df['Name'].apply(get_name)
test_df['Name_c'] = test_df['Name2'].apply(get_nameCategory)

 

2. Family

train_df['Family'] = train_df['Parch'] + train_df['SibSp']

plt.figure(figsize=(5,3))

sns.countplot(train_df, x='Family', hue='Survived')

plt.show()
def get_familyCategory(x):
	if x == 0:
    	return 'Alone'
    elif x in [1,2,3]:
    	return 'Small_f'
    else:
    	return 'Big_f'

train_df['Family_c'] = train_df['Family'].apply(get_familyCategory)

# test 셋에도 적용
test_df['Family'] = test_df['Parch'] + test_df['SibSp']

test_df['Family_c'] = test_df['Family'].apply(get_familyCategory)

3. 스케일링

  • 표준화 스케일링
    • 평균으로 빼서 편차로 나누어 줌
  • 아웃라이어가 있으면 스케일링에 나쁜 영향 => 처리부터 하고
    • 아웃라이어 : 히스토그램으로 확인 시 데이터의 분포가 한쪽으로 치우친 경우, 우측이나 좌측의 데이터는 표준화에 방해가 되는 데이터 이므로 처리가 필요함 => log 변환 사용
# Fare의 경우 한쪽으로 치우친 분포의 히스토그램 형태가 나오기 때문에 표준화 전 로그 변환 후 스케일링 진행
# 오른쪽의 값이 너무 커서 데이터의 영향을 줄 수 있기 때문에(아웃라이어)
# Fare log 변환
import numpy as np

# np.log(train_df['Fare'] + 1)
# log 사용시 x 값이 0이 있으면 경고 발생 --> +1 필요 ==> log1p
train_df['Fare_log'] = np.log1p( train_df['Fare'] )
# 일괄적으로 숫자 데이터들을 스케일링 하기
# ex) (train_df['Age'] - train_df['Age'].mean()) / train_df['Age'].std()
# 1. 스케일러 만들기
# 2. 데이터를 넣고 훈련
# 3. 실제로 스케일링을 수행

from sklearn.preprocessing import StandardScaler

# 1. 스케일러를 만들기
standard_sc = StandardScaler()
# 2. 데이터를 넣고 훈련
standard_sc.fit( train_df[['Age', 'Fare_log']] )
# 3. 실제로 스케일링을 수행
train_df[ ['Age_s', 'Fare_s'] ] = standard_sc.transform( train_df[['Age', 'Fare_log']] )

# test에도
test_df['Fare_log'] = np.log1p( test_df['Fare'] )
test_df[ ['Age_s', 'Fare_s'] ] = standard_sc.transform( test_df[['Age', 'Fare_log']] )

원핫인코딩

  • 카테고리 데이터를 숫자형으로 변환 시 사용 ( 카테고리 => 숫자 )
  • 데이터의 개수는 같으나, 컬럼(feature)의 개수가 증가 => 차원의 저주 : 모델의 성능이 안좋아짐
    • (공백의 영향) => 차원의 축소 필요
cate_cols = ['Pclass', 'Sex', 'Embarked', 'Name_c', 'Family_c']

# feature의 개수를 줄일수록 모델 성능에 좋기 때문에 drop_first=True로 적용
train_final = pd.get_dummies( train_df, columns=cate_cols, dtype='int', drop_first=True )
test_final = pd.get_dummies( test_df, columns=cate_cols, dtype='int', drop_first=True )

# 스케일링 => 인코딩
feature_names = ['Age_s', 'Fare_s',
                'Pclass_2', 'Pclass_3', 'Sex_male', 'Embarked_Q', 'Embarked_S',
                'Name_c_Master', 'Name_c_Mr', 'Name_c_Woman', 'Family_c_Big_f',
                'Family_c_Small_f']
train_final[feature_names]
  • 제출
train_final.to_csv(r'train_final.csv', index=False)
test_final.to_csv(r'test_final.csv', index=False)