데이콘 대회 참여 - 10 데이터 시각화

Page content

공지

제 수업을 듣는 사람들이 계속적으로 실습할 수 있도록 강의 파일을 만들었습니다. 늘 도움이 되기를 바라며. 참고했던 교재 및 Reference는 꼭 확인하셔서 교재 구매 또는 관련 Reference를 확인하시기를 바랍니다.

사전작업

  • 한글 시각화를 위해 나눔고딕 폰트를 불러온다.
!pip install psankey #  sankey diagram
%config InlineBackend.figure_format = 'retina'
!apt -qq -y install fonts-nanum
Requirement already satisfied: psankey in /usr/local/lib/python3.6/dist-packages (1.0.1)
fonts-nanum is already the newest version (20170925-1).
The following package was automatically installed and is no longer required:
  libnvidia-common-440
Use 'apt autoremove' to remove it.
0 upgraded, 0 newly installed, 0 to remove and 33 not upgraded.
import matplotlib.font_manager as fm
import matplotlib as mpl
import matplotlib.pyplot as plt

fontpath = '/usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf'
font = fm.FontProperties(fname=fontpath, size=9)
plt.rc('font', family='NanumBarunGothic')
mpl.font_manager._rebuild()
mpl.pyplot.rc('font', family='NanumGothic')
fm._rebuild()
  • 위 코드 정렬이 끝난 이후에는 런타임-다시시작을 진행한다.
  • 위 셀만 실행한다.

I. 빅쿼리 연동

  • 지난 시간에 데이콘에서 내려받은 데이터를 빅쿼리에 넣는 작업을 진행하였다.
  • 빅쿼리에 저장된 데이터를 구글 코랩으로 불러오려면 다음과 같이 진행한다.

(1) 사용자 계정 인증

구글 코랩을 사용해서 인증 절차를 밟도록 한다. 아래 소스코드는 변경시키지 않는다. 아래 절차대로 진행하면 된다. Gmail 인증 절차와 비슷하다.

from google.colab import auth
auth.authenticate_user()
print('Authenticated')
Authenticated

(2) 데이터 불러오기

  • 이번에는 빅쿼리에서 Random Sampling을 활용하여 데이터를 가져온다.
  • 층화추출도 할 수 있는데, 쿼리상 조금 복잡할 수 있어서, 이부분은 추후 포스팅 하는 것으로 남겨둔다.
  • 이번에는 데이터 갯수를 기존 10000개 $\rightarrow$ 1000000개로 확대해서 추출한다.
    • 확대한 이유 1. 데이터의 경향성을 조금 더 면밀하게 파악하기 위해
    • 확대한 이유 2. 조금 더 많은 데이터를 학습 시켜서 모형의 안정성을 주기 위해
from google.cloud import bigquery
from tabulate import tabulate
import pandas as pd

project_id = 'your_project_id'
client = bigquery.Client(project=project_id)

train = client.query('''
  SELECT 
      * 
  FROM `your_project_id.jeju_data_ver1.201901_202003_train` 
  WHERE RAND() < 1000000 / (SELECT COUNT(*) FROM `your_project_id.jeju_data_ver1.201901_202003_train`)
  ''').to_dataframe()

II. 데이터 전처리 1차

  • 모형을 학습하기 전, 사전 준비 작업에 해당한다.
  • 도메인 + 통계 + 프로그래밍 3가지 기술이 모두 요구되는 지점이기도 하다.
  • 우선은 간단하게 처리만 하였다.

(1) 머신러닝 & 시각화 & 통계 패키지 Loading

  • 종속변수와 독립변수로 구분할 필요가 있다.
import pandas as pd
import numpy as np
import sklearn
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.preprocessing import LabelEncoder

# 03 Chapter에서 추가
# 평가 메트릭
from sklearn.metrics import mean_squared_error, mean_absolute_error

# 시각화
import seaborn as sns
color = sns.color_palette()
sns.set_style('darkgrid')
%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt  # Matlab-style plotting
from psankey.sankey import sankey

# 통계
from scipy import stats
from scipy.stats import norm, skew #for some statistics

# 06 Chapter 추가
from sklearn.base import BaseEstimator, TransformerMixin, RegressorMixin, clone
from sklearn.model_selection import KFold, cross_val_score, train_test_split, GridSearchCV
from sklearn.metrics import mean_squared_error
print('Pandas : %s'%(pd.__version__))
print('Numpy : %s'%(np.__version__))
print('Scikit-Learn : %s'%(sklearn.__version__))
print('seaborn : %s'%(sns.__version__))
print('matplotlib : %s'%(matplotlib.__version__))
!python --version
Pandas : 1.0.5
Numpy : 1.18.5
Scikit-Learn : 0.22.2.post1
seaborn : 0.10.1
matplotlib : 3.2.2
Python 3.6.9

(2) 데이터 전처리 - 결측치 제거

  • 우선 각 데이터별 결측치의 동향을 살펴본다.
train.head()
# data set의 Percent 구하는 함수를 짜보자. 
def check_fill_na(data):
  new_df = data.copy()
  new_df_na = (new_df.isnull().sum() / len(new_df)) * 100
  new_df_na.sort_values(ascending=False).reset_index(drop=True)
  new_df_na = new_df_na.drop(new_df_na[new_df_na == 0].index).sort_values(ascending=False)
  return new_df_na

check_fill_na(train)
HOM_CCG_NM     0.611500
CARD_CCG_NM    0.344251
dtype: float64
  • 결측치의 비율이 애매하게 나타났다.
    • HOM_CCG_MM은 고객의 집주소를 의미
    • CARD_CCG_MM은 카드이용지역_시군구 의미
  • 위 부분에서 결측치가 발생한 이유, 그리고 결측치가 일치하지 않는 이유를 파악해야 하지만, 현재로써는 파악하기 어려우니, 우선 None으로 처리한다.
train["HOM_CCG_NM"] = train["HOM_CCG_NM"].fillna("None")
train["CARD_CCG_NM"] = train["CARD_CCG_NM"].fillna("None")

check_fill_na(train)
Series([], dtype: float64)
  • 결측치가 없음을 확인했다.

(3) 날짜 데이터 전처리

  • 데이터 전처리 기본함수 작성
  • 우선 날짜 처리 진행
    • year & month로 구분한다.
def grap_year(data):
    data = str(data)
    return int(data[:4])

def grap_month(data):
    data = str(data)
    return int(data[4:])
# 날짜 처리
data = train.copy()
print(data['REG_YYMM'].head())
0    201905
1    201906
2    201902
3    201905
4    201905
Name: REG_YYMM, dtype: int64
data['year'] = data['REG_YYMM'].apply(lambda x: grap_year(x))
data['month'] = data['REG_YYMM'].apply(lambda x: grap_month(x))
data['REG_YYMM'] = data['REG_YYMM'].apply(str)
# data.head()

(4) 시군구 컬럼 제거

  • submission 제출 파일 목록에서 시/도는 해당되나, 시군구 항목은 해당되지 않는다.
  • 따라서, 해당 컬럼은 삭제한다.
# 데이터 정제
df = data.drop(['CARD_CCG_NM', 'HOM_CCG_NM'], axis=1)
columns = ['REG_YYMM', 'CARD_SIDO_NM', 'STD_CLSS_NM', 'HOM_SIDO_NM', 'AGE', 'SEX_CTGO_CD', 'FLC', 'year', 'month']
df = df.groupby(columns).sum().reset_index(drop=False)
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 340266 entries, 0 to 340265
Data columns (total 12 columns):
 #   Column        Non-Null Count   Dtype 
---  ------        --------------   ----- 
 0   REG_YYMM      340266 non-null  object
 1   CARD_SIDO_NM  340266 non-null  object
 2   STD_CLSS_NM   340266 non-null  object
 3   HOM_SIDO_NM   340266 non-null  object
 4   AGE           340266 non-null  object
 5   SEX_CTGO_CD   340266 non-null  int64 
 6   FLC           340266 non-null  int64 
 7   year          340266 non-null  int64 
 8   month         340266 non-null  int64 
 9   CSTMR_CNT     340266 non-null  int64 
 10  AMT           340266 non-null  int64 
 11  CNT           340266 non-null  int64 
dtypes: int64(7), object(5)
memory usage: 31.2+ MB

III. 데이터 시각화

  • 조금 더 정확한 전처리를 하기 위해 시각화를 진행해보자.
  • 이 때, 핵심이 되는 변수는 AMT 변수이다.
    • 즉, AMT변수는 Y축으로 놓고 시각화를 진행할 것이다.

(1) 시각화의 작성 방향

  • 데이터 분석에 있어서 가장 기본적인 것은 데이터의 유형을 파악하는 것이다.
    • 명목척도, 순서척도, 구간척도, 비율척도
  • 각 척도마다, 시각화를 하는 방법이 존재한다.
  • 우선 날짜 기준 데이터셋을 기준으로 데이터를 시각화해보자.
  • 시각화에 앞서서, 시각화의 화면단을 미리 설정한다.
%matplotlib inline
import matplotlib.font_manager as fm
import matplotlib as mpl
import matplotlib.pyplot as plt

fontpath = '/usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf'
font = fm.FontProperties(fname=fontpath, size=9)
plt.rc('font', family='NanumBarunGothic')
mpl.font_manager._rebuild()
mpl.pyplot.rc('font', family='NanumGothic')
fm._rebuild()

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

(2) Numeric Features ~ REG_YYMM

  • 위 그래프를 그릴려면, REG_YYMM 기준으로 그래프를 작성한다.
for fea in ["AMT", "CNT", "CSTMR_CNT"]:
  sns.lineplot(x="REG_YYMM", y=fea, data = df)
  plt.xlabel(fea)
  plt.title(fea)
  plt.yscale('log')
  plt.show()

png

png

png

  • 3개 모두 그래프가 유사한 것으로 판단되었다.
    • 즉, 3개의 변수중 대표성이자 또한 종속변수로 판단되는 AMT만 좋을 것 같다.
  • 여기에서 한가지 의문이 드는 것은, 202001~202003까지 지표가 우상향 했다는 것이다.
    • 코로나로 인해 경기가 위축되었다는 기존 뉴스와는 조금 상반되어, 시도별로 그리고 업종별로 다시 확인할 필요가 있음을 느낀다.

(3) AMT ~ REG_YYMM + CARD_SIDO_NM + HOM_SIDO_NM

  • AMTREG_YYMM는 고정 값이다.
  • 이 때, 시도별로 구분해서 시각화를 할 예정이다.
    • 두개의 막대그래프를 비교해서 그리면 어떨까 생각했다.
temp = df[["REG_YYMM", "CARD_SIDO_NM", "HOM_SIDO_NM", "AMT"]]

dates = ['202001', '202002', '202003']
isDates = temp['REG_YYMM'].isin(dates)
temp  = temp[isDates].reset_index(drop=True)
temp.groupby(["CARD_SIDO_NM","REG_YYMM"]).sum().unstack().plot.bar()
plt.yscale('log')

png

  • 카드 가맹점 기준으로 봤을 때, 3월에 매출이 급증하기 시작한것은 수도권보다는 경북, 광주, 대구, 제주, 울산 등으로 확인 되었다.
    • 특히, 대구 및 제주지역의 매출이 전월 대비 큰폭으로 상승하였다.
    • 이것이 의미하는 바는 무엇일까?
temp.groupby(["HOM_SIDO_NM","REG_YYMM"]).sum().unstack().plot.bar()
plt.yscale('log')

png

  • 이번에는 거주지역_시도 (고객 집주소 기준)으로 시각화한 결과, 마찬가지로 지방권의 매출이 급증하는 것을 볼 수 있다.
  • 이 두개의 데이터만으로 그래프로 추정할 수 있는 것은, 수도권보다는 지방중심지를 중심으로 매출액이 크게 변동 되는 것을 확인할 수 있다.
    • 강원, 대구, 울산, 전남, 제주, 충북 등의 데이터가 크게 흔들리는 것을 볼 수 있다.
    • 여기에서는 무엇을 봐야할까?
    • 또한, 일시적으로 흔들리는 데이터를 어떻게 바라봐야 할까?

(4) AMT ~ REG_YYMM + STD_CLSS_NM

  • 위 데이터를 기준으로 Temp 데이터를 작성하는데, 마찬가지로 어떤 업종의 데이터가 큰 변동성을 보여주는지 확인해본다.
temp2 = df[["REG_YYMM", "STD_CLSS_NM", "AMT"]]

dates = ['202001', '202002', '202003']
isDates = temp2['REG_YYMM'].isin(dates)
temp2  = temp2[isDates].reset_index(drop=True)
  • STD_CLSS_NM의 데이터 항목이 41개이기 때문에, 10개씩 잘라서 데이터를 확인한다.
std_clss_nm = temp2['STD_CLSS_NM'].unique().tolist()
_n = 10
_10 = std_clss_nm[:_n]
print(_10)
['건강보조식품 소매업', '골프장 운영업', '과실 및 채소 소매업', '관광 민예품 및 선물용품 소매업', '그외 기타 스포츠시설 운영업', '그외 기타 종합 소매업', '기타 대형 종합 소매업', '기타 외국식 음식점업', '기타 주점업', '기타음식료품위주종합소매업']
_20 = std_clss_nm[_n:_n+10]
print(_20)
['마사지업', '비알콜 음료점업', '빵 및 과자류 소매업', '서양식 음식점업', '수산물 소매업', '슈퍼마켓', '스포츠 및 레크레이션 용품 임대업', '여관업', '여행사업', '욕탕업']
_30 = std_clss_nm[_n+10:_n+20]
print(_30)
['육류 소매업', '일반유흥 주점업', '일식 음식점업', '전시 및 행사 대행업', '중식 음식점업', '차량용 가스 충전업', '차량용 주유소 운영업', '체인화 편의점', '택시 운송업', '피자 햄버거 샌드위치 및 유사 음식점업']
_41 = std_clss_nm[_n+20:]
print(_41)
['한식 음식점업', '호텔업', '화장품 및 방향제 소매업', '휴양콘도 운영업', '기타 수상오락 서비스업', '버스 운송업', '자동차 임대업', '내항 여객 운송업', '면세점', '정기 항공 운송업', '그외 기타 분류안된 오락관련 서비스업']
def grouped_plot_bar(data, value_list, plt_title=False):
  isCLSS = data['STD_CLSS_NM'].isin(value_list)
  data = data[isCLSS].reset_index(drop=True)
  data.groupby(['STD_CLSS_NM', 'REG_YYMM']).sum().unstack().plot.bar()
  plt.yscale('log')

clss_list = [_10, _20, _30, _41]

# grouped_plot_bar(temp2, _20)
for val_list in clss_list:
  grouped_plot_bar(temp2, val_list)

png

png

png

png

  • 전반적으로 관광쪽 매출이 지속적으로 하락하는 것으로 볼 수 있다.
  • 위 시각화를 보면서, 눈에 들어오는 것은 소비의 형태를 필수/비필수로 나누는 것이 중요할 것 같다.
    • 특히, 관광쪽 매출이 큰 타격을 입을 것인데, 지금도 크게 회복되지 않은 것을 감안해서 도출변수를 작성하면 좋을 것 같다.

(5) AMT ~ REG_YYMM + STD_CLSS_NM + CARD_SIDO_NM

  • 이제 마지막 다변량 그래프를 작성해본다.
  • 각 시도별로 위 그래프를 그려보고, 지역별로 업종별의 매출 변화가 어떻게 일어나는지 한번 더 구체적으로 살펴본다.
temp3 = df[["REG_YYMM", "CARD_SIDO_NM", "STD_CLSS_NM", "AMT"]]
dates = ['202001', '202002', '202003']
isDates = temp3['REG_YYMM'].isin(dates)
temp4  = temp3[isDates].reset_index(drop=True)
temp4.head()
print(temp4.CARD_SIDO_NM.unique().tolist())
['강원', '경기', '경남', '경북', '광주', '대구', '대전', '부산', '서울', '세종', '울산', '인천', '전남', '전북', '제주', '충남', '충북']
temp5 = temp4.groupby(['REG_YYMM', 'CARD_SIDO_NM']).sum().reset_index()
temp5.columns = ['source', 'target', 'value']
temp6 = temp4.groupby(['CARD_SIDO_NM', 'STD_CLSS_NM']).sum().reset_index()
temp6.columns = ['source', 'target', 'value']
links = pd.concat([temp5, temp6], axis=0)
print(links.head())
   source target        value
0  202001     강원   5272394622
1  202001     경기  36953324227
2  202001     경남  11335671846
3  202001     경북   6773691327
4  202001     광주   3720483970
sankey(links, aspect_ratio=3/4, nodelabels=True, linklabels=False, labelsize=8, nodecmap='copper', nodealpha=0.5, nodeedgecolor='white')
plt.figure(figsize=(50, 50))
plt.show()

png

<Figure size 3600x3600 with 0 Axes>
  • 다른 시각화 패키지를 활용해서 그래프를 그려보자.
card_sido_nm = temp4.CARD_SIDO_NM.unique().tolist()

for sido in card_sido_nm:
  data = temp4.copy()
  data2 = data.loc[data['CARD_SIDO_NM'] == sido]
  clss_list = [_10, _20, _30, _41]
  for val_list in clss_list:
    grouped_plot_bar(data2, val_list, sido)
    plt.title(sido)
/usr/local/lib/python3.6/dist-packages/pandas/plotting/_matplotlib/core.py:320: RuntimeWarning: More than 20 figures have been opened. Figures created through the pyplot interface (`matplotlib.pyplot.figure`) are retained until explicitly closed and may consume too much memory. (To control this warning, see the rcParam `figure.max_open_warning`).
  fig = self.plt.figure(figsize=self.figsize)

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

IV. What’s Next

  • 시각화를 진행한 이후에, 시도+업종별을 합산하여 가중치 도출변수를 만든다.
  • 그리고, lightgbm으로 성능을 테스트해본다.