20회 ADP 실기 python 통계분석(데이터 처리)


데이터 처리 Part

해당 부분은 데이터마이닝에서도 포함이 될 수는 있지만

일단은 통계분석으로 넣은 이유는 데이터 마이닝에서 해당 분석법이 안 쓰일 수 있고

일단 ADP 쪽 데이터마이닝에서의 핵심은 모델링이기 때문이다

그렇기에, 데이터마이닝에서도 포함이 되긴 하지만,

통계분석에서도 해당 분석법을 사용하여서 분류분석 단계로 넘어갈 수 있을 정도의 분석법들을 정리하고자 한다.

총 항목은 5개로

  1. 결측치 시각화 및 피벗테이블 등 통계분석시 나올 만한 가공법

  2. 값 scaling

  3. PCA, FA

  4. 군집분석 Kmeans

  5. 반응변수 샘플링(under, over, smote)

로 진행해보려 한다.

import os
import numpy as np
import pandas as pd
pd.set_option('display.max_row', 500)
pd.set_option('display.max_columns', 30)
from pandas import DataFrame

import warnings
warnings.filterwarnings("ignore")

결측치 시각화 및 처리, 피벗테이블 등 데이터 가공

간단한 EDA로 데이터의 결측치를 시각화 하고 제거하거나 대체하는 방법을 간단하게 해놓고

이러한 류의 데이터가 아닌 피벗테이블 같은 방식으로 그룹화를 해서 진행을 해야될 경우를 위하여

피벗테이블로 가공을 하는 방법도 해놓았다.

데이터는 타이타닉 데이터를 사용하였고, 다운을 받으려면 https://www.kaggle.com/c/titanic/data 에서 받기 바랍니다.

train = pd.read_csv("train.csv")
print(train.shape)
train.head()
(891, 12)
PassengerIdSurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarked
0103Braund, Mr. Owen Harrismale22.010A/5 211717.2500NaNS
1211Cumings, Mrs. John Bradley (Florence Briggs Th...female38.010PC 1759971.2833C85C
2313Heikkinen, Miss. Lainafemale26.000STON/O2. 31012827.9250NaNS
3411Futrelle, Mrs. Jacques Heath (Lily May Peel)female35.01011380353.1000C123S
4503Allen, Mr. William Henrymale35.0003734508.0500NaNS

missingno 패지키를 사용하면 전체 데이터에서 결측치가 있는 부분은 흰색 선이 나오고

값이 있는 데이터는 정상적인 검정색으로 나온다

import missingno

missingno.matrix(train)
<AxesSubplot:>

absolute

전체 데이터에서의 null의 총 갯수를 보고 싶으면 isnull을 쓰고 sum을 하면 된다

train.isnull().sum()
PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age            177
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         2
dtype: int64

Age 같은 경우는 그나마 결측치 비율이 작으므로 평균 값으로 대체를 하고 진행을 해보았다

train.loc[train.isnull()['Age'],'Age'] = train['Age'].mean()

train.isnull().sum()
PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age              0
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         2
dtype: int64

Cabin은 결측치가 많으므로 해당 열을 삭제하고 Embarked의 경우 2개만 결측치이므로 dropna로 해당 열만 삭제하였다

train2 = train.drop(columns=['Cabin']).dropna(axis=0)

train2.isnull().sum()
PassengerId    0
Survived       0
Pclass         0
Name           0
Sex            0
Age            0
SibSp          0
Parch          0
Ticket         0
Fare           0
Embarked       0
dtype: int64

그 다음으로는 피벗테이블화 시켜서 그룹별로 원하는 값을 뽑아내는 방법입니다.

피벗테이블로 성별, 생존별 나이의 중앙값을 구한 방법입니다.

margins를 사용하는 경우, 전체에 대한 것도 같이 구해줍니다.

pivot1 = pd.pivot_table(train2, index='Sex', columns='Survived', values='Age', aggfunc='mean', margins=True)
pivot1
Survived01All
Sex
female26.02327228.79726528.077094
male31.17522427.63170530.505824
All30.41510028.42360029.653446

만든 피벗 테이블에 unstack이나 stack을 해주면 해당 인덱스와 컬럼이 series 형식으로 풀리며

이 상황에서 reset_index를 해주면 피벗테이블을 만들면서 넣은 열들의 그룹별로의 값이 담긴 DataFrame이 만들어진다

pivot1.unstack().reset_index()
SurvivedSex0
00female26.023272
10male31.175224
20All30.415100
31female28.797265
41male27.631705
51All28.423600
6Allfemale28.077094
7Allmale30.505824
8AllAll29.653446
pivot1.stack().reset_index()
SexSurvived0
0female026.023272
1female128.797265
2femaleAll28.077094
3male031.175224
4male127.631705
5maleAll30.505824
6All030.415100
7All128.423600
8AllAll29.653446

피벗 테이블을 단순하게 만드는 것이 아닌, 여러 열을 더 넣고 하려면 list 안에 넣고 하면 된다

pd.pivot_table(train2, index=['Sex','Parch'], columns='Survived', values='Age', aggfunc='median')
Survived01
SexParch
female028.00000029.699118
128.50000028.349559
216.00000022.000000
348.00000024.000000
437.000000NaN
539.00000038.000000
643.000000NaN
male029.69911829.699118
128.0000006.000000
226.0000003.500000
316.000000NaN
452.000000NaN
539.000000NaN
pd.pivot_table(train2, index=['Sex','Parch'], columns=['Survived','SibSp'], values=['Age','Fare'], aggfunc='median')
AgeFare
Survived0101
SibSp012345801234012345801234
SexParch
female029.69911827.00000018.0NaNNaNNaNNaN29.69911829.69911841.34955931.5NaN8.0500014.458318.0000NaNNaNNaNNaN13.0000052.5500037.364618.425NaN
118.00000028.500000NaN29.699118NaNNaNNaN25.00000034.5000004.000000NaNNaN14.4542015.3729NaN25.46670NaNNaNNaN33.0000029.5000019.2583NaNNaN
229.69911825.00000015.05.5000007.516.029.69911823.00000023.00000019.50000023.511.017.72915151.550034.375027.9000031.2750046.969.5524.1791553.28960262.3750263.00019.65625
3NaN48.000000NaNNaNNaNNaNNaN24.00000054.00000024.000000NaNNaNNaN34.3750NaNNaNNaNNaNNaN19.2583023.0000018.7500NaNNaN
429.00000045.000000NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN21.0750027.9000NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
540.00000039.000000NaNNaNNaNNaNNaNNaN38.000000NaNNaNNaN34.4062531.2750NaNNaNNaNNaNNaNNaN31.38750NaNNaNNaN
6NaN43.000000NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN46.9000NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
male029.69911829.69911828.0NaNNaNNaNNaN29.69911831.50000039.849559NaNNaN8.0500021.000021.6792NaNNaNNaNNaN13.4312554.2708578.4500NaNNaN
140.00000033.00000023.015.8495597.0NaNNaN14.5000006.0000001.000000NaNNaN61.6792024.150011.500023.2708539.68750NaNNaN37.9166517.3250039.0000NaNNaN
238.50000027.849559NaN10.0000006.510.029.6991184.0000006.000000NaNNaN3.069.6375025.6000NaN27.9000031.3312546.969.5537.00420120.00000NaNNaN31.38750
3NaN16.000000NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN34.3750NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
4NaN52.000000NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN145.4500NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
5NaN39.000000NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN31.2750NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN

값 scaling

값 scaling 같은 경우는 각 변수들마다 평균과 분산의 크기가 다른 경우가 많을텐데

표준화 단계를 통해서 데이터의 왜곡으로 인한 문제를 피할 수 있게 해줍니다.

train, test로 나누어서 scaling을 할 때에는 train의 값을 이용해서

test data에 적용을 해줘야지, train은 train끼리 test는 test끼리 적용을 하면 안됩니다.

f_train = pd.read_csv("https://rfriend.tistory.com/attachment/cfile21.uf@2577A64555C56ECF0CF0C8.csv",
           encoding='cp949')
del f_train['company']
f_train.head()
V1V2V3V4V5
02.4311.1018.46441.670.90
13.099.9529.46239.430.90
22.226.8628.62249.360.69
35.7623.1923.47326.091.43
41.605.6425.64289.981.42

MinMaxScaler는 최소값과 최대값을 각각 0과 1이 되게 해주는 스케일링이며

StandardScaler는 평균과 표준편차를 이용해서 하는 방법이다.

test data에 적용하고 싶은 경우는 Scaler에 transform(test_data)를 하면 된다

from sklearn.preprocessing import StandardScaler

standardScaler = StandardScaler()

standardScaled = standardScaler.fit_transform(f_train)

scaled_data = DataFrame(standardScaled, columns=f_train.columns)
scaled_data.head()
V1V2V3V4V5
0-0.5481580.393974-0.9448591.3777810.052140
10.0498330.134995-0.320690-0.0771020.052140
2-0.738427-0.560872-0.368354-0.005667-0.545097
32.4689753.116637-0.6605790.5463161.559452
4-1.300176-0.835615-0.5374470.2865471.531012
scaled_data.describe().iloc[0:3]
V1V2V3V4V5
count1.800000e+011.800000e+011.800000e+0118.0000001.800000e+01
mean2.097088e-168.635068e-17-2.898916e-160.0000001.788693e-16
std1.028992e+001.028992e+001.028992e+001.0289921.028992e+00

표본 갯수가 애초에 작아서 std가 값이 1보다는 살짝은 큰 모습입니다.

하지만 어느정도 평균은 0에 가까운 모습을 보이고 있기에

원하는 대로 scaling이 완료된 모습입니다.

from sklearn.preprocessing import MinMaxScaler

min_max_scaler = MinMaxScaler()

MinMax_train = min_max_scaler.fit_transform(f_train)

MinMax_train = DataFrame(MinMax_train, columns=f_train.columns)
MinMax_train.head()
V1V2V3V4V5
00.1995190.3812690.0187690.9278890.595420
10.3581730.3224160.2252250.4591120.595420
20.1490380.1642780.2094590.4821290.435115
31.0000001.0000000.1128000.6599831.000000
40.0000000.1018420.1535290.5762830.992366
MinMax_train.describe().T[['min','max']]
minmax
V10.01.0
V20.01.0
V30.01.0
V40.01.0
V50.01.0

최소값과 최대값이 확실히 0과 1인 것을 확인할 수 있습니다.

둘 중 제시되는 방법을 사용하시거나

둘을 비교하는 문제가 나올 경우에는 둘 다 수행한 뒤에

생각하시는 장단점을 서술하는 방식으로 하시면 될 듯 합니다.

PCA & FA

이 둘의 차이점에 대한 포스팅은

https://ysyblog.tistory.com/124 해당 블로그를 보시면 자세히 보실 수 있습니다

PCA는 데이터 마이닝쪽에서 나올 가능성이 높고,

만약에 나온다면 PCA에 사용된 변수들은 버리고 pca로 나온 변수들을 사용해서 하면 됩니다.

FA가 나온다면 통계분석 영역에서 나올 가능성이 높습니다.

굳이 pca가 있는데 fa를 머신러닝에서 사용하는 경우는 본 적이 없습니다.

PCA

표준화 된 데이터로 학습을 시키고 pca componenents의 구성요소대로 변환을 수행한 다음에 score에 저장합니다.

pca.components_로 변수들이 어떻게 묶였는지 볼 수는 있지만

높은 값을 가지고 있는 변수들 끼리 묶어서 어떠한 변수로 이름을 지어보겠다는 가능하지

높은 값을 가진 것들만 사용하는 것은 안됩니다.

from sklearn.decomposition import PCA

pca = PCA()

pca.fit(scaled_data )

score = pca.transform(scaled_data)

pca.components_
array([[ 0.07608427, -0.39463007,  0.56970191, -0.5598277 , -0.44778451],
       [ 0.77966993,  0.56541218,  0.16228156, -0.19654293,  0.08636803],
       [ 0.0008916 , -0.29532165,  0.24122211, -0.25659729,  0.88811827],
       [ 0.1407554 , -0.11764417,  0.63772189,  0.74809431,  0.00366842],
       [-0.60540325,  0.65078503,  0.42921686, -0.14992183,  0.05711464]])

ratio는 각 주성분들이 전체 데이터에서 가지고 있는 분산 비율입니다.

제2주성분까지 합쳐보면 약 87퍼입니다.

ratio = pca.explained_variance_ratio_
ratio
array([0.55229245, 0.32113064, 0.11011261, 0.01281263, 0.00365167])
import matplotlib.pyplot as plt

ax = plt.plot(['PC1','PC2','PC3','PC4','PC5'], ratio)
plt.show()

absolute

주성분에서 몇 개의 주성분까지만 선택할 고르는 방법은 기울기가 완만해지는 시점이나

전체 분산 비율이 70퍼에서 90퍼 정도 사이에 있으면 분석가의 판단하에 선택합니다.

3개를 선택해도 되긴 하지만 일단 2개만 선택하도록 하겠습니다.

pca = PCA(n_components=2)

pca_data = pca.fit_transform(scaled_data )

DataFrame(pca_data, columns=['PC1','PC2']).head()
PC1PC2
0-1.530135-0.624247
1-0.2123630.082795
20.202561-0.998596
3-2.4225433.607282
4-0.921329-1.497481

FA

같은 데이터로 FA도 해보고 싶지만, 원하는 방식대로 결과가 나오지 않아서

다른 데이터를 가지고 와서 수행하였습니다.

from factor_analyzer import FactorAnalyzer
df = pd.read_csv("https://vincentarelbundock.github.io/Rdatasets/csv/psych/bfi.csv",
                index_col=0)
df.drop(['gender','education','age'],axis=1,inplace=True)
df.dropna(inplace=True)
df.head(2)
A1A2A3A4A5C1C2C3C4C5E1E2E3E4E5N1N2N3N4N5O1O2O3O4O5
616172.04.03.04.04.02.03.03.04.04.03.03.03.04.04.03.04.02.02.03.03.063.04.03.0
616182.04.05.02.05.05.04.04.03.04.01.01.06.04.03.03.03.03.05.05.04.024.03.03.0

요인성 평가

  1. Bartlett test

귀무가설 : 탐색적 요인 분석에 적합한 데이터가 아니다.

  1. kmo 검정 (변수 최소 3개)

관측된 모든 변수간 분산 비율 추정

0.6미만은 부적절, 0.8이상은 우수

from factor_analyzer.factor_analyzer import calculate_bartlett_sphericity

calculate_bartlett_sphericity(df)
(18170.966350869257, 0.0)
from factor_analyzer.factor_analyzer import calculate_kmo

kmo_all, kmo_model = calculate_kmo(df)

kmo_model
0.848539722194922

요인 수 선택

eigen value가 1이상일때 까지 혹은 기울기가 완만해지는 시점까지 진행

fa = FactorAnalyzer(n_factors = df.shape[1], rotation=None)
fa.fit(df)

ev, v = fa.get_eigenvalues()
ev
array([5.13431118, 2.75188667, 2.14270195, 1.85232761, 1.54816285,
       1.07358247, 0.83953893, 0.79920618, 0.71898919, 0.68808879,
       0.67637336, 0.65179984, 0.62325295, 0.59656284, 0.56309083,
       0.54330533, 0.51451752, 0.49450315, 0.48263952, 0.448921  ,
       0.42336611, 0.40067145, 0.38780448, 0.38185679, 0.26253902])
plt.scatter(range(1,df.shape[1]+1),ev)
plt.plot(range(1,df.shape[1]+1),ev)
[<matplotlib.lines.Line2D at 0x22582431730>]

absolute

import seaborn as sns

fa = FactorAnalyzer(n_factors = 6, rotation=None)
fa.fit(df)

efa_result = pd.DataFrame(fa.loadings_, index = df.columns)

plt.figure(figsize=(6,10))
sns.heatmap(efa_result, annot=True, cmap='Blues', fmt='.2f');

absolute

0부터 5까지 중에서

각각 0은 N, 1은 E, 2는 C, 3은 A, 4는 O에 대해서 높은 요인 적재량을 가진다

fa = FactorAnalyzer(n_factors = 5, rotation='varimax') #최대 우도 방법
fa.fit(df)

df1 = pd.DataFrame(fa.get_factor_variance())

df1.index = ['SS Loading','Proportion Var','Cumulative Var'] # ss loading = 각 factor의 설명력
df1
01234
SS Loading2.7096332.4730902.0411061.8444981.522153
Proportion Var0.1083850.0989240.0816440.0737800.060886
Cumulative Var0.1083850.2073090.2889530.3627330.423619

cf) 신뢰도 계수 구하기

def CronbachAlpha(item):
    item = np.asarray(item)
    itemvars = item.var(axis=0, ddof=1)
    tscores = item.sum(axis=1)
    nitems = item.shape[1]
    return (nitems/(nitems-1)) * (1 - (itemvars.sum() / tscores.var(ddof=1) ))
factors = ['A','C','E','N','O']
items_dict = {}
for factor in factors:
    items_dict[factor] = [x for x in df.columns if x[0] == factor]
    
for key, value in items_dict.items():
    print(key)
    print(CronbachAlpha(df[value]))
    print()
A
0.4437533019387808

C
-0.31628341382240494

E
-0.6552429584732491

N
0.8169468842774033

O
-0.176098107937655

마이너스가 아닌 N과 A가 신뢰도가 높다고 할 수 있는 듯 하다.

군집분석(Kmeans) (som은 패키지가 없고 직접 구현해야함)

군집분석에 대한 설명은 som을 편하게 할 수 있는 R에서 자세히 하려고 하는데

비지도학습이다보니까 반응 변수가 필요 없고, 숫자 데이터들을 몇 개의 그룹으로 묶을지 해놓으면

새로 들어오는 데이터는 어떠한 그룹으로 들어가는지도 예측을 해주는 방식이다

from sklearn.cluster import KMeans
import numpy as np
X = np.array([[1, 2,3], [1, 4,2], [1, 0,0],
              [10, 2,5], [10, 4,7], [1, 0,3]])
X = DataFrame(X)
kmeans = KMeans(n_clusters=3, random_state=0).fit(X)
kmeans.labels_
array([2, 2, 0, 1, 1, 0])
kmeans.predict([[0, 0,3], [12, 3,7]])
array([0, 1])
kmeans.cluster_centers_
array([[ 1. ,  0. ,  1.5],
       [10. ,  3. ,  6. ],
       [ 1. ,  3. ,  2.5]])

반응 변수 샘플링(under, over, smote)

smote의 경우, 범주형 변수가 있다면, 미리 one-hot encoding등을 사용해서 변환을 해줘야지 반응변수 샘플링이 가능하다.

under sampling과 over sampling은 단순하게 늘리거나 줄이는 방식이라 상관 없다.

from collections import Counter

url = 'https://raw.githubusercontent.com/jbrownlee/Datasets/master/glass.csv'
df = pd.read_csv(url, header=None)
data = df.values
X, y = data[:, :-1], data[:, -1]

X[0]
array([1.52101e+00, 1.36400e+01, 4.49000e+00, 1.10000e+00, 7.17800e+01,
       6.00000e-02, 8.75000e+00, 0.00000e+00, 0.00000e+00])
from imblearn.under_sampling import RandomUnderSampler
X_resampled, y_resampled = RandomUnderSampler(random_state=0).fit_resample(X, y)
Counter(y_resampled)
Counter({1.0: 9, 2.0: 9, 3.0: 9, 5.0: 9, 6.0: 9, 7.0: 9})
from imblearn.over_sampling import RandomOverSampler
X_resampled, y_resampled = RandomOverSampler(random_state=0).fit_resample(X, y)
Counter(y_resampled)
Counter({1.0: 76, 2.0: 76, 3.0: 76, 5.0: 76, 6.0: 76, 7.0: 76})
from imblearn.over_sampling import SMOTE
X_resampled, y_resampled = SMOTE(random_state=0).fit_resample(X, y)
Counter(y_resampled)
Counter({1.0: 76, 2.0: 76, 3.0: 76, 5.0: 76, 6.0: 76, 7.0: 76})

20회 ADP 실기를 준비하면서 만든 notebook 파일에 대한 깃허브 링크 입니다.