반응형

듀얼 모멘텀은 투자 자산 가운데 상대적으로 상승 추세가 강한 종목에 투자하는 상대 모멘텀 전략과거 시점 대비 현재 시점의 절대적 상승세를 평가한 절대 모멘텀 전략을 결합해 위험을 관리하는 투자 전략이다. 

  • 모멘텀(momentum)은 물질의 운동량이나 가속도를 의미하는 물리학 용어로, 투자 분야에서는 주가가 한 방향성을 유지하려는 힘을 의미한다.

듀얼 모멘텀 전략은 국내에서도 많은 미디어에 소개되며 대중에게 익숙한 퀀트 투자 전략이기도 하다. 

듀얼 모멘텀 전략을 구현하기 위해서는 먼저 절대 모멘텀과 상대 모멘텀을 이해하고 구현할 수 있어야 한다. 

최근 N개월간 수익률이 양수이면 매수하고 음수이면 공매도하는 전략을 절대 모멘텀 전략이라고 한다.
반면에 상대 모멘텀 전략은 투자 종목군이 10개 종목이라 할 때 10개 종목의 최근 N개월 모멘텀을 계산해 상대적으로 모멘텀이 높은 종목을 매수하고 상대적으로 낮은 종목은 공매도하는 전략이다. 

이번에는 절대 모멘텀 전략을 실행해 보도록 한다.

절대 모멘텀 전략

먼저 필요한 라이브러리들을 가져오고, 분석할 데이터셋을 읽어온다.

import pandas as pd
import numpy as np
import FinanceDataReader as fdr

read_df = fdr.DataReader('SPY', '1993-01-29', '2023-09-12') # S&P 500 ETF 데이터를 가져옴.
read_df.head()

그 다음 조정 종가만 선택해 price_df 변수에 별도의 데이터프레임으로 저장한다.

price_df = read_df.loc[:,['Adj Close']].copy()
price_df.head()

이 전략에서는 월말 데이터를 추가해서 사용한다.

price_df['STD_YM'] = price_df.index.to_period('M')
price_df.head()

이번에는 월말 종가에 접근하는 데이터프레임을 만들어 보자.

month_list = price_df['STD_YM'].unique()
month_last_df = pd.DataFrame()
for m in month_list:
    # 기준 연월에 맞는 인덱스의 마지막 날짜 row를 데이터프레임에 추가
    month_last_df = month_last_df._append(price_df.loc[price_df[price_df['STD_YM'] == m].index[-1], :])

month_last_df.head()

현재 데이터프레임에 있는 중복을 제거한 모든 '연-월' 데이터를 리스트에 저장해 월별 말일자에 해당하는 종가에 쉽게 접근할 수 있다.

월말 종가 데이터를 적재하는 별도의 데이터프레임을 만든다. 월말 종가 데이터 리스트를 순회하면서 index[-1]을 통해 월말에 쉽게 접근하고, 데이터를 추출해 새로 만든 데이터프레임에 적재한다.
(append가 pandas 2.0부터 제거됨. _append로 사용가능)

모멘텀 지수를 계산하기 위한 이전 시점의 데이터를 어떻게 가공하는지 살펴보자.

month_last_df['BF_1M_Adj Close'] = month_last_df.shift(1)['Adj Close']
month_last_df['BF_12M_Adj Close'] = month_last_df.shift(12)['Adj Close']
month_last_df.fillna(0, inplace=True)
month_last_df.head(15)

판다스 DataFrame에서 제공하는 shift()함수를 이용하면 손쉽게 데이터를 가공할 수 있다. shift() 함수는 기본값이 period = 1, axis = 0으로 설정되지만, 입력 매개변수를 원하는 값으로 대체해 사용할 수 있다. 
우리가 만든 데이터프레임에서 1을 지정하면 1개월 전 말일자 종가를 가져오고, 12를 넣으면 12개월 전 말일자 종가 데이터를 가져오게 할 수 있다. 데이터를 뒤로 밀어내는 것인데 초기 n개의 행에는 값이 없으므로 np.nan 값이 출력된다. 

이제 모멘텀 지수를 계산해서 거래가 생길 때 포지션을 기록할 DataFrame을 만든다.

book = price_df.copy()
book['trade'] = ''
book.head()

반응형

최종적으로 기록된 포지션을 바탕으로 최종 수익률을 계산하는 데 사용된다. 
처음에 만든 일별 종가가 저장된 DataFrame을 복사해 날짜 Date 컬럼을 index로 설정하고 거래가 일어난 내역을 저장할 Trade 컬럼을 만든다.

# trading 부분
ticker = 'SPY'
for x in month_last_df.index:
    signal = ''
    # 절대 모멘텀을 계산
    momentum_index = month_last_df.loc[x, 'BF_1M_Adj Close'] / month_last_df.loc[x,'BF_12M_Adj Close']-1
    # 절대 모멘텀 지표 True/False를 판단
    flag = True if ((momentum_index > 0.0) and (momentum_index != np.inf) and (momentum_index != -np.inf)) else False and True
    if flag :
        signal = 'buy ' + ticker # 절대 모멘텀 지표가 Positive이면 매수 후 보유
    print('날짜 : ', x, '모멘텀 인덱스 : ', momentum_index, 'flag : ', flag, 'signal : ', signal)
    book.loc[x:, 'trade'] = signal

월별 인덱스를 순회하면서 12개월 전 종가 대비 1개월 전 종가 수익률이 얼마인지 계산한다. 계산된 수익률은 momentum_index 변수에 저장해 0 이상인지 확인하고, 0 이상이면 모멘텀 현상이 나타난 것으로 판단해 매수 신호가 발생하도록 한다. 이 부분을 flag 변수로 처리했다. 이어서 signal 변수에 저장된 매수 신호를 book 데이터프레임에 저장한다.

월말 종가를 기준으로 매수/매도 신호를 계산하므로 최소 1개월 이상 해당 포지션을 유지한다. 포지션을 유지하는 기간은 개인마다 다르지만 보통 1개월을 유지하기도 한다. 이를 전문 용어로 리밸런스 주기라고 한다.

그 다음으로 전략 수익률을 확인해 보자. 

def returns(book, ticker):
    # 손익 계산
    rtn = 1.0
    book['return'] = 1
    buy = 0.0
    sell = 0.0
    for i in book.index:
        if book.loc[i, 'trade'] == 'buy ' + ticker and book.shift(1).loc[i, 'trade'] == '' :
            # long 진입
            buy = book.loc[i, 'Adj Close']
            print('진입일 : ', i, 'long 진입가격 : ', buy)
        elif book.loc[i, 'trade'] == 'buy' + ticker and book.shift(1).loc[i, 'trade'] == 'buy ' + ticker:
            # 보유중
            current = book.loc[i, 'Adj Close']
            rtn = (current - buy) / buy + 1
            book.loc[i, 'return'] = rtn

        elif book.loc[i, 'trade'] == '' and book.shift(1).loc[i, 'trade'] == 'buy ' + ticker:
            # long 청산
            sell = book.loc[i, 'Adj Close']
            rtn = (sell - buy) / buy + 1 # 손익 계산
            book.loc[i, 'return'] = rtn
            print('청산일 : ', i, 'long 진입가격 : ', buy, ' | long 청산가격 : ', sell, ' | return: ', round(rtn, 4))

        if book.loc[i, 'trade'] == '': # 제로 포지션
            buy = 0.0
            sell = 0.0
            current = 0.0

    acc_rtn = 1.0
    for i in book.index :
        if book.loc[i, 'trade'] == '' and book.shift(1).loc[i, 'trade'] == 'buy ' + ticker:
            # long 청산시
            rtn = book.loc[i, 'return']
            acc_rtn = acc_rtn * rtn # 누적 수익률 계산
            book.loc[i:, 'acc return'] = acc_rtn
    print ('Accumulated return : ', round(acc_rtn, 4))
    return (round(acc_rtn, 4))

SPY ETF의 경우 12개월 전 대비 가격이 올랐을 때 매수하는 절대 모멘텀 전략의 수익률은 다음과 같다.
1에서 시작해 16.4076으로 종료되었으므로 1994년부터 2023년까지 1600%의 수익률을 낸 것이다. 

지금까지 단일 종목을 절대 모멘텀 전략으로 구현해 보았다.
절대 모멘텀 전략은 종목별 자신의 과거 수익률을 기반으로 매수/매도 신호를 포착하므로 단일 종목으로도 구성할 수 있다.

 

<참고 서적> 퀀트 전략을 위한 인공지능 트레이딩

반응형
반응형

퀀트Quant 투자를 달리 표현하면 데이터 기반 data-driven 전략이라고 할 수 있다. 

퀀트는 정량적 방법론을 기반으로 투자 의사를 결정하는 것이며, 정량적 방법론이란 모든 것을 수치화하는 것을 의미한다. 

사용하는 데이터에 따라 주가를 사용해 기술 지표를 만들고 이를 투자에 활용하는 '기술 지표 투자 전략'과 기업 재무제표를 사용하는 '가치 투자 전략', 이렇게 두 가지로 크게 나눌 수 있다. 

주식 시장을 바라보는 트레이더의 시각에 따라 사용되는 지표는 다르다.

기술 지표를 활용한 퀀트 투자 전략 구현에서의 관건은 주가 데이터를 활용해 기술 지표를 만드는 것이다. 
많은 파이썬 라이브러리에서 해당 기능을 제공하지만, 전략 확장성을 위해 수식을 기반으로 직접 만들어보는 것이 더 의미가 있다. 
해당 지표들로 신호를 발생시켜 종목을 매매하고 전략의 수익률이나 승률 등을 살펴보는 방법도 알아본다. 

기술 지표를 활용한 퀀트 투자 방법으로 모멘텀 전략과 평균 회귀 전략을 알아볼 것이다. 평균 회귀와 모멘텀은 서로 상반된 개념이다. 두 전략의 상관관계가 낮고 전략이 초점을 맞추는 시간대도 다르기 때문에 양극에 있는 두 방법을 살펴볼 가치는 충분하다고 볼 수 있다.

가치 투자 전략은 당기순이익, 영업이익, 영업이익률, 매출액, 부채비율, PER, PBR, PSR, PCR, ROE, ROA 등 기업의 가치 판단에 기준이 되는 재무제표 데이터를 기초로 한다. 

가치 투자 전략에서는 기준이 되는 데이터에서 순위를 매겨 분위별로 자르는 작업이 훨씬 더 중요하다. 

하지만 전략 평가는 기술 지표를 활용한 퀀트 투자 방법과 유사하다.

평균 회귀 전략

평균 회귀 (regression to mean)란 결과를 예측할 때 평균에 가까워지려는 경향성을 말한다. 한번 평균보다 큰 값이 나오면 다음번에는 평균보다 작은 값이 나와 전체적으로는 평균 수준을 유지한다는 의미다. 

주식시장에서도 평균 회귀 현상이 통용되는지 검증하려는 많은 시도가 있었다.
이 현상에 의하면, 가격이 평균보다 높아지면 다음번에는 평균보다 낮아질 확률이 높다. 

평균 회귀 속성을 이용한 전략을 들여다보면, 주가의 평균가격을 계산해 일정 범위를 유지하느냐 이탈하느냐에 따라 매매를 결정한다. 현재 주가가 평균가격보다 낮으면 앞으로 주가가 상승할 것으로 기대해 주식을 매수하고, 현재 주가가 평균가격보다 높으면 앞으로 주가가 하락할 것으로 예상해 주식을 매도하는 규칙을 설정할 수 있다. 

볼린저 밴드 Bollinger band

볼린저 밴드는 현재 주가가 상대적으로 높은지 낮은지를 판단할 때 사용하는 보조지표다. 볼린저 밴드는 3개 선으로 구성되는데, 중심선은 이동평균 moving average 선, 상단선과 하단성을 구성하는 표준편차 standard deviation 밴드다.

볼린저 밴드를 사용하는 이유는 이동 평균선으로 추세를 관찰하고 상단선과 하단선으로 밴드 내에서 현재 가격의 상승과 하락폭을 정량적으로 계산할 수 있기 때문이다. 볼린저 밴드는 '추세'와 '변동성'을 분석해주어 기술 분석에서 활용도가 높으며 평균 회귀 현상을 관찰하는 데도 매우 유용하다. 
보통 중심선을 이루는 이동 평균선을 계산할 땐 20일을 사용하고 상하위 밴드는 20일 이동 평균선 ± 2 * 20일 이동 표준 편차(σ)를 사용한다.

그럼 판다스를 사용해 볼린저 밴드 공식을 구현해 보도록 한다.

import pandas as pd
import FinanceDataReader as fdr

df = fdr.DataReader('AAPL', '1997-01-01', '2023-09-12') #애플 주식데이터를 가져온다.
df.head()

가장 먼저 해야할 작업은 데이터를 불러와 탐색해 보는 것이다.
애플('AAPL') 데이터를 실시간으로 불러와 df 변수에 저장한 후 head()함수를 통해 상위 5개 데이터를 확인한다. 
인덱스는 날짜(Date)로 되어 있고, 각 컬럼은 시가(Open), 고가(High), 저가(Low), 종가(Close), 수정 종가(Adj Close), 거래량(Volume) 데이터로 구성된다.

데이터프레임에 있는 describe() 함수를 사용해 데이터셋의 분포, 경향 분산 및 형태를 요약하는 정보를 확인할 수 있다.

df.describe()

위 데이터셋에서 볼린저 밴드 분석에 필요한 컬럼만 선택하도록 한다.

price_df = df.loc[:, [''Adj Close']].copy()
price_df.head()

price_df 데이터셋으로 볼린저 밴드를 만들어 본자.

  • 상단 밴드 = 중간 밴드 + 2*20일 이동 표준 편차
  • 중간 밴드 = 20일 이동 평균선
  • 하단 밴드 = 중간 밴드 - 2*20일 이동 표준 편차

먼저 중간 밴드를 만들도록 한다. 중간 밴드의 20일 이동 평균선은 판다스의 rolling() 함수를 이용하여 만든다.

price_df['center'] = price_df['Adj Close'].rolling(window = 20).mean()
price_df.iloc[18:25]

iloc 인덱서를 사용해 18행부터 24행까지 어떤 값이 들어있는지 확인해 본다. 새롭게 만들어진 center 컬럼은 rolling()함수와 mean()함수로 계산된 20일 이동 평균선을 의미한다.
rolling() 함수 특성상 window 입력값으로 20일을 주었기 때문에 데이터가 20개 미만인 부분은 결측치를 의미하는 NaN으로 표시된다. 

일반적으로 결측치는 어쩔 수 없이 생성되기 때문에 볼린저 밴드에 필요한 기간보다 앞뒤로 더 많은 데이터(보통 쿠션 데이터 cushion data라고 부르는 여분의 데이터)를 추가하여 분석을 해야 한다. 실무에서는 생성하려는 자료를 고려해서 작성하는 것이 중요하지만, 여기서는 결측치가 발생한 행을 삭제한다.

중간 밴드를 만들었으니 이번에는 상단 밴드와 하단 밴드를 만든다.

price_df['ub'] = price_df['center] + 2 * price_df['Adj Close'].rolling(window=20).std()
price_df.iloc[18:25]

상단 밴드는 'ub'라는 컬럼으로 만들고, 표준 편차를 계산하는 std() 함수로 이동 표준 편차를 구한다.

price_df['db'] = price_df['center'] - 2 * price_df['Adj Close'].rolling(window=20).std()
price_df.iloc[18:25]

볼린저 밴드는 다음에 재활용할 수 있도록 파이썬 함수로 만들어 저장하면 편리하게 사용할 수 있다.

n = 20
sigma = 2
def bollinger_band(price_df, n, sigma):
    bb = price_df.copy()
    bb['center'] = price_df['Adj Close'].rolling(n).mean() # 중앙 이동 평균선
    bb['ub'] = bb['center'] + sigma * price_df['Adj Close'].rolling(n).std() # 상단밴드
    bb['db'] = bb['center'] - sigma * price_df['Adj Close'].rolling(n).std() # 하단밴드
    return bb
    
bollinger = bollinger_band(price_df, n, sigma)

그 다음 기간을 나눠 볼린저 밴드를 활용한 전략의 성과를 확인해 보자.

base_date = '2008-01-02'
sample = bollinger.loc[base_date:]
sample.head()

base_date 변수에 새로운 날짜를 할당하고 설정된 날짜 이후 데이터로 전략 성과를 확인한다. 

평균 회귀 전략에서 진입/청산 신호가 발생할 대 우리가 취하는 행동을 기록할 데이터프레임을 만들어보자. 앞으로 이러한 데이터프레임을 trading book이라고 명명한다. 

다음 book 변수를 만들어 거래내역 컬럼을 만든다.

book = sample[['Adj Close']].copy()
book['trade'] = '' # 거래내역 컬럼
book.head()

위 코드를 함수화 하면 다음과 같다.

def create_trade_book(sample):
    book = sample[['Adj Close']].copy()
    book['trade'] = ''
    return(book)

이번에는 볼린저 밴드를 활용한 전략 알고리즘을 만들어 본다.

반응형

트레이딩 전략 알고리즘에 대한 전체 코드는 다음과 같다.

def tradings(sample, book):
    for i in sample.index:
        if sample.loc[i, 'Adj Close'] > sample.loc[i, 'ub']: # 상단밴드 이탈시 동작 안함
            book.loc[i, 'trade'] = ''
        elif sample.loc[i, 'db'] > sample.loc[i, 'Adj Close']: # 하단밴드 이탈시 매수
            if book.shift(1).loc[i, 'trade'] == 'buy': # 이미 매수 상태라면
                book.loc[i, 'trade'] = 'buy' # 매수상태 유지
            else:
                book.loc[i, 'trade'] = 'buy'
        elif sample.loc[i, 'ub'] >= sample.loc[i, 'Adj Close'] and sample.loc[i, 'Adj Close'] >= sample.loc[i, 'db']: # 볼린저밴드 안에 있을 시
            if book.shift(1).loc[i, 'trade'] == 'buy':
                book.loc[i, 'trade'] = 'buy' # 매수상태 유지
            else:
                book.loc[i, 'trade'] = '' # 동작 안함
    return (book)

이 전략의 특징은 장기 보유보다는 과매도 / 과매수 구간을 포착하는 단기 매매에 더 효과적이라고 해석한다. 

완성된 거래 전략을 수행하면 다음과 같이 거래내역이 기록된 것을 확인할 수 있다. 

book = tradings(sample, book)
book.tail(10)

다음은 트레이딩 book에 적혀있는 거래내역대로 진입/청산 일자에 따른 매수/매도 금액을 바탕으로 수익률을 계산해 보자.

def returns(book): # 손익계산
    rtn = 1.000
    book['return'] = 1
    buy = 0.0
    sell = 0.0
    for i in book.index:
        # long 진입
        if book.loc[i, 'trade'] == 'buy' and book.shift(1).loc[i, 'trade'] == '':
            buy = book.loc[i, 'Adj Close']
            print('진입일 : ', i, 'long 진입가격 : ', buy)
        #long 청산
        elif book.loc[i, 'trade'] == '' and book.shift(1).loc[i, 'trade'] == 'buy':
            sell = book.loc[i, 'Adj Close']
            rtn = (sell-buy) / buy + 1 # 손익 계산
            book.loc[i, 'return'] = rtn
            print('청산일 : ', i, 'long 진입가격 : ', buy, '| long 청산가격 : ', sell, '| return:', round(rtn,4))

        if book.loc[i, 'trade'] == '' : # 제로 포지션
            buy = 0.0
            sell = 0.0

    acc_rtn = 1.0
    for i in book.index:
        rtn = book.loc[i, 'return']
        acc_rtn = acc_rtn * rtn # 누적 수익률 계산
        book.loc[i, 'acc return'] = acc_rtn

    print('Accumulated return : ', round(acc_rtn, 4))
    return (round(acc_rtn, 4))

수익률을 저장한 변수와 book변수에 수익률 컬럼을 만든다.
for-loop문을 돌면서 포지션 여부에 따라 수익률을 계산해 데이터프레임에 저장하고, 최종적으로 누적 수익률을 계산한다.

print(returns(book))

변화 추이를 한눈에 보고 싶다면 누적 수익률을 그래프로 그려보자.

import matplotlib.pylab as plt
book['acc return'].plot()

백테스팅이라는 불리는 과정을 통해 실제 데이터를 가지고 확인을 해봐야 자신이 만든 전략의 신뢰도를 높일 수 있다. 여기서는 간단하게 과거 데이터를 통해 평균 회귀 전략에 대한 기본 개념을 정리해 본 것이다. 따라서, 이 전략을 바로 활용하기는 어렵다고 생각한다. 또한 평균 회귀 전략이 모든 종목에 적합한 것도 아니다.
그렇기 때문에 본인의 투자 철학과 해당 주식에 대한 면밀한 파악을 통해 전략을 수립하고 실행하도록 해야 한다.

반응형
반응형

요즘 부업 또는 본업으로 온라인 유통업을 하는 분들이 많이 늘고 있다.

나도 몇개 제품을 가지고 판매를 시작했는데, 처음에는 광고를 달지 않고 판매를 하려고 했다.

그런데, 정말 노출이 안되는 걸 알게 되었다.

그래서, 네이버 광고에 가입해서 광고를 시작하게 되었다.

네이버 광고 시스템이 첫눈에 확 들어오는 구조가 아니라서, 좀 복잡했다.

일단 만들어서 올려보는게 상책인 거 같아서, 만들어 올려보았다.

 

첫 주문이 발생했다. 일주일에 약 3~5개 주문이 들어왔다. 기분이 좋았다. 

그런데, 2주 정도 지나자 주문이 안들어오는 것이다. 광고 노출도 안되고 있었다. 

원인이 뭘까 찾아보니, 유료로 네이버 광고를 도와주는 시스템들이 눈에 들어왔다.

광고소재별 약 5~10분 주기로 그 소재(키워드) 광고 순위를 확인한 후 광고금액을 자동 증감하는 구조로 되어 있었다.

아, 이래서 내 광고는 노출이 안되었던 거구나.

난 목표순위와 상관없이 언제나 최소 고정금액이었으니, 당연히 노출이 안되었던 것이다.

많은 사람들이 네이버 광고를 도와주는 유료 시스템을 사용하고 있다면, 그 사람들께 노출될 수 밖에 없겠구나...

그런데, 이 유료 시스템이 네이버에서 제공하고 있는 네이버 광고 API를 사용하고 있는 것이다.

그렇다면, 파이썬으로 직접 필요한 부분을 만들어서 사용하면 어떨까 하는 생각이 들었다.

네이버 검색 광고 API

http://naver.github.io/searchad-apidoc/#/guides

 

searchad-apidoc

 

naver.github.io

구성은 AdExtension(확장소재), Adgroup(광고그룹), Ad(광고), Campaign(캠페인), BusinessChannel(비즈니스채널), AdKeyword(광고키워드), LabelRef(라벨 참조), Label(라벨), ManagedKeyword(관리하고 있는 키워드), Target(목표), IpExclusion(차단된 IP), Bizmoney(비즈머니), ManagedCustomerLink(관리하고 있는 고객링크), StatReport(상태보고서), Stat(상태), MasterReport(마스터보고서), RelKwdStat(연관 키워드 정보보기), Estimate(평가)

한 눈에 안들어오는 구성이다. 

전체 구성을 대략적으로 파악하기 위해서 '네이버 검색 광고 API'를 실제 네이버 광고 시스템과 매칭해 보면서 익혀 보는 것이 좋다.

일단, 가장 쉬운 것부터 하기로 했으니, 소스 분석은 나중에 심도있게 쪼개보기로 하자.

지금은 파이썬에서 쉽게 사용할 수 있도록 네이버 검색 광고 API에 접근할 수 있는 라이브러리를 활용해 보도록 하겠다.

깃헙에 올라와 있는 powernad로 쉽게 접근해 보도록 하자.

https://github.com/devkingsejong/python-PowerNad

 

devkingsejong/python-PowerNad

Naver search Ad Lib for Python. Contribute to devkingsejong/python-PowerNad development by creating an account on GitHub.

github.com

  • 먼저 라이브러리를 인스톨한다.

>> pip install powernad

또는 git clone https://github.com/devkingsejong/python-PowerNad.git

  • 그 다음에 라이브러리를 읽어들인다.

>>> from powernad.API.Campaign import *

  • 네이버 광고에서 제공하는 관련 API 정보를 세팅한다.

BASE_URL = 'https://api.naver.com'
API_KEY = '000000000000000000000000000000000000000000000000000000000000000000'
SECRET_KEY = '00000000000000000000000000000000000000000000000000'
CUSTOMER_ID = '0000000'   

  • 캠페인 초기화

>>> c = Campaign(BASE_URL, API_KEY, SECRET_KEY, CUSTOMER_ID)

  • 캠페인 리스트 불러오기

>>> c.get_campaign_list()

  • 연관 키워드 정보 보기

>>> from powernad.API.RelKwdStat import *

  • RelKwdStat 초기화

>>> rel = RelKwdStat(BASE_URL, API_KEY, SECRET_KEY, CUSTOMER_ID)

  • '샴푸' 키워드로 검색해 보기

# 위 결과값은 키워드, 월간검색수(PC, 모바일), 월평균클릭수(PC, 모바일), 월평균클릭률(PC, 모바일), 경쟁정도를 말한다. [0]을 다른 값으로 변경해서 연간키워드에 대한 정보도 습득할 수 있다.

오늘은 여기까지 해보자. 

5월 중에 위 알고리즘에 따라 광고키워드 순위에 따른 광고금액 변경을 할 수 있는 방법을 올려보도록 하겠다.

각자 위 라이브러리를 활용해서, 네이버 광고 API를 잘 이용할 수 있는 노하우를 갖게 되었으면 좋겠다.

반응형
반응형

머신러닝에 있어, 가장 먼저해야 하는 일 중 하나가 데이터 정제(Data Cleaning)입니다.

왜냐하면 바로 모델을 훈련할 수 있는 데이터셋을 확보하는 것이 실제로는 매우 어렵기 때문입니다.

따라서, 결측값(NaN)은 없는지, 이상치(outlier)는 없는지 알아보기 위해 데이터셋을 주의깊게 살펴보아야 합니다. 

값이 큰 열이 있는 경우 정규화를 통한 보정이 필요하기도 합니다. 

이번에는 데이터 정제(Data Cleaning)에 필요한 일반적인 작업에 대해 알아보도록 하겠습니다.

 

결측값(NaN)이 있는 열 정제하기

다음 데이터로 된 NaNDataset.csv 파일이 있다고 가정하겠습니다.

눈으로 봐도 몇개 열에 결측값이 있는 것을 확인할 수 있습니다.

작은 데이터셋에서는, 쉽게 결측값을 찾을 수 있습니다.

하지만, 큰 데이터셋에서는, 눈으로 알아내는 게 거의 불가능할 것입니다.

결측값을 찾아내는 효과적인 방법은 판다스(Pandas) 데이터프레임으로 데이터셋을 로드해서 데이터프레임의 빈 값(NaN) 여부를 확인하기 위해 isnull() 함수를 사용하는 것입니다.

>>> import pandas as pd
>>> df = pd.read_csv('NaNDataset.csv')
>>> df.isnull().sum()

B 컬럼에 두개의 빈 값이 있는 것을 볼 수 있습니다.

판다스(Pandas)에서 빈 값이 포함된 데이터셋을 로드할 때, 빈 필드를 나타내는 NaN을 사용할 것입니다.

다음은 데이터프레임의 결과물입니다.

컬럼의 평균값으로 NaN 대체하기

데이터셋의 NaN(빈 값)을 처리하는 방법 중 하나는 그 빈 값이 위치한 컬럼의 평균값으로 빈 값을 대체 처리하는 것입니다.

다음의 스니펫 코드는 B 컬럼의 모든 빈 값(NaN)을 B 컬럼의 평균값으로 대체합니다.

>>> df.B = df.B.fillna(df.B.mean()) # NaN(빈 값)을 B 컬럼의 평균값으로 대체합니다.
>>> df

열 제거하기

데이터셋에서 빈 값(NaN)을 처리하는 다른 방법은 빈 값이 포함된 열을 제거하는 것입니다.

아래와 같이 dropna() 함수를 사용해서 처리할 수 있습니다.

>>> df = pd.read_csv('NaNDataset.csv')
>>> df = df.dropna()
>>> df

NaN이 포함된 행을 제거한 후에 인덱스 순번이 더 이상 맞지 않는다는 것을 알 수 있습니다.

인덱스를 재설정하고 싶다면, reset_index() 함수를 사용합니다.

>>> df = df.reset_index(drop=True) # 인덱스를 재설정합니다
>>> df

반응형

중복된 열 제거하기

다음 데이터로 된 DuplicateRows.csv 파일이 있다고 가정하겠습니다.

중복된 모든 열을 찾기 위해, 먼저 데이터셋을 데이터프레임에 로드합니다.

그리고, duplicated() 함수를 적용합니다.

>>> df = pd.read_csv('DuplicateRows.csv') # 판다스(Pandas) 라이브러리는 이미 로드했다고 가정합니다.
>>> df.duplicated(keep=False))

어떤 열이 중복되었는지 알려줍니다. 위 예에서는, 인덱스 1, 2, 5, 6열이 중복입니다. 

keep 인수를 사용하면 중복을 표시하는 방법을 지정할 수 있습니다.

  • 기본값은 'first' : 첫번째 나타나는 것을 제외한, 모든 중복이 True로 표시됩니다.
  • 'last' : 마지막으로 나타나는 것을 제외한, 모든 중복이 True로 표시됩니다.
  • False : 모든 중복이 True로 표시됩니다.

만약 keep 인수를 'first'로 설정하면, 다음과 같은 결과물을 보게 될 것입니다:

따라서, 모든 중복 열들을 보고 싶다면, keep 인수를 False로 설정해야 합니다. 

>>> df[df.duplicated(keep=False)]

중복 행을 삭제하려면 drop_duplicates() 함수를 사용할 수 있습니다.

>>> df.drop_duplicates(keep='first', inplace=True) # 처음 열은 그대로 두고, 그 다음 중복 열만 제거합니다.
>>> df

기본적으로, drop_duplicates() 함수는 원본 데이터프레임을 수정하지 않고, 제거된 열이 포함된 데이터프레임을 반환합니다.

만약 원본 데이터프레임을 수정하고 싶다면, inplace 파라미터를 True로 설정해야 합니다.

때로는 데이터셋의 특정 열에서 발견된 중복만 제거하고 싶은 경우가 있습니다.

예를 들어, 3, 4열의 B컬럼은 값이 다르지만, A, C 컬럼은 동일합니다. 이를 기준으로 제거해 보도록 하겠습니다.

>>> df.drop_duplicates(subset=['A', 'C'], keep='last', inplace=True) # A, C 컬럼에 모든 중복을 제거하고, 마지막 열을 남깁니다.
>>> df

컬럼 정규화하기

정규화는 데이터 정리 프로세스 중에 자주 적용되는 기술입니다.

정규화의 목적은 데이터 범위의 숫자 열 값을 변경하여, 값 범위의 차이를 수정하지 않고 공통 척도를 적용하는 것입니다.

일부 알고리즘은 데이터를 올바르게 모델링하기 위해 정규화가 중요합니다. 

예를 들어, 데이터셋 열 중 하나는 0에서 1까지의 값을 포함하고 다른 열은 400,000에서 500,000까지의 값을 가질 수 있습니다. 

두 개의 열을 사용하여 모델을 훈련하면 숫자의 척도가 크게 달라질 수 있습니다. 

정규화를 사용하면 두 열의 값 비율을 제한된 범위로 유지하면서 값의 비율을 유지할 수 있습니다.

판다스(Pandas)에서는 MinMaxScaler 클래스를 사용하여 각 열을 특정 값 범위로 확장할 수 있습니다.

다음 데이터로 된 NormalizeColumns.csv 파일이 있다고 가정하겠습니다.

다음 스니펫 코드는 모든 열의 값을 (0,1) 범위로 조정합니다.

>>> import pandas as pd
>>> from skleran import preprocessing
>>> df = pd.read_csv('NormalizeColumns.csv')
>>> x = df.values.astype(float)
>>> min_max_scaler = preprocessing.MinMaxScaler()
>>> x_scaled = min_max_scaler.fit_transform(x)
>>> df = pd.DataFrame(x_scaled, columns=df.columns)
>>> df

이상치(outlier) 제거하기

통계에서 이상치(이상점, outlier)는 관측된 다른 점들과 먼 지점의 점입니다.

예를 들어, 다음과 같은 값들(234, 267, 1, 200, 245, 300, 199, 250, 8999, 245)이 세트로 주어졌다고 하면, 이 중에서 명백하게 1과 8999는 이상치(oulier)입니다. 

그들은 나머지 값들과 뚜렷이 구별되며, 데이터셋의 대부분의 다른 값들과 달리 "바깥에 위치합니다". 

이상치는 주로 기록 또는 실험 오류의 오류로 인해 발생하며 머신러닝에서는 모델을 학습하기 전에 이상치를 제거해야 합니다. 

그렇지 않으면 모델을 왜곡시킬 수 있습니다.

이상치를 제거하는 데는 여러 가지 기술이 있으며,이번에서는 두 가지를 논의합니다.

  • Tukey Fences
  • Z-Score

Tukey Fences

Tukey Fences는 사분위 범위(IQR, interquartile range)를 기반으로 합니다. 

Q1이라고 표시된 첫번째 사분위수는 데이터셋의 값 중 첫번째 25 %를 보유하는 값입니다.

3분위수(Q3)은 데이터셋의 값 중 3번째의 25 %를 보유하는 값입니다. 

따라서, 정의에 따르면, IQR = Q3-Q1입니다.

아래 그림은 짝수 및 홀수 값을 가진 데이터셋에 대해 IQR을 얻는 방법의 예를 보여줍니다.

Tukey Fences에서 이상치(outlier)는 다음과 같은 값입니다.

  • Q1 - (1.5 * IQR) 미만 or
  • Q3 + (1.5 * IQR) 초과

다음 스니펫 코드는 파이썬을 사용해 Tukey Fences를 실행하는 방법을 보여줍니다.

>>> import numpy as np
>>> def outliers_iqr(data):
              q1, q3 = np.percentile(data, [25, 75])
              iqr = q3 - q1
              lower_bound = q1 - (iqr * 1.5)
              upper_bound = q3 + (iqr * 1.5)
              return np.where((data > upper_bound) | (data < lower_bound))

np.where() 함수는 조건에 만족하는 아이템들의 위치를 반환합니다. 

outliers_iqr() 함수는 첫 번째 요소가 이상치(oulier)값을 갖는 행의 인덱스 배열인 튜플을 반환합니다.

Tukey Fences를 테스트하기 위해, 부모와 자녀의 키에 대해 유명한 Galton 데이터셋을 사용해 보도록 하겠습니다. 

이 데이터셋에는 Francis Galton이 1885년에 실시한 유명한 아동 연구결과를 기반으로 한 데이터가 포함되어 있습니다. 

각각의 경우는 성인용이며 변수는 다음과 같습니다.

  • Family : 자녀가 속한 가족으로서 1에서 204 사이의 숫자와 136A로 표시됩니다.
  • Father : 아빠의 키(인치)
  • Mother : 엄마의 키(인치)
  • Gender : 아이들의 성, 남성(M) 또는 여성(F)
  • Height : 아이들의 키(인치)
  • Kids : 아이들의 가정에서, 아이들의 숫자

이 데이터셋은 898 케이스를 갖고 있습니다. 

먼저 데이터를 읽어들입니다:

>>> import pandas as pd
>>> df = pd.read_csv('http://www.mosaic-web.org/go/datasets/galton.csv')
>>> df.head()

height 컬럼에서 이상치(outlier)를 찾고 싶다면, 다음과 같이 outliers_iqr() 함수를 불러옵니다.

>>> print('Outliers using ourliers_iqr()')
>>> print('=====================')
>>> for i in outliers_iqr(df.height)[0]:
                 print(df[i:i+1])

다음과 같은 결과를 볼 수 있습니다:

Tukey Fences 메서드를 사용해, height 컬럼에 하나의 이상치(outlier)가 있다는 것을 알 수 있었습니다.

Z-Score

이상치(outlier)를 결정하는 두번째 방법은 Z-Score 메서드를 사용하는 것입니다. 

Z-Score는 데이터 포인트가 평균에서 얼마나 많은 표준 편차를 가지는지 나타냅니다.

Z-Score의 공식은 다음과 같습니다.

여기서 xi는 데이터 포인트, μ는 데이터셋의 평균, σ는 표준편차입니다.

  • 음의 Z-Score는 데이터 포인트가 평균보다 작음을 나타내고 양의 Z-Score는 문제의 데이터 포인트가 평균보다 큰 것을 나타냅니다.
  • Z-Score가 0이면 데이터 포인트가 중간(평균)이고 Z-Score가 1이면 데이터 포인트가 평균보다 1 표준편차가 높다는 것을 알 수 있습니다.
  • 3보다 크거나 -3보다 작은 모든 Z-Score는 이상치(outlier)로 간주됩니다.

다음 스니펫 코드는 파이썬을 사용해 Z-Score를 실행하는 방법을 보여줍니다.

>>> def outliers_z_score(data):
           threshold = 3
           mean = np.mean(data)
           std = np.std(data)
           z_scores = [(y - mean) / std for y in data]
           return np.where(np.abs(z_scores) > threshold)

이전에 사용한 것과 동일한 Galton 데이터셋에 대해 outliers_z_score()함수를 사용해 height 컬럼에 대한 이상치(outlier)를 찾을 수 있습니다.

>>> print('Outliers using ourliers_z_score()')
>>> print('=========================')
>>> for i in outliers_z_score(df.height)[0]:
                 print(df[i:i+1])

다음과 같은 결과물을 볼 수 있습니다.

Z- Score 메서드를 사용하면 height 컬럼에 3개의 이상치(outlier)가 있음을 알 수 있습니다.

(Source : Python Machine Learning, Wiley, 2019)

반응형
반응형

Scikit-learn 라이브러리는 파이썬에서 가장 유명한 머신러닝 라이브러리 중 하나로, 분류(classification), 회귀(regression), 군집화(clustering), 의사결정 트리(decision tree) 등의 다양한 머신러닝 알고리즘을 적용할 수 있는 함수들을 제공합니다.

이번에는 머신러닝 수행 방법을 알아보기 전에, 다양한 샘플 데이터를 확보할 수 있는 방법들을 알아보려고 합니다.  

데이터셋(Datasets) 얻기

머신러닝을 시작할 때, 간단하게 데이터셋을 얻어서 알고리즘을 테스트해 보는 것이 머신러닝을 이해하는데 있어 매우 유용합니다. 간단한 데이터셋으로 원리를 이해한 후, 실제 생활에서 얻을 수 있는 더 큰 데이터셋을 가지고 작업하는 것이 좋습니다.

우선 머신러닝을 연습하기 위해, 간단한 데이터셋을 얻을 수 있는 곳은 다음과 같습니다. 하나씩 차례대로 알아보도록 하겠습니다.

  • 사이킷런의 빌트인 데이터셋
  • 캐글(Kaggle) 데이터셋
  • UCI(캘리포니아 대학, 얼바인) 머신러닝 저장소

<사이킷런 데이터셋 사용하기>

사이킷런에는 머신러닝을 쉽게 배울 수 있도록 하기 위해, 샘플 데이터셋을 가지고 있습니다. 

샘플 데이터셋을 로드하기 위해, 데이터셋 모듈을 읽어들입니다. 다음은 Iris 데이터셋을 로드한 코드입니다.

>>> from sklearn import datasets
>>> iris = datasets.load_iris() # 아이리스 꽃 데이터셋 또는 피셔 아이리스 데이터셋은 영국의 통계 학자이자 생물학자인 로널드 피셔 (Ronald Fisher)가 소개한 다변수 데이터셋입니다. 데이터셋은 3종의 아이리스(Iris)로 된 50개 샘플로 구성되어 있습니다. 각 샘플로부터 4개의 피쳐(features:피쳐를 우리말로 변수 또는 요인이라고 표현하기도 함)를 측정할 수 있습니다: 꽃받침과 꽃잎의 길이와 너비입니다. 이러한 4가지 피쳐(features)의 결합을 바탕으로 피셔는 종을 서로 구분할 수 있는 선형 판별 모델을 개발했습니다. 

로드된 데이터셋은 속성-스타일 접근을 제공하는 파이썬 딕셔너리, 번치(bunch) 객체로 표현됩니다. 

>>> print(iris.DESCR) # DESCR 속성을 사용해 데이터셋의 정보를 다음과 같이 얻을 수 있습니다.

>>> print(iris.data) # data 속성을 사용해 피쳐를 알아볼 수 있습니다. 

>>> print(iris.feature_names) # feature_names 속성으로 피쳐 이름을 알아낼 수 있습니다.

이것은 데이터셋이 꽃받침 길이, 꽃받침 너비, 꽃잎 길이, 꽃잎 너비 등 4개의 컬럼들로 구성되어 있다는 것을 의미합니다.

꽃의 꽃잎(petal)과 꽃받침(sepal)

>>> print(iris.target) # 레이블을 알 수 있습니다.

>>> print(iris.target_names) # 레이블 이름을 알 수 있습니다.

여기서 0은 'setosa'를, 1은 'versicolor'를 2는 'virginica'를 나타냅니다.

(사이킷런의 모든 샘플 데이터가 feature_names, target_names 속성을 지원하는 것은 아닙니다)

여기서, 데이터를 쉽게 다루기 위해, 판다스(Pandas)의 데이터프레임으로 변환하는 것이 유용합니다.

>>> import pandas as pd # pandas 라이브러리를 읽어들입니다.
>>> df = pd.DataFrame(iris.data) 
>>> df.head()

<캐글(Kaggle) 데이터셋 사용하기>

 데이터 과학자 및 머신러닝 학습자들에게 있어, 캐글(Kaggle)은 세계에서 가장 큰 커뮤니티입니다.

머신러닝 경쟁을 제공하는 플랫폼에서 시작하여, 현재 캐글(Kaggle)은 공개 데이터 플랫폼과 데이터 과학자를 위한 클라우드 기반 워크 벤치도 제공합니다.

구글이 2017년 3월에 캐글(Kaggle)을 인수했습니다. 

우리는 머신러닝 학습자들을 위해, 캐글(Kaggle)에서 제공된 샘플 데이터셋을 이용할 수 있습니다.

몇가지 흥미로운 데이터셋이 있는데, 다음과 같습니다.

<캘리포니아 대학, 어바인의 머신러닝 저장소 사용하기>

캘리포니아 대학, 어바인의 머신러닝 저장소는 머신러닝 알고리즘의 데이터 생성 경험적 분석을 위해, 머신러닝 커뮤니티에서 사용하는 데이터베이스, 도메인 이론 및 데이터 생성기 모음입니다.

이 거대한 데이터셋 모음 중에서 흥미로운 몇가지 데이터셋을 살펴보면 다음과 같습니다.

< 직접 자신의 데이터셋 생성하기>

실험을 위한 적당한 데이터셋을 찾을 수가 없다면, 직접 자신의 데이터셋을 생성합니다.

사이킷런(Scikit-learn) 라이브러리의 sklearn.datasets.samples_generator 모듈에는 다양한 유형의 문제에 대해 서로 다른 유형의 데이터 세트를 생성할 수 있는 많은 함수가 포함되어 있습니다.

다음과 같은 데이터셋들을 만들 수 있습니다.

  • 선형으로 분산된 데이터셋
  • 군집화된 데이터셋
  • 순환 방식으로 분산되고 군집화된 데이터셋

1. 선형으로 분산된 데이터셋

make_regression() 함수를 사용해 선형으로 분산된 데이타를 생성합니다. 아웃풋에 적용된 가우스 노이즈의 표준 편차뿐만 아니라, 원하는 피쳐의 수를 지정할 수도 있습니다.

>>> % matplolib inline
>>> import matplotlib.pyplot as plt
>>> from sklearn.datasets.samples_generator import make_regression
>>> X, y = make_regression(n_samples=100, n_features=1, noise=5.4)
>>> plt.scatter(X, y)

2. 군집화된 데이터셋

make_blobs()함수는 n개의 무작위 데이터 클러스터를 생성합니다. 이것은 비지도(자율)학습에서 군집화를 수행할 때 매우 유용합니다.

>>> %matplotlib inline
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> from sklearn.datasets import make_blobs
>>> X, y = make_blobs(500, centers=3) # 군집화를 위한 등방성 가우시안 블롭 생성
>>> rgb = np.array(['r', 'g', 'b'])
>>> plt.scatter(X[:, 0], X[:, 1], color=rgb[y]) # 산포도와 컬러 코딩을 사용해 그립니다.

3. 순환 방식으로 분산되고 군집화된 데이터셋

make_circles()함수는 두 개의 차원에 작은 원을 포함하는 큰 원이 포함된 임의의 데이터셋을 생성합니다. SVM(

Support Vector Machines)과 같은 알고리즘을 사용하여 분류(classifications)를 수행할 때 유용합니다.

>>> %matplotlib inline
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> from sklearn.datasets import make_circles
>>> X, y = make_circles(n_samples=100, noise=0.09)
>>> rgb = np.array(['r', 'g', 'b'])
>>> plt.scatter(X[:, 0], X[:, 1], color=rgb[y])

다음 번에는 간단한 선형 회귀(linear regression) 알고리즘을 구현해 보면서 사이킷런(Scikit-Learn) 기초 사용법을 익혀 보겠습니다.

 

(Source : Python Machine Learning, Wiley, 2019)

 

반응형
반응형

이번에는 pandas에서 데이터를 삭제하는 방법에 대해 알아보고자 합니다.

1. pandas, numpy 라이브러리를 불러들입니다.

>>> import pandas as pd
>>> numpy as np

2. 다음 주소에서 데이터셋을 읽어들입니다.

>>> url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data'

3. 읽어들인 데이터셋을 wine 변수에 저장합니다.

>>> wine = pd.read_csv(url)
>>> wine.head()

4. 컬럼들 중에서 첫번째, 네번째, 일곱번째, 아홉번째, 열두번째, 열세번째, 열네번째 컬럼을 삭제합니다.

>>> wine = wine.drop(wine.columns[[0, 3, 6, 8, 11, 12, 13]], axis=1) # drop 메서드를 사용해서, 컬럼을 삭제합니다. axis=1 인수로 열을 삭제한다는 것을 명시합니다.
>>> wine.head()

5. 아래와 같이 열을 지정합니다. 

1) alcohol 2) malic_acid 3) alcalinity_of_ash 4) magnesium 5) flavanoids 6) proanthocyanins 7) hue

>>> wine.columns = ['alcohol', 'malic_acid', 'alcalinity_of_ash', 'magnesium', 'flavanoids', 'proanthocyanins', 'hue']
>>> wine.head()

6. alcohol 컬럼 첫 3행의 값을 NaN으로 설정합니다.

>>> wine.iloc[0:3, 0] = np.nan # iloc 메서드로 첫번째 열(0)부터 세번째 열(2)에, 첫번째 컬럼(0)에 적용합니다. 0부터 시작입니다.

>>> wine.head()

7. magnesium 컬럼의 3, 4행 값을 NaN으로 설정합니다.

>>> wine.iloc[2:4, 3] = np.nan # iloc 메서드로 세번째 열(2)부터 네번째 열(3)에, 세번째 컬럼(3)에 적용합니다. 0부터 시작입니다. 

>>> wine.head()

8. alcohol 컬럼의 NaN 값을 10으로 채우고, magnesium 컬럼의 NaN은 100으로 채웁니다.

>>> wine.alcohol.fillna(10, inplace = True) # fillna 메서드로 NaN 값을 10으로 바꿉니다. inplace=True로 다른 객체를 만드는 게 아니라 기존 객체를 바꿉니다.
>>> wine.magnesium.fillna(100, inplace = True) 
>>> wine.head()

9. 결측 값의 숫자를 합산합니다.

>>> wine.isnull().sum() # 결측 값을 가진 데이터가 없습니다.

10. 10까지의 범위에서 랜덤으로 숫자 배열을 생성합니다.

>>> random = np.random.randint(10, size = 10)
>>> random

11. 생성한 난수를 인덱스로 사용하고 각 셀에 NaN 값을 할당합니다.

>>> wine.alcohol[random] = np.nan
>>> wine.head(10)

12. 얼마나 많은 결측 값이 있는지 확인합니다.

>>> wine.isnull().sum()

13. 결측 값을 가진 행들을 삭제합니다.

>>> wine = wine.dropna(axis=0, how = 'any') # dropna 메서드로 결측값을 찾습니다. axis=0으로 행을 선택하고, how='any'로 하나라도 결측값이 있는 경우를 포함합니다.
>>> wine.head()

14. 인덱스를 재설정합니다. 

>>> wine = wine.reset_index(drop = True)
>>> wine.head()

 

(Source : Pandas exercises 깃헙)

반응형
반응형

1. pandas, numpy, matplotlib, seaborn 등 필요한 라이브러리를 읽어들입니다. 

>>> import pandas as pd
>>> import numpy as np
>>> import matplotlib.pyplot as plt # 파이썬에서 시각화를 처리하는데 필요한 대표적인 라이브러리로 생각하면 됩니다.
>>> import seaborn as sns # matplotlib를 바탕으로 해서 시각화를 더 멋지게 만들어줍니다.
% matplotlib inline # jupyter notebook(쥬피터 노트북) 내에서 그래프를 보여주도록 해줍니다.
sns.set(style='ticks') # 더 나은 스타일로 그래프를 보여주도록 seaborn을 설정합니다.

2. 다음 주소에서 데이터셋을 읽어들입니다.

>>> path = 'https://raw.githubusercontent.com/guipsamora/pandas_exercises/master/07_Visualization/Online_Retail/Online_Retail.csv'

3. online_rt라는 변수에 데이터셋을 할당합니다.

>>> online_rt = pd.read_csv(path, encoding = 'latin1') # encoding 인수로 적합한 언어 인코딩을 적용합니다.
>>> online_rt.head()

4. 영국을 제외하고 수량(Quantity)이 가장 많은 10개 국가에 대한 히스토그램을 만듭니다.

>>> countries = online_rt.groupby('Country').sum() # groupby 메서드를 사용해 국가별 합계를 구하고 countries 변수에 저장합니다.
>>> countries = countries.sort_values(by = 'Quantity', ascending=False) # sort_values 메서드로 정렬합니다. 이때 정렬 기준은 by인수를 사용해 'Quantity'로 정하고, ascending=False를 사용해 내림차순으로 정리합니다.
>>> countries['Quantity'].plot(kind='bar') # 그래프를 만듭니다. 
>>> plt.xlabel('Countries') # x축 라벨을 정합니다.
>>> plt.ylabel('Quantity') # y축 라벨을 정합니다.
>>> plt.title('10 Countries with most orders') # 제목을 정합니다.
>>> plt.show() # 그래프를 보여줍니다.

5. 마이너스 수량은 제외하도록 처리합니다.

>>> online_rt = online_rt[online_rt.Quantity > 0] # 수량이 0보다 큰 경우에만 선택해서 online_rt 변수에 재배정합니다.
>>> online_rt.head()

6. 상위 3개 국가의 고객 ID별 단가별 수량을 사용하여 산점도를 만듭니다.

>>> customers = online_rt.groupby(['CustomerID', 'Country']).sum() # groupby 메서드를 사용해 CustomerID와 Country 컬럼에 대한 합계를 구해 customers 변수에 저장합니다.
>>> customers = customers[customers.UnitPrice > 0] # UnitPrice가 0보다 큰 데이타만 customers 변수에 재할당합니다.
>>> customers['Country'] = customers.index.get_level_values(1) # index.get_level_values() 메서드로 요청한 수준의 인덱스를 반환합니다. 레벨은 0부터 시작합니다.
>>> top_countries = ['Netherlands', 'EIRE', 'Germany']
>>> customers = customers[customers['Country'].isin(top_countries)] # top_countries에서 정한 국가들을 선택하기 위해 데이터 프레임을 필터링합니다.
>>> g = sns.FacetGrid(customers, col='Country') # FaceGrid를 만듭니다. FaceGrid가 만드는 플롯은 흔히 "격자", "격자"또는 "작은 다중 그래픽"이라고 불립니다.
>>> g.map(plt.scatter, 'Quantity', 'UnitPrice', alpha=1) # map 메서드로 각 Facet의 데이터 하위 집합에 플로팅 기능을 적용합니다.
>>> g.add_legend() # 범례를 추가합니다.

7. 앞의 결과가 왜 이렇게 보잘 것 없는지를 조사합니다.

>>> customers = online_rt.groupby(['CustomerID', 'Country']).sum()
>>> customers

7-1. 수량(Quantity)과 단가(UnitPrice)를 별도로 보는 게 의미가 있을까?

>>> display(online_rt[online_rt.CustomerID == 12347.0].sort_values(by='UnitPrice', ascending=False).head())
>>> display(online_rt[online_rt.CustomerID == 12346.0].sort_values(by='UnitPrice', ascending=False).head()) # 고객ID 12346.0번은 다른 나라와 다르게 수량이 매우 많고, 단가는 매우 낮게 나옵니다. 그 이유를 알아볼 필요가 있어서 살펴보고자 합니다. 아래 데이터에서 보듯이 고객 ID 12346.0은 단 한건의 주문으로 대량 주문한 경우가 되겠습니다. 

7-2. 6번 최초의 질문으로 돌아가 보면, '상위 3개 국가의 고객 ID별 단가별 수량을 사용하여 산점도를 만듭니다'에 대해 구체화해서 생각해 봐야 합니다. 총 판매량이냐? 아니면 총 수익으로 계산해야 할 것인가? 먼저 판매량에 따라 분석해 보도록 하겠습니다.

>>> sales_volume = online_rt.groupby('Country').Quantity.sum().sort_values(ascending=False)
>>> top3 = sales_volume.index[1:4] # 영국을 제외합니다.
>>> top3

7-3. 이제 상위 3개국을 알게 되었습니다. 이제 나머지 문제에 집중합니다. 국가 ID는 쉽습니다. groupby 메서드로 'CustomerID'컬럼을 그룹핑하면 됩니다. 'Quantity per UnitPrice'부분이 까다롭습니다. 단순히 Quantity 또는 UnitPrice로 계산하는 것은 원하는 단가별 수량을 얻기 어렵습니다. 따라서, 두개 컬럼을 곱해 수익 'Revenue'라는 컬럼을 만듭니다.

>>> online_rt['Revenue'] = online_rt.Quantity * online_rt.UnitPrice
>>> online_rt.head()

7-4. 각 국가 주문별로 평균 가격(수익/수량)을 구합니다. 

>>> grouped = online_rt[online_rt.Country.isin(top3)].groupby(['CustomerID', 'Country])
>>> plottable = grouped['Quantity', 'Revenue'].agg('sum')
>>> plottable['Country'] = plottable.index.get_level_values(1)
>>> plottable.head()

7-5. 그래프 그리기.

>>> g = sns.FacetGrid(plottable, col='Country') # FaceGrid를 만듭니다. FaceGrid가 만드는 플롯은 흔히 "격자", "격자"또는 "작은 다중 그래픽"이라고 불립니다.
>>> g.map(plt.scatter, 'Quantity', 'AvgPrice', alpha=1) # map 메서드로 각 Facet의 데이터 하위 집합에 플로팅 기능을 적용합니다.
>>> g.add_legend() # 범례를 추가합니다.

7-6. 아직 그래프의 정보가 만족스럽지 못합니다. 많은 데이타가 수량은 50000 미만, 평균 가격은 5 이하인 경우라는 것을 확인할 수 있습니다. 트렌드를 볼 수 있을 것 같은데, 이 3개국 만으로는 부족합니다.

>>> grouped = online_rt.groupby(['CustomerID'])
>>> plottable = grouped['Quantity', 'Revenue'].agg('sum')
>>> plottable['AvgPrice'] = plottable.Revenue / plottable.Quantity
>>> plt.scatter(plottable.Quantity, plottable.AvgPrice)
>>> plt.plot()

7-7. 커브를 명확하게 보기 위해 확대해 봅니다.

>>> grouped = online_rt.groupby(['CustomerID', 'Country'])
>>> plottable = grouped.agg({'Quantity' : 'sum', 'Revenue' : 'sum'})
>>> plottable['AvgPrice'] = plottable.Revenue / plottable.Quantity
>>> plt.scatter(plottable.Quantity, plottable.AvgPrice)
>>> plt.xlim(-40, 2000) # 확대하기 위해 x축을 2000까지만 표시합니다.
>>> plt.ylim(-1, 80) # y축은 80까지만 표시합니다.
>>> plt.plot() # 아래 그래프에서 우리는 인사이트를 볼 수 있습니다. 평균 가격이 높아질수록, 주문 수량은 줄어든다는 것입니다. 

8. 단가 (x) 당 수익 (y)을 보여주는 선형 차트를 그립니다.

8-1. 가격 [0~50]을 간격 1로하여 단가를 그룹화하고 수량과 수익을 그룹핑니다.

>>> price_start = 0
>>> price_end = 50
>>> price_interval = 1
>>> buckets = np.arange(price_start, price_end, price_interval)
>>> revenue_per_price = online_rt.groupby(pd.cut(online_rt.UnitPrice, buckets)).Revenue.sum()
>>> revenue_per_price.head()

8-2. 그래프를 그려봅니다.

>>> revenue_per_price.plot()
>>> plt.xlabel('Unit Price (in intervals of ' +str(price_interval_+')')
>>> plt.ylabel('Revenue')
>>> plt.show()

8-3. 좀 더 멋지게 그려보도록 합니다.

>>> revenue_per_price.plot()
>>> plt.xlabel('Unit Price (in buckets of '+str(price_interval)+')')
>>> plt.ylabel('Revenue')
>>> plt.xticks(np.arange(price_start, price_end, 3), np.arange(price_start, price_end, 3))
>>> plt.yticks(0, 500000, 1000000, 1500000, 2000000, 2500000], ['0', '$0.5M', '$1M', '$1.5M', '$2M', '$2.5M'])
>>> plt.show()

(Source : Pandas exercises 깃헙)

반응형
반응형

판다스(Pandas)의 기능 중 데이터셋으로 기본적인 통계작업을 하는 명령어들을 알아보도록 합니다.

1. pandas 및 datetime 라이브러리를 읽어들입니다. 

>>> import pandas as pd
>>> import datetime

2. data_url 변수에 다음 주소를 저장합니다.

>>> data_url = 'https://raw.githubusercontent.com/guipsamora/pandas_exercises/master/06_Stats/Wind_Stats/wind.data'

3. pandas로 위 주소를 data 변수로 읽어들이되, 처음 3개 컬럼을 datetime 인덱스로 대체합니다.

>>> data = pd.read_table(data_url, sep = '\s+', parse_dates = [[0, 1, 2]]) # parse_dates 인수는 불린값 또는 숫자 리스트 또는 여러 컬럼들 값을 읽어들여서 날짜로 처리할 수 있습니다. 여기서는 맨 앞에 3개의 컬럼 값으로 날짜를 인식하도록 합니다.
>>> data.head() # 데이터셋의 구조를 알아보기 위해 상위 열 5개를 조회합니다.

4. 년도가 2061? 이 년도의 데이터가 실제로 있을까요? 이것을 수정해서 적용할 수 있는 함수를 만들어 봅시다.

>>> def fix_century(x):
               year = x.year - 100 if x.year > 1989 else x.year
               return datetime.date(year, x.month, x.day)      # 년도 데이터를 1900년대와 2000년대 구분을 위해 1989보다 많은 경우에는 1900년대으로 변경하도록 if문으로 간단한 함수를 만듭니다.
>>> data['Yr_Mo_Dy'] = data['Yr_Mo_Dy'].apply(fix_century) # apply 메서드를 사용해 'Yr_Mo_Dy'] 컬럼 전체에 함수를 적용합니다. 
>>> data.head() # 다시 데이터셋의 구조를 알아보기 위해 상위 열 5개를 조회합니다.

5. 날짜를 인덱스로 설정합니다. 이때 데이터 타입을 변경해서 적용해야 합니다. 데이터 타입은 datetime64[ns]여야 합니다.

>>> data['Yr_Mo_Dr'] = pd.to_datetime(data['Yr_Mo_Dy']) # 'Yr_Mo_Dy' 컬럼의 데이터 타입을 datetime64[ns]로 변경합니다.
>>> data = data.set_index('Yr_Mo_Dy') # set_index 메서드를 사용해 'Yr_Mo_Dy' 컬럼을 인덱스로 설정합니다.
>>> data.head()

6. 전체 데이터 값의 각 컬럼별 누락된 값을 알아봅니다. 

>>> data.isnull().sum() # 각 컬럼 값들 중에서 결측 값(Null)이 있는 곳의 갯수를 확인합니다.

7. 결측 값을 가진 데이터를 제외한 전체 데이터를 계산해 봅니다.

>>> data.shape[0] - data.isnull().sum()

또는

>>> data.notnull().sum()

8. 모든 위치와 전체 시간의 풍속에 대한 평균 값을 계산합니다.

>>> data.fillna(0).values.flatten().mean() # 결측값을 fillna(0)를 사용해 0으로 대체합니다. 그 후 dataframe을 list로 변경하는 flatten() 메서드를 사용한 후, 평균 값을 계산합니다.
>>> 10.223864592840483

9.  data의 각종 통계 수치를 한눈에 볼 수 있도록 표시해 봅니다.

>>> data.describe(percentiles=[])

10. day_stats라는 데이터 프레임을 만들고 모든 위치 및 전체 시간에 대한 풍속의 최소, 최대 및 평균 풍속 및 표준편차를 계산합니다.

>>> day_stats = pd.DataFrame()
>>> day_stats['min'] = data.min(axis = 1) # 최소값
>>> day_stats['max'] = data.max(axis = 1) # 최대값
>>> day_stats['mean'] = data.mean(axis = 1) # 평균
>>> day_stats['std'] = data.std(axis = 1) # 표준편차

11. 각 위치에 대한 1월의 평균 풍속을 찾아 봅니다.

>>> data.loc[data.index.month == 1].mean()

12. 연간 빈도로 각 위치별로 레코드를 다운 샘플링합니다.

>>> data.groupby(data.index.to_period('A')).mean()

13. 월간 빈도로 각 위치별로 레코드를 다운 샘플링합니다.

>>> data.groupby(data.index.to_period('M')).mean()

14. 주간 빈도로 각 위치별로 레코드를 다운 샘플링합니다.

>>> data.groupby(data.index.to_period('W')).mean()

15. 처음 52주 동안, 매주 모든 지역 풍속의 최소, 최대 및 평균 풍속 및 표준 편차를 계산합니다.

>>> weekly = data.resample('W').agg(['min', 'max', 'mean', 'std']) # 데이터를 'W(주)'에 재샘플링하고 agg 메서드를 사용해 최소, 최대, 평균, 표준편차 함수를 적용합니다.
>>> weekly.loc[weekly.index[1:53], 'RPT':'MAL'].head(10) # 처음 52주 동안의 값들 중 상위 10개 데이터 열을 보여줍니다.

(Source : Pandas exercises 깃헙)

반응형

+ Recent posts