EDA with Housing Price Prediction - Handling Missing Values

Page content

강의 홍보

I. 개요

  • 이제 본격적으로 Kaggle 데이터를 활용하여 분석을 진행한다.
  • 데이터는 이미 다운 받은 상태를 전제로 하며, 만약에 데이터가 없다면 이전 포스팅에서 절차를 확인하기 바란다. (미리보기 가능)

II. 구글 드라이브 연동

  • 구글 코랩을 시작하면 언제든지 가장 먼저 해야 하는 것은 드라이브 연동이다.
from google.colab import drive # 패키지 불러오기 
from os.path import join  

ROOT = "/content/drive"     # 드라이브 기본 경로
print(ROOT)                 # print content of ROOT (Optional)
drive.mount(ROOT)           # 드라이브 기본 경로 Mount

MY_GOOGLE_DRIVE_PATH = 'My Drive/Colab Notebooks/inflearn_kaggle/' # 프로젝트 경로
PROJECT_PATH = join(ROOT, MY_GOOGLE_DRIVE_PATH) # 프로젝트 경로
print(PROJECT_PATH)
/content/drive
Go to this URL in a browser: https://accounts.google.com/o/oauth2/auth?client_id=947318989803-6bn6qk8qdgf4n4g3pfee6491hc0brc4i.apps.googleusercontent.com&redirect_uri=urn%3aietf%3awg%3aoauth%3a2.0%3aoob&response_type=code&scope=email%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdocs.test%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdrive%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdrive.photos.readonly%20https%3a%2f%2fwww.googleapis.com%2fauth%2fpeopleapi.readonly

Enter your authorization code:
··········
Mounted at /content/drive
/content/drive/My Drive/Colab Notebooks/inflearn_kaggle/
%cd "{PROJECT_PATH}"
/content/drive/My Drive/Colab Notebooks/inflearn_kaggle
  • 위 코드가 에러 없이 돌아간다면 이제 데이터를 불러올 차례다.
!ls
data  docs  source
  • 필자는 inflearn_kaggle 폴더안에 data, docs, source 등의 하위 폴더를 추가로 만들었다.
  • 즉, data 안에 다운로드 받은 파일이 있을 것이다.

III. 캐글 데이터 수집 및 EDA

  • 우선 데이터를 수집하기에 앞서서 EDA에 관한 필수 패키지를 설치하자.
import pandas as pd
import pandas_profiling
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.pyplot import figure
import seaborn as sns

from IPython.core.display import display, HTML
from pandas_profiling import ProfileReport
/usr/local/lib/python3.6/dist-packages/statsmodels/tools/_testing.py:19: FutureWarning: pandas.util.testing is deprecated. Use the functions in the public API at pandas.testing instead.
  import pandas.util.testing as tm
%matplotlib inline
import matplotlib.pylab as plt

plt.rcParams["figure.figsize"] = (14,4)
plt.rcParams['lines.linewidth'] = 2
plt.rcParams['lines.color'] = 'r'
plt.rcParams['axes.grid'] = True 

(1) 데이터 수집

  • 지난 시간에 받은 데이터가 총 4개임을 확인했다.
    • data_description.txt
    • sample_submission.csv
    • test.csv
    • train.csv
  • 여기에서는 우선 test.csv & train.csv 파일을 받도록 한다.
train = pd.read_csv('data/train.csv')
test = pd.read_csv('data/test.csv')
print("data import is done")
data import is done

(2) 데이터 확인

  • Kaggle 데이터를 불러오면 우선 확인해야 하는 것은 데이터셋의 크기다.
    • 변수의 갯수
    • Numeric 변수 & Categorical 변수의 개수 등을 파악해야 한다.
  • Point 1 - train데이터에서 굳이 훈련데이터와 테스트 데이터를 구분할 필요는 없다.
    • 보통 Kaggle에서는 테스트 데이터를 주기적으로 업데이트 해준다.
  • Point 2 - 보통 test 데이터의 변수의 개수가 하나 더 작다.
train.shape, test.shape
((1460, 81), (1459, 80))
  • 그 후 train데이터의 상위 5개의 데이터만 확인한다.
display(train.head())
IdMSSubClassMSZoningLotFrontageLotAreaStreetAlleyLotShapeLandContourUtilitiesLotConfigLandSlopeNeighborhoodCondition1Condition2BldgTypeHouseStyleOverallQualOverallCondYearBuiltYearRemodAddRoofStyleRoofMatlExterior1stExterior2ndMasVnrTypeMasVnrAreaExterQualExterCondFoundationBsmtQualBsmtCondBsmtExposureBsmtFinType1BsmtFinSF1BsmtFinType2BsmtFinSF2BsmtUnfSFTotalBsmtSFHeating...CentralAirElectrical1stFlrSF2ndFlrSFLowQualFinSFGrLivAreaBsmtFullBathBsmtHalfBathFullBathHalfBathBedroomAbvGrKitchenAbvGrKitchenQualTotRmsAbvGrdFunctionalFireplacesFireplaceQuGarageTypeGarageYrBltGarageFinishGarageCarsGarageAreaGarageQualGarageCondPavedDriveWoodDeckSFOpenPorchSFEnclosedPorch3SsnPorchScreenPorchPoolAreaPoolQCFenceMiscFeatureMiscValMoSoldYrSoldSaleTypeSaleConditionSalePrice
0160RL65.08450PaveNaNRegLvlAllPubInsideGtlCollgCrNormNorm1Fam2Story7520032003GableCompShgVinylSdVinylSdBrkFace196.0GdTAPConcGdTANoGLQ706Unf0150856GasA...YSBrkr85685401710102131Gd8Typ0NaNAttchd2003.0RFn2548TATAY0610000NaNNaNNaN022008WDNormal208500
1220RL80.09600PaveNaNRegLvlAllPubFR2GtlVeenkerFeedrNorm1Fam1Story6819761976GableCompShgMetalSdMetalSdNone0.0TATACBlockGdTAGdALQ978Unf02841262GasA...YSBrkr1262001262012031TA6Typ1TAAttchd1976.0RFn2460TATAY29800000NaNNaNNaN052007WDNormal181500
2360RL68.011250PaveNaNIR1LvlAllPubInsideGtlCollgCrNormNorm1Fam2Story7520012002GableCompShgVinylSdVinylSdBrkFace162.0GdTAPConcGdTAMnGLQ486Unf0434920GasA...YSBrkr92086601786102131Gd6Typ1TAAttchd2001.0RFn2608TATAY0420000NaNNaNNaN092008WDNormal223500
3470RL60.09550PaveNaNIR1LvlAllPubCornerGtlCrawforNormNorm1Fam2Story7519151970GableCompShgWd SdngWd ShngNone0.0TATABrkTilTAGdNoALQ216Unf0540756GasA...YSBrkr96175601717101031Gd7Typ1GdDetchd1998.0Unf3642TATAY035272000NaNNaNNaN022006WDAbnorml140000
4560RL84.014260PaveNaNIR1LvlAllPubFR2GtlNoRidgeNormNorm1Fam2Story8520002000GableCompShgVinylSdVinylSdBrkFace350.0GdTAPConcGdTAAvGLQ655Unf04901145GasA...YSBrkr1145105302198102141Gd9Typ1TAAttchd2000.0RFn3836TATAY192840000NaNNaNNaN0122008WDNormal250000
......................................................................................................................................................................................................................................................
1455145660RL62.07917PaveNaNRegLvlAllPubInsideGtlGilbertNormNorm1Fam2Story6519992000GableCompShgVinylSdVinylSdNone0.0TATAPConcGdTANoUnf0Unf0953953GasA...YSBrkr95369401647002131TA7Typ1TAAttchd1999.0RFn2460TATAY0400000NaNNaNNaN082007WDNormal175000
1456145720RL85.013175PaveNaNRegLvlAllPubInsideGtlNWAmesNormNorm1Fam1Story6619781988GableCompShgPlywoodPlywoodStone119.0TATACBlockGdTANoALQ790Rec1635891542GasA...YSBrkr2073002073102031TA7Min12TAAttchd1978.0Unf2500TATAY34900000NaNMnPrvNaN022010WDNormal210000
1457145870RL66.09042PaveNaNRegLvlAllPubInsideGtlCrawforNormNorm1Fam2Story7919412006GableCompShgCemntBdCmentBdNone0.0ExGdStoneTAGdNoGLQ275Unf08771152GasA...YSBrkr1188115202340002041Gd9Typ2GdAttchd1941.0RFn1252TATAY0600000NaNGdPrvShed250052010WDNormal266500
1458145920RL68.09717PaveNaNRegLvlAllPubInsideGtlNAmesNormNorm1Fam1Story5619501996HipCompShgMetalSdMetalSdNone0.0TATACBlockTATAMnGLQ49Rec102901078GasA...YFuseA1078001078101021Gd5Typ0NaNAttchd1950.0Unf1240TATAY3660112000NaNNaNNaN042010WDNormal142125
1459146020RL75.09937PaveNaNRegLvlAllPubInsideGtlEdwardsNormNorm1Fam1Story5619651965GableCompShgHdBoardHdBoardNone0.0GdTACBlockTATANoBLQ830LwQ2901361256GasA...YSBrkr1256001256101131TA6Typ0NaNAttchd1965.0Fin1276TATAY736680000NaNNaNNaN062008WDNormal147500

1460 rows × 81 columns

  • 그 다음 확인해야 하는 것은 Numerical 변수와 Categorical 변수를 구분한다.
    • 먼저 numerical_features를 구분하자.
numeric_features = train.select_dtypes(include=[np.number])
print(numeric_features.columns)
print("The total number of numeric features are: ", len(numeric_features.columns))
Index(['Id', 'MSSubClass', 'LotFrontage', 'LotArea', 'OverallQual',
       'OverallCond', 'YearBuilt', 'YearRemodAdd', 'MasVnrArea', 'BsmtFinSF1',
       'BsmtFinSF2', 'BsmtUnfSF', 'TotalBsmtSF', '1stFlrSF', '2ndFlrSF',
       'LowQualFinSF', 'GrLivArea', 'BsmtFullBath', 'BsmtHalfBath', 'FullBath',
       'HalfBath', 'BedroomAbvGr', 'KitchenAbvGr', 'TotRmsAbvGrd',
       'Fireplaces', 'GarageYrBlt', 'GarageCars', 'GarageArea', 'WoodDeckSF',
       'OpenPorchSF', 'EnclosedPorch', '3SsnPorch', 'ScreenPorch', 'PoolArea',
       'MiscVal', 'MoSold', 'YrSold', 'SalePrice'],
      dtype='object')
The total number of numeric features are:  38
  • numeric_features을 제외한 나머지 변수를 추출하자.
categorical_features = train.select_dtypes(exclude=[np.number])
print(categorical_features.columns)
print("The total number of numeric features are: ", len(categorical_features.columns))
Index(['MSZoning', 'Street', 'Alley', 'LotShape', 'LandContour', 'Utilities',
       'LotConfig', 'LandSlope', 'Neighborhood', 'Condition1', 'Condition2',
       'BldgType', 'HouseStyle', 'RoofStyle', 'RoofMatl', 'Exterior1st',
       'Exterior2nd', 'MasVnrType', 'ExterQual', 'ExterCond', 'Foundation',
       'BsmtQual', 'BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2',
       'Heating', 'HeatingQC', 'CentralAir', 'Electrical', 'KitchenQual',
       'Functional', 'FireplaceQu', 'GarageType', 'GarageFinish', 'GarageQual',
       'GarageCond', 'PavedDrive', 'PoolQC', 'Fence', 'MiscFeature',
       'SaleType', 'SaleCondition'],
      dtype='object')
The total number of numeric features are:  43

IV. 연도 데이터 탐색 개요

  • 지난시간에 각 데이터에서 categorical_featuresnumeric_features를 각각 추출하는 방법에 대해 배웠다.

    • categorical_features: Alley, BldgType, BsmtCond, BsmtExposure, BsmtFinType1, BsmtFinType2, BsmtQual, CentralAir, Condition1, Condition2, Electrical, ExterCond, ExterQual, Exterior1st, Exterior2nd, Fence, FireplaceQu, Foundation, Functional, GarageCond, GarageFinish, GarageQual, GarageType, Heating, HeatingQC, HouseStyle, KitchenQual, LandContour, LandSlope, LotConfig, LotShape, MSZoning, MasVnrType, MiscFeature, Neighborhood, PavedDrive, PoolQC, RoofMatl, RoofStyle, SaleCondition, SaleType, Street, Utilitif
    • numeric_features:‘Id’, ‘MSSubClass’, ‘LotFrontage’, ‘LotArea’, ‘OverallQual’, ‘OverallCond’, ‘YearBuilt’, ‘YearRemodAdd’, ‘MasVnrArea’, ‘BsmtFinSF1’, ‘BsmtFinSF2’, ‘BsmtUnfSF’, ‘TotalBsmtSF’, ‘1stFlrSF’, ‘2ndFlrSF’, ‘LowQualFinSF’, ‘GrLivArea’, ‘BsmtFullBath’, ‘BsmtHalfBath’, ‘FullBath’, ‘HalfBath’, ‘BedroomAbvGr’, ‘KitchenAbvGr’, ‘TotRmsAbvGrd’, ‘Fireplaces’, ‘GarageYrBlt’, ‘GarageCars’, ‘GarageArea’, ‘WoodDeckSF’, ‘OpenPorchSF’, ‘EnclosedPorch’, ‘3SsnPorch’, ‘ScreenPorch’, ‘PoolArea’, ‘MiscVal’, ‘MoSold’, ‘YrSold’, ‘SalePrice’
  • 여기에서 우선 categorical_features는 논의에서 제외한다.

  • 이 중에서, 우선 numeric_features를 다시 살펴본다.

    • 우선 훈련데이터의 ID는 삭제한다.
    • 또한, 종속변수인 salesprice는 테스트 데이터에는 존재하지 않는다.
    • 그럼 결과적으로 36개numeric_features만 남게 된다.
  • 36개의 features만 남았다.

    • 여기에서 유심히 살펴보면 Year과 관련된 features가 보인다.
  • 특히 매출과 관련된 데이터를 다루는데 있어서, 연,월,일은 매우 중요하다. 패턴을 찾아서 특정 변수만 추출하는 코드가 필요하다.

    • YearBuilt, YearRemodAdd, GarageYrBlt, YrSold만 추출해보자.
year_fea = [fea for fea in numeric_features if 'Yr' in fea or 'Year' in fea]
print(year_fea)
['YearBuilt', 'YearRemodAdd', 'GarageYrBlt', 'YrSold']

(1) 연도의 변수 처리 방법

  • 여기에서 잊지 말아야 하는 것은 새로운 데이터셋을 만들더라도 항상 종속변수(sales price)는 늘 함께 움직여야 한다.
  • 각각의 변수는 어떻게 이해해야 할까?
    • 이 때, 필요한 것이 일종의 데이터 정의서가 필요하다.
    • data_description.txt를 참고하자.
  • 각 변수는 다음과 같다.
    • YearBuilt: Original construction date
    • YearRemodAdd: Remodel date (same as construction date if no remodeling or additions)
    • GarageYrBlt: Year garage was built
    • YrSold: Year Sold (YYYY)
  • 여기에서 우선 각 변수별로 연도가 차이가 나는지 확인해보자.
for fea in year_fea:
  data = train.copy()
  data[fea].value_counts(sort=False).plot(kind='bar')
  plt.xlabel(fea)
  plt.title(fea)
  plt.show()

png

png

png

png

  • 위 그래프를 보면서 알 수 있는 것은 무엇일까?
    • 우선, 첫 건축 시기는 1872년이고 첫 리모델링 시기는 1950년이고, 마지막으로 첫 차고 건축시기는 1930년으로 확인된다.
    • 그리고 매매 시기는 2006-2010년 사이로 집계된 것으로 확인할 수 있다.

(2) SalePrice와의 관계

  • 위 데이터는 각 연도의 특성을 이해하는데는 도움이 될지 모르지만, 결국 Kaggle대회에서 중요한 것은 종속변수와의 관계다
  • 조금 의미있는 변수를 만들어야 한다. (위 4가지 변수의 조합)
    • 보통 실무에서는 이를 도출변수라고 하는데.. 이 부분은 Intermediate level에서 조금 더 자세히 다루기로 한다.
    • 여기서 강사가 하고자 하는 것은 YrSold에서 그 외 다른 변수와의 연도 시기의 차이를 계산하면 통상적으로 연수가 짧으면 짧을수록 매매가도 올라가고 연수가 길면 길수록 매매가가 하락하는 것을 예상할 수 있다.
    • 실제 그러한 그래프를 그리도록 해보자.
for fea in year_fea:
    if fea!='YrSold': # `YrSold` 변수는 제외 한다.
        data=train.copy() # 이렇게 해주는 것이 좋다. (원본 데이터는 늘 보존할 수 있다)
        data[fea]=data['YrSold']-data[fea] #  여기가 사실 핵심 포인트다. 연수 차이 계산
        plt.scatter(data[fea], data['SalePrice']) # 산점도 그래프를 그린다.
        plt.title(fea)
        plt.xlabel(fea)
        plt.ylabel('SalePrice')
        plt.show()

png

png

png

  • 굳이 회귀선을 그리지 않더라도, 연수의 차이가 작을수록 매매가가 높게 형성된 것을 확인할 수 있다.

V. 양적 변수 시각화 - 이산형 그래프

VI. 양적 변수 시각화 - 연속형 그래프

VII. 범주형 변수 탐색

VIII. 결측치 처리

  • 결측치는 데이터 분석 및 처리에 있어서 가장 큰 적이자 어려움이다.
  • 이론적으로 간단하게 결측데이터의 종류에 대해 확인해보자.
  • 결측치를 처리하는 여러가지 방법론이 있지만, 우선 여기에서는 결측치를 확인하는 과정을 시각화하는 것을 목적으로 한다.

(1) 결측 데이터의 종류

  • 임의적 결측 발생(MAR: Missing at Random): 누락된 데이터가 특정 변수와 관련되어 일어나지만, 그 변수의 값과는 관계가 없는 경우. 예를 들면, 어떤 설문조사에서 누락된 자료가 특정 변수들에 국한되어 발견되었는데 알고 보니 일부 대상자가 설문지 3페이지에 반대쪽 면이 있는 것을 모르고 채우지 않았을 경우 MAR로 확인할 수 있다.
  • 완전무작위 결측 발생(MCAR: Missing Completely at Random): 변수의 종류와 변수의 값과 상관없이 전체에 걸쳐 무작위적으로 나타나는 것으로, 이러한 missing data는 분석에 영향을 주지 않는다.
  • 비임의적 결측 발생(NMAR: Not Missing at Random): 누락된 변수의 값과 누락된 이유가 관련이 있는 경우. 예를 들면, 일부 설문지에 종교 또는 정치적인 이유로 일부러 대답을 회피하거나 데이터입력 과정에서의 실수한 경우에 발생한다.

(2) 결측 데이터 시각화

  • 모든 데이터에 공통적으로 적용할 수 있기 때문에 다음 코드를 함수(=function)화 해서 관리하는 것을 추천한다.

Bar Graph

missing_total = train.isnull().sum().sort_values(ascending=False)
missing_rate = (train.isnull().sum()/train.isnull().count()).sort_values(ascending=False)
missing_data = pd.concat([missing_total, missing_rate], axis=1, keys=['Total', 'Percent'])
f, ax = plt.subplots(figsize=(15, 6))
plt.xticks(rotation='90')
sns.barplot(x=missing_data.index, y=missing_data['Percent'])
plt.xlabel('Features', fontsize=15)
plt.ylabel('Percent of missing values', fontsize=15)
plt.title('Percent missing data by feature', fontsize=15)

png

  • 시각적으로 봐도 상위 4개의 변수 PoolQC, MiscFeature, Alley, Fence를 그대로 사용하는 것은 문제가 있어 보인다.
  • 또다른 시각화 방법은 missingno 패키지를 활용한 방법니다.

Heatmap

import missingno as msno
msno.heatmap(train)
<matplotlib.axes._subplots.AxesSubplot at 0x7f1fe3aa5240>

png

  • 위 그래프의 해석은 다음과 같다.
    • -1A변수가 데이터가 확인되면 B변수의 데이터가 없을 관계가 크다는 뜻이다.
    • 0에 가까울 수록 두 변수 사이의 결측치의 의존성은 없다.
    • 1A변수에 결측치가 있으면 B변수에 결측치가 존재할 가능성이 크다.

(3) 결측 데이터 처리방법

  • 우선, 평균, 빈도, 중간값, 최대, 최소 등과 같은 방법으로 결측 데이터를 처리하지 않는다. (특히, 통계모형과 관련된 작업을 수행한다면 더욱 그렇다.)
  • MCAR 결측치의 경우에는 삭제해도 무방하다.
  • MAR의 경우에는 Expectation Maximization 알고리즘을 사용해서 결측치 대치를 한다. 한글 표현으로는 기대값 최대화 알고리즘이라고 불리우며 자세한 설명은 Learn by example Expectation Maximization에서 확인한다.
  • MNAR의 경우에는 이항 데이터로 변환하는 방법을 선택할 수 있다. 다만, 이러한 접근법의 어려움은 이 정보를 적절한 방식으로 적절히 통합할 수 있도록 매우 비선형적인 모델을 설계해야 한다.
  • 실제 코드를 통해서 결측 데이터를 처리하는 방법은 ML 모형을 설계할 때 다시 설명하도록 한다.

VI. Review & What’s next

  • 처음 프로그래밍을 입문하시는 분들에게 for-loop 또는 if 문법에 대해 고민하지 말 것을 권한다.
    • 우선 intermediate 들어가기전에 그래프의 종류, 기초 문법, 데이터 타입의 종류 등에 대해 한번 정리를 할 것이다.
  • 이산형 그래프의 핵심은 다변량의 그래프를 for-loop를 통해서 비교적 쉽고 빠르게 그래프를 구현했다는 점이다.
    • 금일 업데이트 한 소스코드를 복습하고, 또한 boxplot의 개념이 약하신 분들은 교재를 ���번 더 참고하기를 바란다.
  • 연속형 그래프의 핵심은 여러 그래프를 한 이미지로 확인할 수 있도록 구현하기 위해 그래프의 좌표평면 기법을 도입했다는 것을 확인한다.
  • 범주형 변수 탐색의 핵심은 유일한 값이 몇개가 존재하는지 파악하는 것이 우선이다.
    • 이산형 그래프와 같이 박스플롯 그래프를 작성했지만, SalePrice의 값이 매우 크게 요동치는 것을 확인할 수 있었다.
  • 다음 시간에는 이상치에 대해 이야기를 진행한다.