로지스틱 회귀 분류기 튜토리얼
로지스틱 회귀 분류기 튜토리얼
캐글에 있는 Prashant Banerjee라는 분이 작성한 로지스틱 회귀 분류기 튜토리얼Logistic Regression Classifier Tutorial을 정리하려고 한다.
파이썬을 이용한 로지스틱 회귀 분류기 튜토리얼
이 커널에서는 파이썬과 사이킷런을 사용해 로지스틱 회귀(Logistic Regression)를 구현한다. 호주에 내일 비가 올지 예측하는 이진 분류 모델을 로지스틱 회귀 분류기로 구축한다.
목차
20. GridSearch CV를 사용한 하이퍼파라미터 최적화
1. 로지스틱 회귀에 대한 소개
데이터 과학자들이 새로운 분류 문제를 마주하게 될 때, 가장 먼저 떠오르는 알고리즘 중 하나가 로지스틱 회귀(Logistic Regression)이다. 이는 이산적인 클래스로 관측치를 예측하기 위해 사용되는 지도학습 분류 알고리즘이다. 실제로는 관측치를 다른 카테고리로 분류하는 데 사용된다. 따라서 출력은 이산적인 성격을 가지게 된다. 로지스틱 회귀는 Logit 회귀라고도 불리며, 가장 간단하고 직관적이며 다재다능한 분류 알고리즘 중 하나이다. 분류 문제를 해결하는 데 사용된다.
2. 로지스틱 회귀의 이해
통계학에서 로지스틱 회귀 모델은 주로 분류 목적으로 사용되는 널리 사용되는 통계 모델이다. 이는 일련의 관측치가 주어지면 로지스틱 회귀 알고리즘을 사용하여 이러한 관측치를 두 개 이상의 이산적인 클래스로 분류하는 데 도움이 된다. 따라서 대상 변수는 이산적인 성격을 가지게 된다.
로지스틱 회귀 알고리즘은 다음과 같이 작동한다 -
선형 방정식 구현
로지스틱 회귀 알고리즘은 독립 변수 또는 설명 변수와 함께 선형 방정식을 구현하여 반응 값을 예측한다. 예를 들어, 우리는 공부한 시간과 시험 합격 확률의 예를 고려한다. 여기서, 공부한 시간은 설명 변수이고 x1로 나타내며, 시험 합격 확률은 반응 또는 대상 변수이고 z로 나타낸다.
만약 우리가 하나의 설명 변수(x1)와 하나의 반응 변수(z)를 가진다면, 선형 방정식은 다음과 같은 수식으로 나타낼 수 있다.
z = β0 + β1x1
여기서, 계수 β0
와 β1
은 모델의 매개변수이다.
만약 여러 개의 설명 변수가 있다면, 위의 방정식은 다음과 같이 확장될 수 있다.
z = β0 + β1x1+ β2x2 + ... + βnxn
여기서 β0
, β1
, β2
및 βn
은 모델의 매개변수이다.
따라서, 예측된 반응 값은 위의 방정식에 의해 결정되며, z
로 표시된다.
시그모이드 함수
z라는 예측된 응답 값은, 0과 1 사이의 확률값으로 변환된다. 예측된 값에서 확률값을 얻기 위해서 시그모이드 함수를 사용한다. 이 시그모이드 함수는 모든 실수 값을 0과 1 사이의 확률 값으로 매핑한다.
머신러닝에서, 시그모이드 함수는 예측 값을 확률 값으로 매핑하는 데 사용된다. 시그모이드 함수는 S 모양의 곡선을 가지며, 이를 시그모이드 곡선이라고도 한다.
시그모이드 함수는 로지스틱 함수의 특수한 경우 중 하나이다. 이는 다음 수식으로 나타낼 수 있다.
그래프로는 다음과 같이 시그모이드 함수를 표현할 수 있다.
시그모이드 함수
결정 경계Decision boundary
시그모이드 함수는 0과 1 사이의 확률 값을 반환한다. 이 확률 값은 "0"
또는 "1"
인 이진 클래스로 매핑된다. 이 확률 값을 이진 클래스로 매핑하려면 임계값을 선택해야 한다. 이 임계값을 결정 경계(Decision boundary)라고 한다. 이 임계값 이상에서는 확률 값을 클래스 1로 매핑하고, 이하에서는 클래스 0으로 매핑한다.
수학적으로는 다음과 같이 표현할 수 있다.
p ≥ 0.5 => class = 1
p < 0.5 => class = 0
일반적으로 결정 경계는 0.5로 설정된다. 따라서, 만약 확률 값이 0.8(>0.5)이면 해당 관측치를 1로 매핑한다.(0.5보다 작을 때는 0으로 매핑) 이는 아래 그래프에서 표현된다.
예측하기
이제 로지스틱 회귀에서 시그모이드 함수와 결정 경계에 대해 알았으므로, 우리는 이 지식을 활용해 예측 함수를 작성할 수 있다. 로지스틱 회귀의 예측 함수는 관측값이 긍정적인 클래스 1일 확률을 반환한다. 이는 P(class=1)로 나타내며, 확률이 1에 가까워질수록 해당 관측값이 클래스 1에 속한다는 모델에 대한 더 높은 신뢰도를 가진다. 그렇지 않으면 클래스 0에 속한다고 할 수 있다.
3. 로지스틱 회귀의 가정
로지스틱 회귀 모델은 몇 가지 주요 가정이 필요하다. 이는 다음과 같다.
-
로지스틱 회귀 모델은 종속 변수가 이진, 다항 또는 순서형이어야한다.
-
각 관측치는 서로 독립적이어야한다. 즉, 관측치는 반복 측정에서 나오면 안된다.
-
로지스틱 회귀 알고리즘은 독립 변수 간 다중공선성이 적거나 없어야한다. 즉, 독립 변수는 서로 높은 상관 관계를 가지면 안된다.
-
로지스틱 회귀 모델은 독립 변수 및 로그 오즈의 선형성을 가정한다.
-
로지스틱 회귀 모델의 성공은 샘플 크기에 따라 달려 있다. 일반적으로 높은 정확도를 얻으려면 큰 샘플 크기가 필요하다.
4. 로지스틱 회귀의 유형
로지스틱 회귀 모델은 대상 변수 범주에 따라 세 가지 그룹으로 분류할 수 있다. 이는 다음과 같다.
-
이항 로지스틱 회귀 이항 로지스틱 회귀에서 대상 변수는 두 가지 가능한 범주를 가지고 있다. 대표적인 범주 예시로는 yes 혹은 no, good 혹은 bad, true 혹은 false, spam 혹은 no spam 그리고 pass 혹은 fail이 있다.
-
다항 로지스틱 회귀 다항 로지스틱 회귀에서 대상 변수는 특정 순서가 없는 세 개 이상의 범주를 가진다. 따라서 세 개 이상의 명목 범주가 있다. 이에 대한 예시로는 과일의 종류 - 사과, 망고, 오렌지, 바나나 등이 있다.
-
순서형 로지스틱 회귀 순서형 로지스틱 회귀에서 대상 변수는 세 개 이상의 순서형 범주를 가진다. 즉, 범주 간에 내재적인 순서가 있다. 예를 들어 학생 성적을 poor, average, good 그리고 excellent로 분류할 수 있다.
5. 필요한 라이브러리 불러오기
# This Python 3 environment comes with many helpful analytics libraries installed
# It is defined by the kaggle/python docker image: https://github.com/kaggle/docker-python
# For example, here's several helpful packages to load in
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import matplotlib.pyplot as plt # data visualization
import seaborn as sns # statistical data visualization
%matplotlib inline
# Input data files are available in the "../input/" directory.
# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory
import os
for dirname, _, filenames in os.walk('/kaggle/input'):
for filename in filenames:
print(os.path.join(dirname, filename))
# Any results you write to the current directory are saved as output.
/kaggle/input/weather-dataset-rattle-package/weatherAUS.csv
import warnings
warnings.filterwarnings('ignore')
6. 데이터셋 불러오기
data = '/kaggle/input/weather-dataset-rattle-package/weatherAUS.csv'
df = pd.read_csv(data)
7. 탐색적 데이터 분석
데이터를 분석해보자.
# view dimensions of dataset
df.shape
(142193, 24)
142193개의 인스턴스와 24개의 변수가 있음을 확인할 수 있다.
# preview the dataset
df.head()
Date | Location | MinTemp | MaxTemp | Rainfall | Evaporation | Sunshine | WindGustDir | WindGustSpeed | WindDir9am | ... | Humidity9am | Humidity3pm | Pressure9am | Pressure3pm | Cloud9am | Cloud3pm | Temp9am | Temp3pm | RainToday | RainTomorrow | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2008-12-01 | Albury | 13.4 | 22.9 | 0.6 | NaN | NaN | W | 44.0 | W | ... | 71.0 | 22.0 | 1007.7 | 1007.1 | 8.0 | NaN | 16.9 | 21.8 | No | No |
1 | 2008-12-02 | Albury | 7.4 | 25.1 | 0.0 | NaN | NaN | WNW | 44.0 | NNW | ... | 44.0 | 25.0 | 1010.6 | 1007.8 | NaN | NaN | 17.2 | 24.3 | No | No |
2 | 2008-12-03 | Albury | 12.9 | 25.7 | 0.0 | NaN | NaN | WSW | 46.0 | W | ... | 38.0 | 30.0 | 1007.6 | 1008.7 | NaN | 2.0 | 21.0 | 23.2 | No | No |
3 | 2008-12-04 | Albury | 9.2 | 28.0 | 0.0 | NaN | NaN | NE | 24.0 | SE | ... | 45.0 | 16.0 | 1017.6 | 1012.8 | NaN | NaN | 18.1 | 26.5 | No | No |
4 | 2008-12-05 | Albury | 17.5 | 32.3 | 1.0 | NaN | NaN | W | 41.0 | ENE | ... | 82.0 | 33.0 | 1010.8 | 1006.0 | 7.0 | 8.0 | 17.8 | 29.7 | No | No |
5 rows × 23 columns
col_names = df.columns
col_names
Index(['Date', 'Location', 'MinTemp', 'MaxTemp', 'Rainfall', 'Evaporation',
'Sunshine', 'WindGustDir', 'WindGustSpeed', 'WindDir9am', 'WindDir3pm',
'WindSpeed9am', 'WindSpeed3pm', 'Humidity9am', 'Humidity3pm',
'Pressure9am', 'Pressure3pm', 'Cloud9am', 'Cloud3pm', 'Temp9am',
'Temp3pm', 'RainToday', 'RainTomorrow'],
dtype='object')
RISK_MM 특성 지우기
RISK_MM 특성 변수를 데이터 세트에서 삭제해야한다는 데이터 세트 설명이 있으므로 다음과 같이 삭제해야한다.
df.drop(['RISK_MM'], axis=1, inplace=True)
# view summary of dataset
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 142193 entries, 0 to 142192
Data columns (total 23 columns):
Date 142193 non-null object
Location 142193 non-null object
MinTemp 141556 non-null float64
MaxTemp 141871 non-null float64
Rainfall 140787 non-null float64
Evaporation 81350 non-null float64
Sunshine 74377 non-null float64
WindGustDir 132863 non-null object
WindGustSpeed 132923 non-null float64
WindDir9am 132180 non-null object
WindDir3pm 138415 non-null object
WindSpeed9am 140845 non-null float64
WindSpeed3pm 139563 non-null float64
Humidity9am 140419 non-null float64
Humidity3pm 138583 non-null float64
Pressure9am 128179 non-null float64
Pressure3pm 128212 non-null float64
Cloud9am 88536 non-null float64
Cloud3pm 85099 non-null float64
Temp9am 141289 non-null float64
Temp3pm 139467 non-null float64
RainToday 140787 non-null object
RainTomorrow 142193 non-null object
dtypes: float64(16), object(7)
memory usage: 25.0+ MB
변수 유형
이 섹션에서는 데이터 세트를 범주형 변수와 수치형 변수로 분류한다. 데이터 세트에는 범주형 및 수치형 변수가 혼합되어 있다. 범주형 변수는 데이터 유형이 객체이다. 수치형 변수는 데이터 유형이 float64이다.
우선 범주형 변수를 찾아보자.
# find categorical variables
categorical = [var for var in df.columns if df[var].dtype=='O']
print('There are {} categorical variables\n'.format(len(categorical)))
print('The categorical variables are :', categorical)
There are 7 categorical variables
The categorical variables are : ['Date', 'Location', 'WindGustDir', 'WindDir9am', 'WindDir3pm', 'RainToday', 'RainTomorrow']
# view the categorical variables
df[categorical].head()
Date | Location | WindGustDir | WindDir9am | WindDir3pm | RainToday | RainTomorrow | |
---|---|---|---|---|---|---|---|
0 | 2008-12-01 | Albury | W | W | WNW | No | No |
1 | 2008-12-02 | Albury | WNW | NNW | WSW | No | No |
2 | 2008-12-03 | Albury | WSW | W | WSW | No | No |
3 | 2008-12-04 | Albury | NE | SE | E | No | No |
4 | 2008-12-05 | Albury | W | ENE | NW | No | No |
범주형 변수 요약
-
날짜 변수가 있다.
Date
열로 표시된다. -
6개의 범주형 변수가 있다. 이들은
Location
,WindGustDir
,WindDir9am
,WindDir3pm
,RainToday
및RainTomorrow
이다. -
RainToday
와RainTomorrow
두 개의 이진 범주형 변수가 있다. -
RainTomorrow
는 대상 변수다.
범주형 변수 문제 탐색
먼저, 범주형 변수를 살펴보자.
범주형 변수에서 결측치 찾기
# check missing values in categorical variables
df[categorical].isnull().sum()
Date 0
Location 0
WindGustDir 10326
WindDir9am 10566
WindDir3pm 4228
RainToday 3261
RainTomorrow 3267
dtype: int64
# print categorical variables containing missing values
cat1 = [var for var in categorical if df[var].isnull().sum()!=0]
print(df[cat1].isnull().sum())
WindGustDir 10326
WindDir9am 10566
WindDir3pm 4228
RainToday 3261
RainTomorrow 3267
dtype: int64
데이터 세트에서 누락된 값이 포함 된 범주형 변수는 WindGustDir, WindDir9am, WindDir3pm 및 RainToday이며 총 4개다.
범주형 변수의 빈도수
범주형 변수의 빈도수를 확인해보자.
# view frequency of categorical variables
for var in categorical:
print(df[var].value_counts())
2013-04-01 49
2014-01-17 49
2014-12-01 49
2014-06-26 49
2013-07-19 49
..
2007-12-13 1
2007-11-04 1
2007-11-23 1
2008-01-12 1
2007-11-16 1
Name: Date, Length: 3436, dtype: int64
Canberra 3436
Sydney 3344
Adelaide 3193
Melbourne 3193
Darwin 3193
Perth 3193
Brisbane 3193
Hobart 3193
GoldCoast 3040
Albury 3040
Cairns 3040
Launceston 3040
Ballarat 3040
MountGambier 3040
Bendigo 3040
Albany 3040
Townsville 3040
MountGinini 3040
AliceSprings 3040
Wollongong 3040
Newcastle 3039
Penrith 3039
Tuggeranong 3039
PearceRAAF 3009
Watsonia 3009
WaggaWagga 3009
NorfolkIsland 3009
Moree 3009
Dartmoor 3009
Witchcliffe 3009
BadgerysCreek 3009
Portland 3009
Cobar 3009
Sale 3009
Richmond 3009
Williamtown 3009
MelbourneAirport 3009
Mildura 3009
PerthAirport 3009
Nuriootpa 3009
CoffsHarbour 3009
Woomera 3009
SydneyAirport 3009
Walpole 3006
NorahHead 3004
SalmonGums 3001
Nhil 1578
Uluru 1578
Katherine 1578
Name: Location, dtype: int64
W 9915
SE 9418
N 9313
SSE 9216
E 9181
S 9168
WSW 9069
SW 8967
SSW 8736
WNW 8252
NW 8122
ENE 8104
ESE 7372
NE 7133
NNW 6620
NNE 6548
Name: WindGustDir, dtype: int64
N 11758
SE 9287
E 9176
SSE 9112
NW 8749
S 8659
W 8459
SW 8423
NNE 8129
NNW 7980
ENE 7836
NE 7671
ESE 7630
SSW 7587
WNW 7414
WSW 7024
Name: WindDir9am, dtype: int64
SE 10838
W 10110
S 9926
WSW 9518
SSE 9399
SW 9354
N 8890
WNW 8874
NW 8610
ESE 8505
E 8472
NE 8263
SSW 8156
NNW 7870
ENE 7857
NNE 6590
Name: WindDir3pm, dtype: int64
No 110319
Yes 31880
Name: RainToday, dtype: int64
No 110316
Yes 31877
Name: RainTomorrow, dtype: int64
# view frequency distribution of categorical variables
for var in categorical:
print(df[var].value_counts()/np.float(len(df)))
2013-04-01 0.000337
2014-01-17 0.000337
2014-12-01 0.000337
2014-06-26 0.000337
2013-07-19 0.000337
...
2007-12-13 0.000007
2007-11-04 0.000007
2007-11-23 0.000007
2008-01-12 0.000007
2007-11-16 0.000007
Name: Date, Length: 3436, dtype: float64
Canberra 0.023622
Sydney 0.022989
Adelaide 0.021951
Melbourne 0.021951
Darwin 0.021951
Perth 0.021951
Brisbane 0.021951
Hobart 0.021951
GoldCoast 0.020899
Albury 0.020899
Cairns 0.020899
Launceston 0.020899
Ballarat 0.020899
MountGambier 0.020899
Bendigo 0.020899
Albany 0.020899
Townsville 0.020899
MountGinini 0.020899
AliceSprings 0.020899
Wollongong 0.020899
Newcastle 0.020892
Penrith 0.020892
Tuggeranong 0.020892
PearceRAAF 0.020686
Watsonia 0.020686
WaggaWagga 0.020686
NorfolkIsland 0.020686
Moree 0.020686
Dartmoor 0.020686
Witchcliffe 0.020686
BadgerysCreek 0.020686
Portland 0.020686
Cobar 0.020686
Sale 0.020686
Richmond 0.020686
Williamtown 0.020686
MelbourneAirport 0.020686
Mildura 0.020686
PerthAirport 0.020686
Nuriootpa 0.020686
CoffsHarbour 0.020686
Woomera 0.020686
SydneyAirport 0.020686
Walpole 0.020665
NorahHead 0.020652
SalmonGums 0.020631
Nhil 0.010848
Uluru 0.010848
Katherine 0.010848
Name: Location, dtype: float64
W 0.068163
SE 0.064746
N 0.064024
SSE 0.063358
E 0.063117
S 0.063028
WSW 0.062347
SW 0.061646
SSW 0.060058
WNW 0.056730
NW 0.055837
ENE 0.055713
ESE 0.050681
NE 0.049038
NNW 0.045511
NNE 0.045016
Name: WindGustDir, dtype: float64
N 0.080833
SE 0.063846
E 0.063083
SSE 0.062643
NW 0.060147
S 0.059528
W 0.058153
SW 0.057906
NNE 0.055885
NNW 0.054860
ENE 0.053870
NE 0.052736
ESE 0.052454
SSW 0.052159
WNW 0.050969
WSW 0.048288
Name: WindDir9am, dtype: float64
SE 0.074508
W 0.069504
S 0.068239
WSW 0.065434
SSE 0.064616
SW 0.064306
N 0.061116
WNW 0.061006
NW 0.059192
ESE 0.058470
E 0.058243
NE 0.056806
SSW 0.056070
NNW 0.054104
ENE 0.054015
NNE 0.045305
Name: WindDir3pm, dtype: float64
No 0.758415
Yes 0.219167
Name: RainToday, dtype: float64
No 0.758394
Yes 0.219146
Name: RainTomorrow, dtype: float64
레이블의 수
카테고리 변수 내 레이블의 수는 기수(cardinality
)라고한다. 변수 내 레이블의 수가 많으면 높은 기수(high cardinality
)라고한다. 높은 기수는 머신 러닝 모델에서 일부 심각한 문제를 일으킬 수 있으므로, 높은 기수를 확인해보자.
# check for cardinality in categorical variables
for var in categorical:
print(var, ' contains ', len(df[var].unique()), ' labels')
Date contains 3436 labels
Location contains 49 labels
WindGustDir contains 17 labels
WindDir9am contains 17 labels
WindDir3pm contains 17 labels
RainToday contains 3 labels
RainTomorrow contains 3 labels
다음과 같이 전처리가 필요해 보이는 Date
변수가 있다.
다른 모든 변수는 비교적 적은 수의 변수를 가지고 있다.
Data 변수의 특성 엔지니어링
df['Date'].dtypes
dtype('O')
Date
변수의 데이터 타입이 object
이므로, 이를 datetime 형식으로 파싱해보자.
# parse the dates, currently coded as strings, into datetime format
df['Date'] = pd.to_datetime(df['Date'])
# extract year from date
df['Year'] = df['Date'].dt.year
df['Year'].head()
0 2008
1 2008
2 2008
3 2008
4 2008
Name: Year, dtype: int64
# extract month from date
df['Month'] = df['Date'].dt.month
df['Month'].head()
0 12
1 12
2 12
3 12
4 12
Name: Month, dtype: int64
# extract day from date
df['Day'] = df['Date'].dt.day
df['Day'].head()
0 1
1 2
2 3
3 4
4 5
Name: Day, dtype: int64
# again view the summary of dataset
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 145460 entries, 0 to 145459
Data columns (total 26 columns):
Date 145460 non-null datetime64[ns]
Location 145460 non-null object
MinTemp 143975 non-null float64
MaxTemp 144199 non-null float64
Rainfall 142199 non-null float64
Evaporation 82670 non-null float64
Sunshine 75625 non-null float64
WindGustDir 135134 non-null object
WindGustSpeed 135197 non-null float64
WindDir9am 134894 non-null object
WindDir3pm 141232 non-null object
WindSpeed9am 143693 non-null float64
WindSpeed3pm 142398 non-null float64
Humidity9am 142806 non-null float64
Humidity3pm 140953 non-null float64
Pressure9am 130395 non-null float64
Pressure3pm 130432 non-null float64
Cloud9am 89572 non-null float64
Cloud3pm 86102 non-null float64
Temp9am 143693 non-null float64
Temp3pm 141851 non-null float64
RainToday 142199 non-null object
RainTomorrow 142193 non-null object
Year 145460 non-null int64
Month 145460 non-null int64
Day 145460 non-null int64
dtypes: datetime64[ns](1), float64(16), int64(3), object(6)
memory usage: 28.9+ MB
Data
변수에서 세 개의 추가 열이 만들어진 것을 확인할 수 있습니다. 이제 데이터 세트에서 원래의 날짜 변수를 삭제하겠습니다.
# drop the original Date variable
df.drop('Date', axis=1, inplace = True)
# preview the dataset again
df.head()
Location | MinTemp | MaxTemp | Rainfall | Evaporation | Sunshine | WindGustDir | WindGustSpeed | WindDir9am | WindDir3pm | ... | Pressure3pm | Cloud9am | Cloud3pm | Temp9am | Temp3pm | RainToday | RainTomorrow | Year | Month | Day | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Albury | 13.4 | 22.9 | 0.6 | NaN | NaN | W | 44.0 | W | WNW | ... | 1007.1 | 8.0 | NaN | 16.9 | 21.8 | No | No | 2008 | 12 | 1 |
1 | Albury | 7.4 | 25.1 | 0.0 | NaN | NaN | WNW | 44.0 | NNW | WSW | ... | 1007.8 | NaN | NaN | 17.2 | 24.3 | No | No | 2008 | 12 | 2 |
2 | Albury | 12.9 | 25.7 | 0.0 | NaN | NaN | WSW | 46.0 | W | WSW | ... | 1008.7 | NaN | 2.0 | 21.0 | 23.2 | No | No | 2008 | 12 | 3 |
3 | Albury | 9.2 | 28.0 | 0.0 | NaN | NaN | NE | 24.0 | SE | E | ... | 1012.8 | NaN | NaN | 18.1 | 26.5 | No | No | 2008 | 12 | 4 |
4 | Albury | 17.5 | 32.3 | 1.0 | NaN | NaN | W | 41.0 | ENE | NW | ... | 1006.0 | 7.0 | 8.0 | 17.8 | 29.7 | No | No | 2008 | 12 | 5 |
5 rows × 25 columns
데이터셋에서 Date
변수가 삭제된 것을 볼 수 있다.
범주형 변수 탐색
이제 하나씩 범주형 변수를 하나하나 탐색해보자.
# find categorical variables
categorical = [var for var in df.columns if df[var].dtype=='O']
print('There are {} categorical variables\n'.format(len(categorical)))
print('The categorical variables are :', categorical)
There are 6 categorical variables
The categorical variables are : ['Location', 'WindGustDir', 'WindDir9am', 'WindDir3pm', 'RainToday', 'RainTomorrow']
6개의 범주형 변수들이 있는 것을 볼 수 있다. Data
변수는 사라졌다. 제일 먼저, 범주형 변수들의 결측치를 살펴보자.
# check for missing values in categorical variables
df[categorical].isnull().sum()
Location 0
WindGustDir 9330
WindDir9am 10013
WindDir3pm 3778
RainToday 1406
RainTomorrow 0
dtype: int64
WindGustDir
, WindDir9am
, WindDir3pm
, RainToday
, 이 4개가 결측치를 포함하고 있는 것을 볼 수 있다. 모든 변수들을 하나하나 살펴보자.
Location
변수 탐색
# print number of labels in Location variable
print('Location contains', len(df.Location.unique()), 'labels')
Location contains 49 labels
# check labels in location variable
df.Location.unique()
array(['Albury', 'BadgerysCreek', 'Cobar', 'CoffsHarbour', 'Moree',
'Newcastle', 'NorahHead', 'NorfolkIsland', 'Penrith', 'Richmond',
'Sydney', 'SydneyAirport', 'WaggaWagga', 'Williamtown',
'Wollongong', 'Canberra', 'Tuggeranong', 'MountGinini', 'Ballarat',
'Bendigo', 'Sale', 'MelbourneAirport', 'Melbourne', 'Mildura',
'Nhil', 'Portland', 'Watsonia', 'Dartmoor', 'Brisbane', 'Cairns',
'GoldCoast', 'Townsville', 'Adelaide', 'MountGambier', 'Nuriootpa',
'Woomera', 'Albany', 'Witchcliffe', 'PearceRAAF', 'PerthAirport',
'Perth', 'SalmonGums', 'Walpole', 'Hobart', 'Launceston',
'AliceSprings', 'Darwin', 'Katherine', 'Uluru'], dtype=object)
# check frequency distribution of values in Location variable
df.Location.value_counts()
Canberra 3418
Sydney 3337
Perth 3193
Darwin 3192
Hobart 3188
Brisbane 3161
Adelaide 3090
Bendigo 3034
Townsville 3033
AliceSprings 3031
MountGambier 3030
Ballarat 3028
Launceston 3028
Albany 3016
Albury 3011
PerthAirport 3009
MelbourneAirport 3009
Mildura 3007
SydneyAirport 3005
Nuriootpa 3002
Sale 3000
Watsonia 2999
Tuggeranong 2998
Portland 2996
Woomera 2990
Cobar 2988
Cairns 2988
Wollongong 2983
GoldCoast 2980
WaggaWagga 2976
NorfolkIsland 2964
Penrith 2964
SalmonGums 2955
Newcastle 2955
CoffsHarbour 2953
Witchcliffe 2952
Richmond 2951
Dartmoor 2943
NorahHead 2929
BadgerysCreek 2928
MountGinini 2907
Moree 2854
Walpole 2819
PearceRAAF 2762
Williamtown 2553
Melbourne 2435
Nhil 1569
Katherine 1559
Uluru 1521
Name: Location, dtype: int64
# let's do One Hot Encoding of Location variable
# get k-1 dummy variables after One Hot Encoding
# preview the dataset with head() method
pd.get_dummies(df.Location, drop_first=True).head()
Albany | Albury | AliceSprings | BadgerysCreek | Ballarat | Bendigo | Brisbane | Cairns | Canberra | Cobar | ... | Townsville | Tuggeranong | Uluru | WaggaWagga | Walpole | Watsonia | Williamtown | Witchcliffe | Wollongong | Woomera | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
5 rows × 48 columns
WindGustDir
변수 탐색
# print number of labels in WindGustDir variable
print('WindGustDir contains', len(df['WindGustDir'].unique()), 'labels')
WindGustDir contains 17 labels
# check labels in WindGustDir variable
df['WindGustDir'].unique()
array(['W', 'WNW', 'WSW', 'NE', 'NNW', 'N', 'NNE', 'SW', 'ENE', 'SSE',
'S', 'NW', 'SE', 'ESE', nan, 'E', 'SSW'], dtype=object)
# check frequency distribution of values in WindGustDir variable
df.WindGustDir.value_counts()
W 9780
SE 9309
E 9071
N 9033
SSE 8993
S 8949
WSW 8901
SW 8797
SSW 8610
WNW 8066
NW 8003
ENE 7992
ESE 7305
NE 7060
NNW 6561
NNE 6433
Name: WindGustDir, dtype: int64
# let's do One Hot Encoding of WindGustDir variable
# get k-1 dummy variables after One Hot Encoding
# also add an additional dummy variable to indicate there was missing data
# preview the dataset with head() method
pd.get_dummies(df.WindGustDir, drop_first=True, dummy_na=True).head()
ENE | ESE | N | NE | NNE | NNW | NW | S | SE | SSE | SSW | SW | W | WNW | WSW | NaN | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
3 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
# sum the number of 1s per boolean variable over the rows of the dataset
# it will tell us how many observations we have for each category
pd.get_dummies(df.WindGustDir, drop_first=True, dummy_na=True).sum(axis=0)
ENE 7992
ESE 7305
N 9033
NE 7060
NNE 6433
NNW 6561
NW 8003
S 8949
SE 9309
SSE 8993
SSW 8610
SW 8797
W 9780
WNW 8066
WSW 8901
NaN 9330
dtype: int64
WindGustDir
변수에 9330개의 결측치가 있는 것을 볼 수 있다.
WindDir9am
변수 탐색
# print number of labels in WindDir9am variable
print('WindDir9am contains', len(df['WindDir9am'].unique()), 'labels')
WindDir9am contains 17 labels
# check labels in WindDir9am variable
df['WindDir9am'].unique()
array(['W', 'NNW', 'SE', 'ENE', 'SW', 'SSE', 'S', 'NE', nan, 'SSW', 'N',
'WSW', 'ESE', 'E', 'NW', 'WNW', 'NNE'], dtype=object)
# check frequency distribution of values in WindDir9am variable
df['WindDir9am'].value_counts()
N 11393
SE 9162
E 9024
SSE 8966
NW 8552
S 8493
W 8260
SW 8237
NNE 7948
NNW 7840
ENE 7735
ESE 7558
NE 7527
SSW 7448
WNW 7194
WSW 6843
Name: WindDir9am, dtype: int64
# let's do One Hot Encoding of WindDir9am variable
# get k-1 dummy variables after One Hot Encoding
# also add an additional dummy variable to indicate there was missing data
# preview the dataset with head() method
pd.get_dummies(df.WindDir9am, drop_first=True, dummy_na=True).head()
ENE | ESE | N | NE | NNE | NNW | NW | S | SE | SSE | SSW | SW | W | WNW | WSW | NaN | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
3 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
# sum the number of 1s per boolean variable over the rows of the dataset
# it will tell us how many observations we have for each category
pd.get_dummies(df.WindDir9am, drop_first=True, dummy_na=True).sum(axis=0)
ENE 7735
ESE 7558
N 11393
NE 7527
NNE 7948
NNW 7840
NW 8552
S 8493
SE 9162
SSE 8966
SSW 7448
SW 8237
W 8260
WNW 7194
WSW 6843
NaN 10013
dtype: int64
WindDir9am
변수에 10013개의 결측치가 있는 것을 볼 수 있다.
WinDir3pm
변수 탐색
# print number of labels in WindDir3pm variable
print('WindDir3pm contains', len(df['WindDir3pm'].unique()), 'labels')
WindDir3pm contains 17 labels
# check labels in WindDir3pm variable
df['WindDir3pm'].unique()
array(['WNW', 'WSW', 'E', 'NW', 'W', 'SSE', 'ESE', 'ENE', 'NNW', 'SSW',
'SW', 'SE', 'N', 'S', 'NNE', nan, 'NE'], dtype=object)
# check frequency distribution of values in WindDir3pm variable
df['WindDir3pm'].value_counts()
SE 10663
W 9911
S 9598
WSW 9329
SW 9182
SSE 9142
N 8667
WNW 8656
NW 8468
ESE 8382
E 8342
NE 8164
SSW 8010
NNW 7733
ENE 7724
NNE 6444
Name: WindDir3pm, dtype: int64
# let's do One Hot Encoding of WindDir3pm variable
# get k-1 dummy variables after One Hot Encoding
# also add an additional dummy variable to indicate there was missing data
# preview the dataset with head() method
pd.get_dummies(df.WindDir3pm, drop_first=True, dummy_na=True).head()
ENE | ESE | N | NE | NNE | NNW | NW | S | SE | SSE | SSW | SW | W | WNW | WSW | NaN | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
3 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
# sum the number of 1s per boolean variable over the rows of the dataset
# it will tell us how many observations we have for each category
pd.get_dummies(df.WindDir3pm, drop_first=True, dummy_na=True).sum(axis=0)
ENE 7724
ESE 8382
N 8667
NE 8164
NNE 6444
NNW 7733
NW 8468
S 9598
SE 10663
SSE 9142
SSW 8010
SW 9182
W 9911
WNW 8656
WSW 9329
NaN 3778
dtype: int64
WindDir3pm
변수에는 3778개의 결측치가 있는 것을 볼 수 있다.
RainToday
변수 탐색
# print number of labels in RainToday variable
print('RainToday contains', len(df['RainToday'].unique()), 'labels')
RainToday contains 3 labels
# check labels in WindGustDir variable
df['RainToday'].unique()
array(['No', 'Yes', nan], dtype=object)
# check frequency distribution of values in WindGustDir variable
df.RainToday.value_counts()
No 109332
Yes 31455
Name: RainToday, dtype: int64
# let's do One Hot Encoding of RainToday variable
# get k-1 dummy variables after One Hot Encoding
# also add an additional dummy variable to indicate there was missing data
# preview the dataset with head() method
pd.get_dummies(df.RainToday, drop_first=True, dummy_na=True).head()
Yes | NaN | |
---|---|---|
0 | 0 | 0 |
1 | 0 | 0 |
2 | 0 | 0 |
3 | 0 | 0 |
4 | 0 | 0 |
# sum the number of 1s per boolean variable over the rows of the dataset
# it will tell us how many observations we have for each category
pd.get_dummies(df.RainToday, drop_first=True, dummy_na=True).sum(axis=0)
Yes 31455
NaN 1406
dtype: int64
RainToday
변수에는 1406개의 결측치가 있는 것을 볼 수 있다.
수치형 변수 탐색
# find numerical variables
numerical = [var for var in df.columns if df[var].dtype!='O']
print('There are {} numerical variables\n'.format(len(numerical)))
print('The numerical variables are :', numerical)
There are 19 numerical variables
The numerical variables are : ['MinTemp', 'MaxTemp', 'Rainfall', 'Evaporation', 'Sunshine', 'WindGustSpeed', 'WindSpeed9am', 'WindSpeed3pm', 'Humidity9am', 'Humidity3pm', 'Pressure9am', 'Pressure3pm', 'Cloud9am', 'Cloud3pm', 'Temp9am', 'Temp3pm', 'Year', 'Month', 'Day']
# view the numerical variables
df[numerical].head()
MinTemp | MaxTemp | Rainfall | Evaporation | Sunshine | WindGustSpeed | WindSpeed9am | WindSpeed3pm | Humidity9am | Humidity3pm | Pressure9am | Pressure3pm | Cloud9am | Cloud3pm | Temp9am | Temp3pm | Year | Month | Day | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 13.4 | 22.9 | 0.6 | NaN | NaN | 44.0 | 20.0 | 24.0 | 71.0 | 22.0 | 1007.7 | 1007.1 | 8.0 | NaN | 16.9 | 21.8 | 2008 | 12 | 1 |
1 | 7.4 | 25.1 | 0.0 | NaN | NaN | 44.0 | 4.0 | 22.0 | 44.0 | 25.0 | 1010.6 | 1007.8 | NaN | NaN | 17.2 | 24.3 | 2008 | 12 | 2 |
2 | 12.9 | 25.7 | 0.0 | NaN | NaN | 46.0 | 19.0 | 26.0 | 38.0 | 30.0 | 1007.6 | 1008.7 | NaN | 2.0 | 21.0 | 23.2 | 2008 | 12 | 3 |
3 | 9.2 | 28.0 | 0.0 | NaN | NaN | 24.0 | 11.0 | 9.0 | 45.0 | 16.0 | 1017.6 | 1012.8 | NaN | NaN | 18.1 | 26.5 | 2008 | 12 | 4 |
4 | 17.5 | 32.3 | 1.0 | NaN | NaN | 41.0 | 7.0 | 20.0 | 82.0 | 33.0 | 1010.8 | 1006.0 | 7.0 | 8.0 | 17.8 | 29.7 | 2008 | 12 | 5 |
수치형 변수들 요약
-
16개의 수치형 변수들이 있다.
-
이들은
MinTemp
,MaxTemp
,Rainfall
,Evaporation
,Sunshine
,WindGustSpeed
,WindSpeed9am
,WindSpeed3pm
,Humidity9am
,Humidity3pm
,Pressure9am
,Pressure3pm
,Cloud9am
,Cloud3pm
,Temp9am
,Temp3pm
이다. -
모든 수치형 변수들은 연속형 변수이다.
수치형 변수의 문제 탐색
수치형 변수들을 탐색할 것이다.
수치형 특성의 결측치
# check missing values in numerical variables
df[numerical].isnull().sum()
MinTemp 637
MaxTemp 322
Rainfall 1406
Evaporation 60843
Sunshine 67816
WindGustSpeed 9270
WindSpeed9am 1348
WindSpeed3pm 2630
Humidity9am 1774
Humidity3pm 3610
Pressure9am 14014
Pressure3pm 13981
Cloud9am 53657
Cloud3pm 57094
Temp9am 904
Temp3pm 2726
Year 0
Month 0
Day 0
dtype: int64
결측치를 포함한 16개의 수치형 변수들을 볼 수 있다.
수치형 변수의 이상치
# view summary statistics in numerical variables
print(round(df[numerical].describe()),2)
MinTemp MaxTemp Rainfall Evaporation Sunshine WindGustSpeed \
count 141556.0 141871.0 140787.0 81350.0 74377.0 132923.0
mean 12.0 23.0 2.0 5.0 8.0 40.0
std 6.0 7.0 8.0 4.0 4.0 14.0
min -8.0 -5.0 0.0 0.0 0.0 6.0
25% 8.0 18.0 0.0 3.0 5.0 31.0
50% 12.0 23.0 0.0 5.0 8.0 39.0
75% 17.0 28.0 1.0 7.0 11.0 48.0
max 34.0 48.0 371.0 145.0 14.0 135.0
WindSpeed9am WindSpeed3pm Humidity9am Humidity3pm Pressure9am \
count 140845.0 139563.0 140419.0 138583.0 128179.0
mean 14.0 19.0 69.0 51.0 1018.0
std 9.0 9.0 19.0 21.0 7.0
min 0.0 0.0 0.0 0.0 980.0
25% 7.0 13.0 57.0 37.0 1013.0
50% 13.0 19.0 70.0 52.0 1018.0
75% 19.0 24.0 83.0 66.0 1022.0
max 130.0 87.0 100.0 100.0 1041.0
Pressure3pm Cloud9am Cloud3pm Temp9am Temp3pm Year \
count 128212.0 88536.0 85099.0 141289.0 139467.0 142193.0
mean 1015.0 4.0 5.0 17.0 22.0 2013.0
std 7.0 3.0 3.0 6.0 7.0 3.0
min 977.0 0.0 0.0 -7.0 -5.0 2007.0
25% 1010.0 1.0 2.0 12.0 17.0 2011.0
50% 1015.0 5.0 5.0 17.0 21.0 2013.0
75% 1020.0 7.0 7.0 22.0 26.0 2015.0
max 1040.0 9.0 9.0 40.0 47.0 2017.0
Month Day
count 142193.0 142193.0
mean 6.0 16.0
std 3.0 9.0
min 1.0 1.0
25% 3.0 8.0
50% 6.0 16.0
75% 9.0 23.0
max 12.0 31.0 2
Rainfall
, Evaporation
, WindSpeed9am
, WindSpeed3pm
이 이상치를 가지고 있는 것으로 보인다.
위 변수들의 이상치를 시각화 해보자.
# draw boxplots to visualize outliers
plt.figure(figsize=(15,10))
plt.subplot(2, 2, 1)
fig = df.boxplot(column='Rainfall')
fig.set_title('')
fig.set_ylabel('Rainfall')
plt.subplot(2, 2, 2)
fig = df.boxplot(column='Evaporation')
fig.set_title('')
fig.set_ylabel('Evaporation')
plt.subplot(2, 2, 3)
fig = df.boxplot(column='WindSpeed9am')
fig.set_title('')
fig.set_ylabel('WindSpeed9am')
plt.subplot(2, 2, 4)
fig = df.boxplot(column='WindSpeed3pm')
fig.set_title('')
fig.set_ylabel('WindSpeed3pm')
Text(0, 0.5, 'WindSpeed3pm')
이 변수들 안에 많은 이상치가 있는 것을 확인할 수 있다.
변수의 분산 확인
이제 히스토그램을 그려 분포를 확인하여 변수들이 정규분포를 따르는지, 혹은 치우친 분포를 보이는지를 확인해보자. 만약 변수가 정규분포를 따른다면, 극단치 분석(Extreme Value Analysis
)을 수행할 것이고, 변수가 치우친 분포를 보인다면 IQR (Interquantile range)을 수행할 것이다.
# plot histogram to check distribution
plt.figure(figsize=(15,10))
plt.subplot(2, 2, 1)
fig = df.Rainfall.hist(bins=10)
fig.set_xlabel('Rainfall')
fig.set_ylabel('RainTomorrow')
plt.subplot(2, 2, 2)
fig = df.Evaporation.hist(bins=10)
fig.set_xlabel('Evaporation')
fig.set_ylabel('RainTomorrow')
plt.subplot(2, 2, 3)
fig = df.WindSpeed9am.hist(bins=10)
fig.set_xlabel('WindSpeed9am')
fig.set_ylabel('RainTomorrow')
plt.subplot(2, 2, 4)
fig = df.WindSpeed3pm.hist(bins=10)
fig.set_xlabel('WindSpeed3pm')
fig.set_ylabel('RainTomorrow')
Text(0, 0.5, 'RainTomorrow')
4개 모두 치우친 것을 볼 수 있다. 따라서, IQR을 수행할 것이다.
# find outliers for Rainfall variable
IQR = df.Rainfall.quantile(0.75) - df.Rainfall.quantile(0.25)
Lower_fence = df.Rainfall.quantile(0.25) - (IQR * 3)
Upper_fence = df.Rainfall.quantile(0.75) + (IQR * 3)
print('Rainfall outliers are values < {lowerboundary} or > {upperboundary}'.format(lowerboundary=Lower_fence, upperboundary=Upper_fence))
Rainfall outliers are values < -2.4000000000000004 or > 3.2
Rainfall
에 대해, 최소값과 최대값이 0.0과 371.0이다. 따라서 이상치는 3.2보다 큰 값들이다.
# find outliers for Evaporation variable
IQR = df.Evaporation.quantile(0.75) - df.Evaporation.quantile(0.25)
Lower_fence = df.Evaporation.quantile(0.25) - (IQR * 3)
Upper_fence = df.Evaporation.quantile(0.75) + (IQR * 3)
print('Evaporation outliers are values < {lowerboundary} or > {upperboundary}'.format(lowerboundary=Lower_fence, upperboundary=Upper_fence))
Evaporation outliers are values < -11.800000000000002 or > 21.800000000000004
Evaporation
에 대해, 최소값과 최대값이 0.0과 145.0이다. 따라서 이상치는 21.8보다 큰 값들이다.
# find outliers for WindSpeed9am variable
IQR = df.WindSpeed9am.quantile(0.75) - df.WindSpeed9am.quantile(0.25)
Lower_fence = df.WindSpeed9am.quantile(0.25) - (IQR * 3)
Upper_fence = df.WindSpeed9am.quantile(0.75) + (IQR * 3)
print('WindSpeed9am outliers are values < {lowerboundary} or > {upperboundary}'.format(lowerboundary=Lower_fence, upperboundary=Upper_fence))
WindSpeed9am outliers are values < -29.0 or > 55.0
WindSpeed9am
에 대해, 최소값과 최대값이 0.0과 130.0이다. 따라서 이상치는 55.0보다 큰 값들이다.
# find outliers for WindSpeed3pm variable
IQR = df.WindSpeed3pm.quantile(0.75) - df.WindSpeed3pm.quantile(0.25)
Lower_fence = df.WindSpeed3pm.quantile(0.25) - (IQR * 3)
Upper_fence = df.WindSpeed3pm.quantile(0.75) + (IQR * 3)
print('WindSpeed3pm outliers are values < {lowerboundary} or > {upperboundary}'.format(lowerboundary=Lower_fence, upperboundary=Upper_fence))
WindSpeed3pm outliers are values < -20.0 or > 57.0
WindSpeed3pm
에 대해, 최소값과 최대값이 0.0과 87.0이다. 따라서 이상치는 570보다 큰 값들이다.
8. 특성 벡터와 대상 변수 선언
X = df.drop(['RainTomorrow'], axis=1)
y = df['RainTomorrow']
9. 데이터를 학습 및 테스트 세트로 분리
# split X and y into training and testing sets
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)
# check the shape of X_train and X_test
X_train.shape, X_test.shape
((113754, 24), (28439, 24))
10. 특성 공학
특성 공학은 원시 데이터를 유용한 기능으로 변환하여 모델을 이해하고 예측 능력을 향상시키는 프로세스이다. 다른 유형의 변수에 대해 특성 공학을 수행해보자.
먼저, 범주형 변수와 수치형 변수를 다시 나눠서 보여줄 것이다.
# check data types in X_train
X_train.dtypes
Location object
MinTemp float64
MaxTemp float64
Rainfall float64
Evaporation float64
Sunshine float64
WindGustDir object
WindGustSpeed float64
WindDir9am object
WindDir3pm object
WindSpeed9am float64
WindSpeed3pm float64
Humidity9am float64
Humidity3pm float64
Pressure9am float64
Pressure3pm float64
Cloud9am float64
Cloud3pm float64
Temp9am float64
Temp3pm float64
RainToday object
Year int64
Month int64
Day int64
dtype: object
# display categorical variables
categorical = [col for col in X_train.columns if X_train[col].dtypes == 'O']
categorical
['Location', 'WindGustDir', 'WindDir9am', 'WindDir3pm', 'RainToday']
# display numerical variables
numerical = [col for col in X_train.columns if X_train[col].dtypes != 'O']
numerical
['MinTemp',
'MaxTemp',
'Rainfall',
'Evaporation',
'Sunshine',
'WindGustSpeed',
'WindSpeed9am',
'WindSpeed3pm',
'Humidity9am',
'Humidity3pm',
'Pressure9am',
'Pressure3pm',
'Cloud9am',
'Cloud3pm',
'Temp9am',
'Temp3pm',
'Year',
'Month',
'Day']
수치형 변수들의 결측치 공학
# check missing values in numerical variables in X_train
X_train[numerical].isnull().sum()
MinTemp 495
MaxTemp 264
Rainfall 1139
Evaporation 48718
Sunshine 54314
WindGustSpeed 7367
WindSpeed9am 1086
WindSpeed3pm 2094
Humidity9am 1449
Humidity3pm 2890
Pressure9am 11212
Pressure3pm 11186
Cloud9am 43137
Cloud3pm 45768
Temp9am 740
Temp3pm 2171
Year 0
Month 0
Day 0
dtype: int64
# check missing values in numerical variables in X_test
X_test[numerical].isnull().sum()
MinTemp 142
MaxTemp 58
Rainfall 267
Evaporation 12125
Sunshine 13502
WindGustSpeed 1903
WindSpeed9am 262
WindSpeed3pm 536
Humidity9am 325
Humidity3pm 720
Pressure9am 2802
Pressure3pm 2795
Cloud9am 10520
Cloud3pm 11326
Temp9am 164
Temp3pm 555
Year 0
Month 0
Day 0
dtype: int64
# print percentage of missing values in the numerical variables in training set
for col in numerical:
if X_train[col].isnull().mean()>0:
print(col, round(X_train[col].isnull().mean(),4))
MinTemp 0.0044
MaxTemp 0.0023
Rainfall 0.01
Evaporation 0.4283
Sunshine 0.4775
WindGustSpeed 0.0648
WindSpeed9am 0.0095
WindSpeed3pm 0.0184
Humidity9am 0.0127
Humidity3pm 0.0254
Pressure9am 0.0986
Pressure3pm 0.0983
Cloud9am 0.3792
Cloud3pm 0.4023
Temp9am 0.0065
Temp3pm 0.0191
추정
데이터가 완전히 무작위로 누락되었다고 가정하고 (MCAR), 결측값을 보완하는 데 두 가지 방법을 사용할 수 있다. 하나는 평균 또는 중앙값 보완이고, 다른 하나는 무작위 샘플 보완이다. 데이터 세트에 이상치가 있을 때 중앙값 보완을 사용해야 한다. 따라서, 이번 분석에서는 중앙값 보완을 사용해보자.
결측값 보완은 적절한 통계적 측정값(중앙값)으로 이루어진다. 보완은 학습 데이터 세트에서 이루어져야 하며, 그 후에 테스트 데이터 세트로 전파되어야 한다. 즉, 학습 및 테스트 데이터 세트에서 결측값을 채우기 위해 사용되는 통계 측정값은 학습 세트에서만 추출되어야 한다.
# impute missing values in X_train and X_test with respective column median in X_train
for df1 in [X_train, X_test]:
for col in numerical:
col_median=X_train[col].median()
df1[col].fillna(col_median, inplace=True)
# check again missing values in numerical variables in X_train
X_train[numerical].isnull().sum()
MinTemp 0
MaxTemp 0
Rainfall 0
Evaporation 0
Sunshine 0
WindGustSpeed 0
WindSpeed9am 0
WindSpeed3pm 0
Humidity9am 0
Humidity3pm 0
Pressure9am 0
Pressure3pm 0
Cloud9am 0
Cloud3pm 0
Temp9am 0
Temp3pm 0
Year 0
Month 0
Day 0
dtype: int64
# check missing values in numerical variables in X_test
X_test[numerical].isnull().sum()
MinTemp 0
MaxTemp 0
Rainfall 0
Evaporation 0
Sunshine 0
WindGustSpeed 0
WindSpeed9am 0
WindSpeed3pm 0
Humidity9am 0
Humidity3pm 0
Pressure9am 0
Pressure3pm 0
Cloud9am 0
Cloud3pm 0
Temp9am 0
Temp3pm 0
Year 0
Month 0
Day 0
dtype: int64
모든 결측치가 사라진 것을 볼 수 있다.
범주형 변수의 결측치 공학
# print percentage of missing values in the categorical variables in training set
X_train[categorical].isnull().mean()
Location 0.000000
WindGustDir 0.065114
WindDir9am 0.070134
WindDir3pm 0.026443
RainToday 0.010013
dtype: float64
# print categorical variables with missing data
for col in categorical:
if X_train[col].isnull().mean()>0:
print(col, (X_train[col].isnull().mean()))
WindGustDir 0.06511419378659213
WindDir9am 0.07013379749283542
WindDir3pm 0.026443026179299188
RainToday 0.01001283471350458
# impute missing categorical variables with most frequent value
for df2 in [X_train, X_test]:
df2['WindGustDir'].fillna(X_train['WindGustDir'].mode()[0], inplace=True)
df2['WindDir9am'].fillna(X_train['WindDir9am'].mode()[0], inplace=True)
df2['WindDir3pm'].fillna(X_train['WindDir3pm'].mode()[0], inplace=True)
df2['RainToday'].fillna(X_train['RainToday'].mode()[0], inplace=True)
# check missing values in categorical variables in X_train
X_train[categorical].isnull().sum()
Location 0
WindGustDir 0
WindDir9am 0
WindDir3pm 0
RainToday 0
dtype: int64
# check missing values in categorical variables in X_test
X_test[categorical].isnull().sum()
Location 0
WindGustDir 0
WindDir9am 0
WindDir3pm 0
RainToday 0
dtype: int64
마지막으로, X_train
과 X_test
의 결측치를 확인해보자.
# check missing values in X_train
X_train.isnull().sum()
Location 0
MinTemp 0
MaxTemp 0
Rainfall 0
Evaporation 0
Sunshine 0
WindGustDir 0
WindGustSpeed 0
WindDir9am 0
WindDir3pm 0
WindSpeed9am 0
WindSpeed3pm 0
Humidity9am 0
Humidity3pm 0
Pressure9am 0
Pressure3pm 0
Cloud9am 0
Cloud3pm 0
Temp9am 0
Temp3pm 0
RainToday 0
Year 0
Month 0
Day 0
dtype: int64
# check missing values in X_test
X_test.isnull().sum()
Location 0
MinTemp 0
MaxTemp 0
Rainfall 0
Evaporation 0
Sunshine 0
WindGustDir 0
WindGustSpeed 0
WindDir9am 0
WindDir3pm 0
WindSpeed9am 0
WindSpeed3pm 0
Humidity9am 0
Humidity3pm 0
Pressure9am 0
Pressure3pm 0
Cloud9am 0
Cloud3pm 0
Temp9am 0
Temp3pm 0
RainToday 0
Year 0
Month 0
Day 0
dtype: int64
결측치가 없는 것을 확인할 수 있다.
수치형 변수의 이상치 공학
Rainfall
, Evaporation
, WindSpeed9am
, WindSpeed3pm
에서 이상치가 있는 것을 봤었다. 위의 변수에서 최대값을 상한선으로 설정하고 이상치를 제거하기 위해 top-coding 접근 방식을 사용해보자.
def max_value(df3, variable, top):
return np.where(df3[variable]>top, top, df3[variable])
for df3 in [X_train, X_test]:
df3['Rainfall'] = max_value(df3, 'Rainfall', 3.2)
df3['Evaporation'] = max_value(df3, 'Evaporation', 21.8)
df3['WindSpeed9am'] = max_value(df3, 'WindSpeed9am', 55)
df3['WindSpeed3pm'] = max_value(df3, 'WindSpeed3pm', 57)
X_train.Rainfall.max(), X_test.Rainfall.max()
(3.2, 3.2)
X_train.Evaporation.max(), X_test.Evaporation.max()
(21.8, 21.8)
X_train.WindSpeed9am.max(), X_test.WindSpeed9am.max()
(55.0, 55.0)
X_train.WindSpeed3pm.max(), X_test.WindSpeed3pm.max()
(57.0, 57.0)
X_train[numerical].describe()
MinTemp | MaxTemp | Rainfall | Evaporation | Sunshine | WindGustSpeed | WindSpeed9am | WindSpeed3pm | Humidity9am | Humidity3pm | Pressure9am | Pressure3pm | Cloud9am | Cloud3pm | Temp9am | Temp3pm | Year | Month | Day | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 |
mean | 12.193497 | 23.237216 | 0.675080 | 5.151606 | 8.041154 | 39.884074 | 13.978155 | 18.614756 | 68.867486 | 51.509547 | 1017.640649 | 1015.241101 | 4.651801 | 4.703588 | 16.995062 | 21.688643 | 2012.759727 | 6.404021 | 15.710419 |
std | 6.388279 | 7.094149 | 1.183837 | 2.823707 | 2.769480 | 13.116959 | 8.806558 | 8.685862 | 18.935587 | 20.530723 | 6.738680 | 6.675168 | 2.292726 | 2.117847 | 6.463772 | 6.855649 | 2.540419 | 3.427798 | 8.796821 |
min | -8.200000 | -4.800000 | 0.000000 | 0.000000 | 0.000000 | 6.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 980.500000 | 977.100000 | 0.000000 | 0.000000 | -7.200000 | -5.400000 | 2007.000000 | 1.000000 | 1.000000 |
25% | 7.600000 | 18.000000 | 0.000000 | 4.000000 | 8.200000 | 31.000000 | 7.000000 | 13.000000 | 57.000000 | 37.000000 | 1013.500000 | 1011.000000 | 3.000000 | 4.000000 | 12.300000 | 16.700000 | 2011.000000 | 3.000000 | 8.000000 |
50% | 12.000000 | 22.600000 | 0.000000 | 4.800000 | 8.500000 | 39.000000 | 13.000000 | 19.000000 | 70.000000 | 52.000000 | 1017.600000 | 1015.200000 | 5.000000 | 5.000000 | 16.700000 | 21.100000 | 2013.000000 | 6.000000 | 16.000000 |
75% | 16.800000 | 28.200000 | 0.600000 | 5.400000 | 8.700000 | 46.000000 | 19.000000 | 24.000000 | 83.000000 | 65.000000 | 1021.800000 | 1019.400000 | 6.000000 | 6.000000 | 21.500000 | 26.300000 | 2015.000000 | 9.000000 | 23.000000 |
max | 33.900000 | 48.100000 | 3.200000 | 21.800000 | 14.500000 | 135.000000 | 55.000000 | 57.000000 | 100.000000 | 100.000000 | 1041.000000 | 1039.600000 | 9.000000 | 8.000000 | 40.200000 | 46.700000 | 2017.000000 | 12.000000 | 31.000000 |
Rainfall
, Evaporation
, WindSpeed9am
WindSpeed3pm
에서 이상치가 사라진 것을 볼 수 있다.
범주형 변수 인코딩
categorical
['Location', 'WindGustDir', 'WindDir9am', 'WindDir3pm', 'RainToday']
X_train[categorical].head()
Location | WindGustDir | WindDir9am | WindDir3pm | RainToday | |
---|---|---|---|---|---|
110803 | Witchcliffe | S | SSE | S | No |
87289 | Cairns | ENE | SSE | SE | Yes |
134949 | AliceSprings | E | NE | N | No |
85553 | Cairns | ESE | SSE | E | No |
16110 | Newcastle | W | N | SE | No |
# encode RainToday variable
import category_encoders as ce
encoder = ce.BinaryEncoder(cols=['RainToday'])
X_train = encoder.fit_transform(X_train)
X_test = encoder.transform(X_test)
X_train.head()
Location | MinTemp | MaxTemp | Rainfall | Evaporation | Sunshine | WindGustDir | WindGustSpeed | WindDir9am | WindDir3pm | ... | Pressure3pm | Cloud9am | Cloud3pm | Temp9am | Temp3pm | RainToday_0 | RainToday_1 | Year | Month | Day | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
110803 | Witchcliffe | 13.9 | 22.6 | 0.2 | 4.8 | 8.5 | S | 41.0 | SSE | S | ... | 1013.4 | 5.0 | 5.0 | 18.8 | 20.4 | 0 | 1 | 2014 | 4 | 25 |
87289 | Cairns | 22.4 | 29.4 | 2.0 | 6.0 | 6.3 | ENE | 33.0 | SSE | SE | ... | 1013.1 | 7.0 | 5.0 | 26.4 | 27.5 | 1 | 0 | 2015 | 11 | 2 |
134949 | AliceSprings | 9.7 | 36.2 | 0.0 | 11.4 | 12.3 | E | 31.0 | NE | N | ... | 1013.6 | 1.0 | 1.0 | 28.5 | 35.0 | 0 | 1 | 2014 | 10 | 19 |
85553 | Cairns | 20.5 | 30.1 | 0.0 | 8.8 | 11.1 | ESE | 37.0 | SSE | E | ... | 1010.8 | 2.0 | 3.0 | 27.3 | 29.4 | 0 | 1 | 2010 | 10 | 30 |
16110 | Newcastle | 16.8 | 29.2 | 0.0 | 4.8 | 8.5 | W | 39.0 | N | SE | ... | 1015.2 | 5.0 | 8.0 | 22.2 | 27.0 | 0 | 1 | 2012 | 11 | 8 |
5 rows × 25 columns
두 개의 추가적인 변수인 RainToday_0
, RainToday_1
가 RainToday
변수로부터 생긴 것을 볼 수 있다.
X_train
훈련세트를 생성해보자.
X_train = pd.concat([X_train[numerical], X_train[['RainToday_0', 'RainToday_1']],
pd.get_dummies(X_train.Location),
pd.get_dummies(X_train.WindGustDir),
pd.get_dummies(X_train.WindDir9am),
pd.get_dummies(X_train.WindDir3pm)], axis=1)
X_train.head()
MinTemp | MaxTemp | Rainfall | Evaporation | Sunshine | WindGustSpeed | WindSpeed9am | WindSpeed3pm | Humidity9am | Humidity3pm | ... | NNW | NW | S | SE | SSE | SSW | SW | W | WNW | WSW | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
110803 | 13.9 | 22.6 | 0.2 | 4.8 | 8.5 | 41.0 | 20.0 | 28.0 | 65.0 | 55.0 | ... | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
87289 | 22.4 | 29.4 | 2.0 | 6.0 | 6.3 | 33.0 | 7.0 | 19.0 | 71.0 | 59.0 | ... | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
134949 | 9.7 | 36.2 | 0.0 | 11.4 | 12.3 | 31.0 | 15.0 | 11.0 | 6.0 | 2.0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
85553 | 20.5 | 30.1 | 0.0 | 8.8 | 11.1 | 37.0 | 22.0 | 19.0 | 59.0 | 53.0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
16110 | 16.8 | 29.2 | 0.0 | 4.8 | 8.5 | 39.0 | 0.0 | 7.0 | 72.0 | 53.0 | ... | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
5 rows × 118 columns
유사하게, X_test
훈련세트도 만들어보자.
X_test = pd.concat([X_test[numerical], X_test[['RainToday_0', 'RainToday_1']],
pd.get_dummies(X_test.Location),
pd.get_dummies(X_test.WindGustDir),
pd.get_dummies(X_test.WindDir9am),
pd.get_dummies(X_test.WindDir3pm)], axis=1)
X_test.head()
MinTemp | MaxTemp | Rainfall | Evaporation | Sunshine | WindGustSpeed | WindSpeed9am | WindSpeed3pm | Humidity9am | Humidity3pm | ... | NNW | NW | S | SE | SSE | SSW | SW | W | WNW | WSW | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
86232 | 17.4 | 29.0 | 0.0 | 3.6 | 11.1 | 33.0 | 11.0 | 19.0 | 63.0 | 61.0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
57576 | 6.8 | 14.4 | 0.8 | 0.8 | 8.5 | 46.0 | 17.0 | 22.0 | 80.0 | 55.0 | ... | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
124071 | 10.1 | 15.4 | 3.2 | 4.8 | 8.5 | 31.0 | 13.0 | 9.0 | 70.0 | 61.0 | ... | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
117955 | 14.4 | 33.4 | 0.0 | 8.0 | 11.6 | 41.0 | 9.0 | 17.0 | 40.0 | 23.0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
133468 | 6.8 | 14.3 | 3.2 | 0.2 | 7.3 | 28.0 | 15.0 | 13.0 | 92.0 | 47.0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
5 rows × 118 columns
이제 모델 구축을 위한 학습 및 테스트 세트가 준비되었다. 그러나 그 전에, 모든 특성 변수를 동일한 척도로 매핑해야 한다. 이를 특성 스케일링
feature scaling이라고 합니다. 다음과 같이 수행해보자.
11. 특성 스케일링
X_train.describe()
MinTemp | MaxTemp | Rainfall | Evaporation | Sunshine | WindGustSpeed | WindSpeed9am | WindSpeed3pm | Humidity9am | Humidity3pm | ... | NNW | NW | S | SE | SSE | SSW | SW | W | WNW | WSW | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | ... | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 |
mean | 12.193497 | 23.237216 | 0.675080 | 5.151606 | 8.041154 | 39.884074 | 13.978155 | 18.614756 | 68.867486 | 51.509547 | ... | 0.054530 | 0.060288 | 0.067259 | 0.101605 | 0.064059 | 0.056402 | 0.064464 | 0.069334 | 0.060798 | 0.065483 |
std | 6.388279 | 7.094149 | 1.183837 | 2.823707 | 2.769480 | 13.116959 | 8.806558 | 8.685862 | 18.935587 | 20.530723 | ... | 0.227061 | 0.238021 | 0.250471 | 0.302130 | 0.244860 | 0.230698 | 0.245578 | 0.254022 | 0.238960 | 0.247378 |
min | -8.200000 | -4.800000 | 0.000000 | 0.000000 | 0.000000 | 6.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
25% | 7.600000 | 18.000000 | 0.000000 | 4.000000 | 8.200000 | 31.000000 | 7.000000 | 13.000000 | 57.000000 | 37.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
50% | 12.000000 | 22.600000 | 0.000000 | 4.800000 | 8.500000 | 39.000000 | 13.000000 | 19.000000 | 70.000000 | 52.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
75% | 16.800000 | 28.200000 | 0.600000 | 5.400000 | 8.700000 | 46.000000 | 19.000000 | 24.000000 | 83.000000 | 65.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
max | 33.900000 | 48.100000 | 3.200000 | 21.800000 | 14.500000 | 135.000000 | 55.000000 | 57.000000 | 100.000000 | 100.000000 | ... | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 |
8 rows × 118 columns
cols = X_train.columns
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
X_train = pd.DataFrame(X_train, columns=[cols])
X_test = pd.DataFrame(X_test, columns=[cols])
X_train.describe()
MinTemp | MaxTemp | Rainfall | Evaporation | Sunshine | WindGustSpeed | WindSpeed9am | WindSpeed3pm | Humidity9am | Humidity3pm | ... | NNW | NW | S | SE | SSE | SSW | SW | W | WNW | WSW | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | ... | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 | 113754.000000 |
mean | 0.484406 | 0.530004 | 0.210962 | 0.236312 | 0.554562 | 0.262667 | 0.254148 | 0.326575 | 0.688675 | 0.515095 | ... | 0.054530 | 0.060288 | 0.067259 | 0.101605 | 0.064059 | 0.056402 | 0.064464 | 0.069334 | 0.060798 | 0.065483 |
std | 0.151741 | 0.134105 | 0.369949 | 0.129528 | 0.190999 | 0.101682 | 0.160119 | 0.152384 | 0.189356 | 0.205307 | ... | 0.227061 | 0.238021 | 0.250471 | 0.302130 | 0.244860 | 0.230698 | 0.245578 | 0.254022 | 0.238960 | 0.247378 |
min | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
25% | 0.375297 | 0.431002 | 0.000000 | 0.183486 | 0.565517 | 0.193798 | 0.127273 | 0.228070 | 0.570000 | 0.370000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
50% | 0.479810 | 0.517958 | 0.000000 | 0.220183 | 0.586207 | 0.255814 | 0.236364 | 0.333333 | 0.700000 | 0.520000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
75% | 0.593824 | 0.623819 | 0.187500 | 0.247706 | 0.600000 | 0.310078 | 0.345455 | 0.421053 | 0.830000 | 0.650000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
max | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | ... | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 |
8 rows × 118 columns
이제 로지스틱 회귀 분류기에 들어갈 X_train
데이터세트가 준비되었다.
12. 모델 학습
# train a logistic regression model on the training set
from sklearn.linear_model import LogisticRegression
# instantiate the model
logreg = LogisticRegression(solver='liblinear', random_state=0)
# fit the model
logreg.fit(X_train, y_train)
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
intercept_scaling=1, l1_ratio=None, max_iter=100,
multi_class='warn', n_jobs=None, penalty='l2',
random_state=0, solver='liblinear', tol=0.0001, verbose=0,
warm_start=False)
13. 결과 예측
y_pred_test = logreg.predict(X_test)
y_pred_test
array(['No', 'No', 'No', ..., 'No', 'No', 'Yes'], dtype=object)
predict_proba 메서드
predict_proba 메서드는 대상 변수(0과 1)에 대한 확률을 배열 형태로 제공합니다.
0은 비가 오지 않을 확률
을 나타내며, 1은 비가 올 확률
을 나타낸다.
# probability of getting output as 0 - no rain
logreg.predict_proba(X_test)[:,0]
array([0.91382428, 0.83565645, 0.82033915, ..., 0.97674285, 0.79855098,
0.30734161])
# probability of getting output as 1 - rain
logreg.predict_proba(X_test)[:,1]
array([0.08617572, 0.16434355, 0.17966085, ..., 0.02325715, 0.20144902,
0.69265839])
14. 정확도 점수 확인
from sklearn.metrics import accuracy_score
print('Model accuracy score: {0:0.4f}'. format(accuracy_score(y_test, y_pred_test)))
Model accuracy score: 0.8502
예측된 클래스 레이블을 나타내는 y_pred_test
와 테스트 세트의 실제 클래스 레이블을 나타내는 y_test
이다.
훈련세트와 테스트세트 정확도 비교
이제 훈련세트와 테스트세트의 과대적합 여부를 확인하기 위해 정확도를 비교해보자.
y_pred_train = logreg.predict(X_train)
y_pred_train
array(['No', 'No', 'No', ..., 'No', 'No', 'No'], dtype=object)
print('Training-set accuracy score: {0:0.4f}'. format(accuracy_score(y_train, y_pred_train)))
Training-set accuracy score: 0.8476
과대적합과 과소적합 확인
# print the scores on training and test set
print('Training set score: {:.4f}'.format(logreg.score(X_train, y_train)))
print('Test set score: {:.4f}'.format(logreg.score(X_test, y_test)))
Training set score: 0.8476
Test set score: 0.8502
훈련 세트의 정확도 점수는 0.8476이고, 테스트 세트의 정확도는 0.8501이다. 이 두 값은 꽤 비교가 가능하다. 따라서 과대적합은 아니다.
로지스틱 회귀에서는 C=1의 기본값을 사용한다. 이는 훈련 세트와 테스트 세트 모두 대략 85%의 정확도로 좋은 성능을 제공한다. 그러나 훈련 세트와 테스트 세트 모두 모델의 성능이 매우 비교가 가능하므로, 과소적합일 가능성이 있다.
C를 높이고 더 유연한 모델을 적합시켜보자.
# fit the Logsitic Regression model with C=100
# instantiate the model
logreg100 = LogisticRegression(C=100, solver='liblinear', random_state=0)
# fit the model
logreg100.fit(X_train, y_train)
LogisticRegression(C=100, class_weight=None, dual=False, fit_intercept=True,
intercept_scaling=1, l1_ratio=None, max_iter=100,
multi_class='warn', n_jobs=None, penalty='l2',
random_state=0, solver='liblinear', tol=0.0001, verbose=0,
warm_start=False)
# print the scores on training and test set
print('Training set score: {:.4f}'.format(logreg100.score(X_train, y_train)))
print('Test set score: {:.4f}'.format(logreg100.score(X_test, y_test)))
Training set score: 0.8478
Test set score: 0.8505
C값을 100으로 설정한 더 규제가 높은 모델을 사용했을 때, 테스트 세트의 정확도는 0.8266으로 낮아졌다. 이는 모델이 지나치게 단순해져서 일반화 능력이 감소했다는 것을 의미한다. 따라서 C=1보다 더 규제를 가하는 것은 좋지 않은 결과를 가져올 수 있다.
이제 C=0.01로 세팅함으로써 어떤 일이 발생하는 지 봐보자.
# fit the Logsitic Regression model with C=001
# instantiate the model
logreg001 = LogisticRegression(C=0.01, solver='liblinear', random_state=0)
# fit the model
logreg001.fit(X_train, y_train)
LogisticRegression(C=0.01, class_weight=None, dual=False, fit_intercept=True,
intercept_scaling=1, l1_ratio=None, max_iter=100,
multi_class='warn', n_jobs=None, penalty='l2',
random_state=0, solver='liblinear', tol=0.0001, verbose=0,
warm_start=False)
# print the scores on training and test set
print('Training set score: {:.4f}'.format(logreg001.score(X_train, y_train)))
print('Test set score: {:.4f}'.format(logreg001.score(X_test, y_test)))
Training set score: 0.8409
Test set score: 0.8448
그래서, C=0.01로 더 규제가 된 모델을 사용하면, 기본 매개 변수에 비해 훈련 및 테스트 세트 정확도가 감소한다.
null 정확도 비교
모델의 정확도는 0.8501이다. 하지만, 우리의 모델이 위의 정확도보다 매우 좋다고는 할 수 없다. 그래서 null 정확도Null Accuracy를 비교해야만 한다. Null 정확도는 가장 빈번한 클래스를 예측하는 것으로 달성할 수 있는 정확도이다.
따라서 먼저 테스트 세트에서 클래스 분포를 확인해야 한다.
# check class distribution in test set
y_test.value_counts()
No 22067
Yes 6372
Name: RainTomorrow, dtype: int64
가장 빈번한 클래스의 발생 횟수는 22067이다. 따라서 총 발생 횟수로 나누어 22067을 계산하여 null 정확도를 계산할 수 있다.
# check null accuracy score
null_accuracy = (22067/(22067+6372))
print('Null accuracy score: {0:0.4f}'. format(null_accuracy))
Null accuracy score: 0.7759
우리는 모델의 정확도 점수가 0.8501이지만, 널 정확도 점수는 0.7759임을 알 수 있다. 따라서 우리는 로지스틱 회귀 모델이 클래스 레이블을 예측하는 데 매우 잘하고 있다고 결론을 내릴 수 있다.
이제 위의 분석을 기반으로 분류 모델의 정확도가 매우 높다고 결론을 내릴 수 있다. 우리 모델은 클래스 레이블을 예측하는 데 아주 좋은 성능을 보인다.
하지만 이 방법은 값의 분포를 보여주지 않으며, 분류기가 어떤 유형의 오류를 발생시키는지 알려주지 않는다.
오차 행렬
Confusion matrix라는 도구가 도와준다.
15. 오차 행렬
오차 행렬(Confusion matrix)은 분류 알고리즘의 성능을 요약하는 도구이다. 오차 행렬을 사용하면 분류 모델의 성능과 모델이 생성한 오류 유형을 명확하게 파악할 수 있다. 이는 각 범주별로 올바른 예측과 잘못된 예측을 요약하여 표 형태로 제공한다.
분류 모델의 성능을 평가할 때, 네 가지 종류의 결과가 가능하다:
True Positives (TP) - TP는 우리가 관찰이 특정 클래스에 속한다고 예측하고, 실제로 그 클래스에 속하는 경우이다.
True Negatives (TN) - TN은 우리가 관찰이 특정 클래스에 속하지 않는다고 예측하고, 실제로 그 클래스에 속하지 않는 경우이다.
False Positives (FP) - FP는 우리가 관찰이 특정 클래스에 속한다고 예측했지만, 실제로는 그 클래스에 속하지 않는 경우이다. 이러한 종류의 오류를 Type I error
라고 한다.
False Negatives (FN) - FN은 우리가 관찰이 특정 클래스에 속하지 않는다고 예측했지만, 실제로는 그 클래스에 속하는 경우이다. 이러한 오류는 매우 심각하며, Type II error
라고 하다.
이 네 가지 결과는 다음과 같이 요약되며, 아래의 오차 행렬에서 확인할 수 있다.
# Print the Confusion Matrix and slice it into four pieces
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, y_pred_test)
print('Confusion matrix\n\n', cm)
print('\nTrue Positives(TP) = ', cm[0,0])
print('\nTrue Negatives(TN) = ', cm[1,1])
print('\nFalse Positives(FP) = ', cm[0,1])
print('\nFalse Negatives(FN) = ', cm[1,0])
Confusion matrix
[[20892 1175]
[ 3086 3286]]
True Positives(TP) = 20892
True Negatives(TN) = 3286
False Positives(FP) = 1175
False Negatives(FN) = 3086
이 오차 행렬은 20892 + 3285 = 24177개의 올바른 예측과 3087 + 1175 = 4262개의 틀린 예측을 보여줍니다.
다음과 같은 결과를 얻을 수 있다.
True Positives
(실제 Positive:1 및 예측 Positive:1) - 20892
True Negatives
(실제 Negative:0 및 예측 Negative:0) - 3285
False Positives
(실제 Negative:0이지만 예측 Positive:1) - 1175 (Type I error)
False Negatives
(실제 Positive:1이지만 예측 Negative:0) - 3087 (Type II error)
# visualize confusion matrix with seaborn heatmap
cm_matrix = pd.DataFrame(data=cm, columns=['Actual Positive:1', 'Actual Negative:0'],
index=['Predict Positive:1', 'Predict Negative:0'])
sns.heatmap(cm_matrix, annot=True, fmt='d', cmap='YlGnBu')
<matplotlib.axes._subplots.AxesSubplot at 0x7f28b1306208>
16. 분류 지표
분류 보고서
분류 보고서는 분류 모델의 성능을 평가하는 또 다른 방법이다. 이 보고서는 모델의 정밀도(precision), 재현율(recall), F1 점수와 지원(support) 점수를 표시한다.
분류 보고서는 다음과 같이 출력할 수 있습니다:-
from sklearn.metrics import classification_report
print(classification_report(y_test, y_pred_test))
precision recall f1-score support
No 0.87 0.95 0.91 22067
Yes 0.74 0.52 0.61 6372
accuracy 0.85 28439
macro avg 0.80 0.73 0.76 28439
weighted avg 0.84 0.85 0.84 28439
분류 정확성
TP = cm[0,0]
TN = cm[1,1]
FP = cm[0,1]
FN = cm[1,0]
# print classification accuracy
classification_accuracy = (TP + TN) / float(TP + TN + FP + FN)
print('Classification accuracy : {0:0.4f}'.format(classification_accuracy))
Classification accuracy : 0.8502
분류 오류
# print classification error
classification_error = (FP + FN) / float(TP + TN + FP + FN)
print('Classification error : {0:0.4f}'.format(classification_error))
Classification error : 0.1498
정밀도
정밀도는 예측된 양성 결과 중에서 올바르게 예측된 결과의 비율로 정의할 수 있다. 이것은 참 양성(True Positive; TP)을 예측한 비율이다.
수학적으로, 정밀도는 TP를 (TP + FP)로 나눈 비율로 정의된다.
# print precision score
precision = TP / float(TP + FP)
print('Precision : {0:0.4f}'.format(precision))
Precision : 0.9468
재현율
재현율은 참 양성 중에서 올바르게 예측된 양성의 비율을 나타냅니다. 참 양성(TP)를 참 양성과 거짓 음성의 합인 (TP + FN)으로 나눈 비율로 표현할 수 있다. 재현율은 민감도(Sensitivity)라고도 불립니다.
수학적으로는 TP / (TP + FN)으로 정의할 수 있다.
recall = TP / float(TP + FN)
print('Recall or Sensitivity : {0:0.4f}'.format(recall))
Recall or Sensitivity : 0.8713
참 양성 비율
참 양성 비율은 재현율과 같은 말이다.
true_positive_rate = TP / float(TP + FN)
print('True Positive Rate : {0:0.4f}'.format(true_positive_rate))
True Positive Rate : 0.8713
거짓 양성 비율
false_positive_rate = FP / float(FP + TN)
print('False Positive Rate : {0:0.4f}'.format(false_positive_rate))
False Positive Rate : 0.2634
특이도
specificity = TN / (TN + FP)
print('Specificity : {0:0.4f}'.format(specificity))
Specificity : 0.7366
f1-score
f1-score는 정밀도와 재현율의 가중 조화 평균이다. 최고의 f1-score는 1.0이며 최악의 경우는 0.0이다. f1-score는 정확도 측정치보다 항상 낮다. f1-score의 가중 평균은 전역 정확도보다 분류기 모델을 비교하는 데 사용해야 한다.
Support
Support는 데이터셋에서 해당 클래스가 실제로 발생한 횟수를 나타내는 값이다.
17. 임계값 조정
# print the first 10 predicted probabilities of two classes- 0 and 1
y_pred_prob = logreg.predict_proba(X_test)[0:10]
y_pred_prob
array([[0.91382428, 0.08617572],
[0.83565645, 0.16434355],
[0.82033915, 0.17966085],
[0.99025322, 0.00974678],
[0.95726711, 0.04273289],
[0.97993908, 0.02006092],
[0.17833011, 0.82166989],
[0.23480918, 0.76519082],
[0.90048436, 0.09951564],
[0.85485267, 0.14514733]])
관찰 결과
-
각 행(row)의 숫자는 1에 합산된다.
-
2개의 열(column)은 0과 1의 2개의 클래스를 나타낸다.
-
클래스 0 - 내일 비가 오지 않을 확률에 대한 예측 확률.
-
클래스 1 - 내일 비가 올 확률에 대한 예측 확률.
-
-
예측 확률의 중요성
- 우리는 비 또는 비가 오지 않을 확률에 따라 관찰 값을 순위 지정할 수 있다.
-
predict_proba 과정
-
확률 값을 예측한다.
-
가장 높은 확률을 가진 클래스를 선택한다.
-
-
분류 임계값
-
분류 임계값이 0.5
-
클래스 1 - 확률 > 0.5 이면 비가 올 확률이 예측된다.
-
클래스 0 - 확률 < 0.5 이면 비가 오지 않을 확률이 예측된다.
-
# store the probabilities in dataframe
y_pred_prob_df = pd.DataFrame(data=y_pred_prob, columns=['Prob of - No rain tomorrow (0)', 'Prob of - Rain tomorrow (1)'])
y_pred_prob_df
Prob of - No rain tomorrow (0) | Prob of - Rain tomorrow (1) | |
---|---|---|
0 | 0.913824 | 0.086176 |
1 | 0.835656 | 0.164344 |
2 | 0.820339 | 0.179661 |
3 | 0.990253 | 0.009747 |
4 | 0.957267 | 0.042733 |
5 | 0.979939 | 0.020061 |
6 | 0.178330 | 0.821670 |
7 | 0.234809 | 0.765191 |
8 | 0.900484 | 0.099516 |
9 | 0.854853 | 0.145147 |
# print the first 10 predicted probabilities for class 1 - Probability of rain
logreg.predict_proba(X_test)[0:10, 1]
array([0.08617572, 0.16434355, 0.17966085, 0.00974678, 0.04273289,
0.02006092, 0.82166989, 0.76519082, 0.09951564, 0.14514733])
# store the predicted probabilities for class 1 - Probability of rain
y_pred1 = logreg.predict_proba(X_test)[:, 1]
# plot histogram of predicted probabilities
# adjust the font size
plt.rcParams['font.size'] = 12
# plot histogram with 10 bins
plt.hist(y_pred1, bins = 10)
# set the title of predicted probabilities
plt.title('Histogram of predicted probabilities of rain')
# set the x-axis limit
plt.xlim(0,1)
# set the title
plt.xlabel('Predicted probabilities of rain')
plt.ylabel('Frequency')
Text(0, 0.5, 'Frequency')
관찰 결과
-
위의 히스토그램은 매우 양으로 치우쳤다.
-
첫번째 열은 약 0.0부터 0.1 사이의 확률을 가진 약 15000개의 관측치가 있다는 것을 보여준다.
-
확률이 0.5보다 큰 일부 관측치가 존재한다.
-
이 작은 수의 관측치는 내일 비가 올 것이라고 예측한다.
-
대부분의 관측치는 내일 비가 오지 않을 것이라고 예측한다.
더 낮은 임계값
from sklearn.preprocessing import binarize
for i in range(1,5):
cm1=0
y_pred1 = logreg.predict_proba(X_test)[:,1]
y_pred1 = y_pred1.reshape(-1,1)
y_pred2 = binarize(y_pred1, i/10)
y_pred2 = np.where(y_pred2 == 1, 'Yes', 'No')
cm1 = confusion_matrix(y_test, y_pred2)
print ('With',i/10,'threshold the Confusion Matrix is ','\n\n',cm1,'\n\n',
'with',cm1[0,0]+cm1[1,1],'correct predictions, ', '\n\n',
cm1[0,1],'Type I errors( False Positives), ','\n\n',
cm1[1,0],'Type II errors( False Negatives), ','\n\n',
'Accuracy score: ', (accuracy_score(y_test, y_pred2)), '\n\n',
'Sensitivity: ',cm1[1,1]/(float(cm1[1,1]+cm1[1,0])), '\n\n',
'Specificity: ',cm1[0,0]/(float(cm1[0,0]+cm1[0,1])),'\n\n',
'====================================================', '\n\n')
With 0.1 threshold the Confusion Matrix is
[[12726 9341]
[ 547 5825]]
with 18551 correct predictions,
9341 Type I errors( False Positives),
547 Type II errors( False Negatives),
Accuracy score: 0.6523084496641935
Sensitivity: 0.9141556811048337
Specificity: 0.5766982371867494
====================================================
With 0.2 threshold the Confusion Matrix is
[[17066 5001]
[ 1234 5138]]
with 22204 correct predictions,
5001 Type I errors( False Positives),
1234 Type II errors( False Negatives),
Accuracy score: 0.7807588171173389
Sensitivity: 0.8063402385436284
Specificity: 0.7733720034440568
====================================================
With 0.3 threshold the Confusion Matrix is
[[19080 2987]
[ 1872 4500]]
with 23580 correct predictions,
2987 Type I errors( False Positives),
1872 Type II errors( False Negatives),
Accuracy score: 0.8291430781673055
Sensitivity: 0.7062146892655368
Specificity: 0.8646395069560883
====================================================
With 0.4 threshold the Confusion Matrix is
[[20191 1876]
[ 2517 3855]]
with 24046 correct predictions,
1876 Type I errors( False Positives),
2517 Type II errors( False Negatives),
Accuracy score: 0.845529027040332
Sensitivity: 0.6049905838041432
Specificity: 0.9149861784565188
====================================================
Comments
-
이진 분류 문제에서는 기본적으로 예측된 확률을 클래스 예측으로 변환하기 위해 0.5의 임계값을 사용한다.
-
임계값은 민감도 또는 특이도를 높이기 위해 조정할 수 있다.
-
민감도와 특이도는 서로 역의 관계를 가지고 있습니다. 한 쪽을 높이면 다른 쪽은 낮아지게 된다(트레이드 오프 관계).
-
임계값을 높이면 정확도가 높아지는 것을 볼 수 있다.
-
임계값 조정은 모델 구축 프로세스에서 마지막 단계 중 하나여야 한다.
18. ROC-AUC
ROC 곡선
분류 모델의 성능을 시각적으로 측정하는 또 다른 도구는 ROC 곡선이다.
ROC 곡선은 다양한 임계값에서 참 양성 비율(TPR)와 거짓 양성 비율(FPR)을 나타냅니다.
다양한 임계값에서의 TPR과 FPR로 이루어진 ROC 곡선의 일반적인 성능을 파악할 수 있다. 임계값을 낮출수록, 더 많은 항목이 긍정적으로 분류될 수 있다.
# plot ROC Curve
from sklearn.metrics import roc_curve
fpr, tpr, thresholds = roc_curve(y_test, y_pred1, pos_label = 'Yes')
plt.figure(figsize=(6,4))
plt.plot(fpr, tpr, linewidth=2)
plt.plot([0,1], [0,1], 'k--' )
plt.rcParams['font.size'] = 12
plt.title('ROC curve for RainTomorrow classifier')
plt.xlabel('False Positive Rate (1 - Specificity)')
plt.ylabel('True Positive Rate (Sensitivity)')
plt.show()
ROC-AUC
ROC AUC는 분류기의 성능을 비교하는 기술이다. 이 기술에서는 곡선 아래 면적인 AUC (Area Under Curve)를 측정한다. 완벽한 분류기는 ROC AUC가 1이 되고, 완전한 무작위 분류기는 ROC AUC가 0.5가 된다.
따라서, ROC AUC는 ROC 그래프에서 곡선 아래 부분의 백분율을 나타낸다.
# compute ROC AUC
from sklearn.metrics import roc_auc_score
ROC_AUC = roc_auc_score(y_test, y_pred1)
print('ROC AUC : {:.4f}'.format(ROC_AUC))
ROC AUC : 0.8729
Comments
- ROC AUC는 값이 높을수록 분류기의 성능이 더 좋다.
# calculate cross-validated ROC AUC
from sklearn.model_selection import cross_val_score
Cross_validated_ROC_AUC = cross_val_score(logreg, X_train, y_train, cv=5, scoring='roc_auc').mean()
print('Cross validated ROC AUC : {:.4f}'.format(Cross_validated_ROC_AUC))
Cross validated ROC AUC : 0.8695
19. k-Fold 교차 검증
# Applying 5-Fold Cross Validation
from sklearn.model_selection import cross_val_score
scores = cross_val_score(logreg, X_train, y_train, cv = 5, scoring='accuracy')
print('Cross-validation scores:{}'.format(scores))
Cross-validation scores:[0.84686387 0.84624852 0.84633642 0.84963298 0.84773626]
교차 검증 정확도는 평균을 계산하여 요약할 수 있다.
# compute Average cross-validation score
print('Average cross-validation score: {:.4f}'.format(scores.mean()))
Average cross-validation score: 0.8474
원래 모델의 점수는 0.8476으로 나타났다. 교차 검증의 평균 점수는 0.8474다. 따라서, 교차 검증이 성능 향상을 가져오지 않는다는 결론을 내릴 수 있다.
20. 그리드 탐색 CV를 사용한 하이퍼파라미터 최적화
from sklearn.model_selection import GridSearchCV
parameters = [{'penalty':['l1','l2']},
{'C':[1, 10, 100, 1000]}]
grid_search = GridSearchCV(estimator = logreg,
param_grid = parameters,
scoring = 'accuracy',
cv = 5,
verbose=0)
grid_search.fit(X_train, y_train)
GridSearchCV(cv=5, error_score='raise-deprecating',
estimator=LogisticRegression(C=1.0, class_weight=None, dual=False,
fit_intercept=True,
intercept_scaling=1, l1_ratio=None,
max_iter=100, multi_class='warn',
n_jobs=None, penalty='l2',
random_state=0, solver='liblinear',
tol=0.0001, verbose=0,
warm_start=False),
iid='warn', n_jobs=None,
param_grid=[{'penalty': ['l1', 'l2']}, {'C': [1, 10, 100, 1000]}],
pre_dispatch='2*n_jobs', refit=True, return_train_score=False,
scoring='accuracy', verbose=0)
# examine the best model
# best score achieved during the GridSearchCV
print('GridSearch CV best score : {:.4f}\n\n'.format(grid_search.best_score_))
# print parameters that give the best results
print('Parameters that give the best results :','\n\n', (grid_search.best_params_))
# print estimator that was chosen by the GridSearch
print('\n\nEstimator that was chosen by the search :','\n\n', (grid_search.best_estimator_))
GridSearch CV best score : 0.8474
Parameters that give the best results :
{'penalty': 'l1'}
Estimator that was chosen by the search :
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
intercept_scaling=1, l1_ratio=None, max_iter=100,
multi_class='warn', n_jobs=None, penalty='l1',
random_state=0, solver='liblinear', tol=0.0001, verbose=0,
warm_start=False)
# calculate GridSearch CV score on test set
print('GridSearch CV score on test set: {0:0.4f}'.format(grid_search.score(X_test, y_test)))
GridSearch CV score on test set: 0.8507
Comments
원래 모델의 테스트 정확도는 0.8501이고, GridSearch CV의 정확도는 0.8507다.
따라서, 이 모델에서 GridSearch CV가 성능을 개선시켰다는 것을 알 수 있다.
21. 결과 및 결론
-
로지스틱 회귀 모델의 정확도 점수는 0.8501다. 따라서 이 모델은 오늘 오스트레일리아에서 비가 올 것인지 아닌지 예측하는 데 아주 잘 작동한다.
-
일부 관측치는 내일 비가 올 것으로 예측하고 있지만, 대부분의 관측치는 내일 비가 오지 않을 것으로 예측하고 있다.
-
모델은 과대 적합의 흔적이 없다.
-
C값을 높일수록 테스트 세트 정확도와 약간 증가한 훈련 세트 정확도를 얻을 수 있다. 따라서 더 복잡한 모델이 더 나은 성능을 발휘할 것으로 결론을 내릴 수 있다.
-
임계값을 높이면 정확도가 증가한다.
-
모델의 ROC AUC는 1에 가까워진다. 따라서 이 분류기는 내일 비가 올지 안 올지 예측하는 데 아주 잘 작동한다고 결론을 내릴 수 있다.
-
원래 모델의 정확도 점수는 0.8501이고, RFECV 이후 정확도 점수는 0.8500다. 따라서 약간의 feature를 줄여서 비슷한 정확도를 얻을 수 있다.
-
원래 모델에서 FP = 1175이고, FP1 = 1174다. 거짓 양성의 수가 거의 같다. 또한, FN = 3087이고, FN1 = 3091다. 거짓 음성이 약간 더 높아졌다.
-
원래 모델의 점수는 0.8476다. 교차 검증의 평균 점수는 0.8474다. 따라서 교차 검증은 성능 향상을 가져오지 않는다는 결론을 내릴 수 있다.
-
원래 모델의 테스트 정확도는 0.8501이고, GridSearch CV의 정확도는 0.8507다. GridSearch CV가 이 모델에서 성능을 개선시켰다는 것을 알 수 있다.
22. 참고 문헌
-
Hands on Machine Learning with Scikit-Learn and Tensorflow by Aurélién Géron
-
Introduction to Machine Learning with Python by Andreas C. Müller and Sarah Guido
-
Udemy course – Machine Learning – A Z by Kirill Eremenko and Hadelin de Ponteves
-
Udemy course – Feature Engineering for Machine Learning by Soledad Galli
-
Udemy course – Feature Selection for Machine Learning by Soledad Galli
-
https://en.wikipedia.org/wiki/Logistic_regression
-
https://ml-cheatsheet.readthedocs.io/en/latest/logistic_regression.html
-
https://en.wikipedia.org/wiki/Sigmoid_function
-
https://www.statisticssolutions.com/assumptions-of-logistic-regression/
-
https://www.kaggle.com/mnassrib/titanic-logistic-regression-with-python
-
https://www.kaggle.com/neisha/heart-disease-prediction-using-logistic-regression
-
https://www.ritchieng.com/machine-learning-evaluate-classification-model/