[NewsSentiment] News Sentiment Analysis Part4: Daily Grouping


개요

기사별 News Sentiment Analysis 결과를 날짜별로 집계합니다.

필요한 라이브러리를 가져옵니다.

import pandas as pd
import pandas_datareader.data as web
import re
import sqlite3
import datetime
from datetime import datetime
import numpy as np
import math
import time
import calendar
import json
import plotly
import plotly.graph_objects as go
from plotly.subplots import make_subplots
con = sqlite3.connect('news_sentiment.db')
news_sentiment_data = pd.read_sql('select publish_date, sent_score_adj from news_sentiment', con)
con.close()

날짜 - sentiment 형태로 만들기를 원하는데, 날짜 형식은 2021-04-19와 같은 형태를 원합니다. 저런 모양으로 날짜가 적혀 있는 기사들도 있지만, Mon, 19 Apr 2021과 같은 형태로 적힌 경우도 있어 조정이 필요합니다.

def MonthCharToNum(month):
    if month == 'Jan':
        return '01'
    if month == 'Feb':
        return '02'
    if month == 'Mar':
        return '03'
    if month == 'Apr':
        return '04'
    if month == 'May':
        return '05'
    if month == 'Jun':
        return '06'
    if month == 'Jul':
        return '07'
    if month == 'Aug':
        return '08'
    if month == 'Sep':
        return '09'
    if month == 'Oct':
        return '10'
    if month == 'Nov':
        return '11'
    if month == 'Dec':
        return '12'

제가 가져오는 뉴스 소스에서의 날짜 형태는 2021-04-12T10:11:31Z, Fri, 9 Apr 2021 03:37:42, Fri, 09 Apr 2021 03:37:42 세 가지 형태입니다. 3가지 모두 2021-04-19와 같은 모양으로 만듭니다.

형식 1은 앞 4글자가 연도니까 앞 4글자를 잘라서 숫자만 있는지 정규식으로 체크합니다. 맞으면 앞 10자를 잘라오면 원하는 형태가 되니 그렇게 합니다. 형식 2는 요일을 나타내는 알파벳 3개 후 쉼표 공백 숫자 공백의 형태입니다. 앞 7글자가 형태에 맞으면 가공해 줍니다. 형식 3은 형식 2와 거의 비슷한데 알파벳 3개 후 쉼표 공백 숫자 숫자 공백의 형태라는 점만 다릅니다.

## 날짜 가공
# format 1: 2021-04-12T10:11:31Z
# format 2: Fri, 9 Apr 2021 03:37:42
# format 3: Fri, 09 Apr 2021 03:37:42 

# 숫자만 나오도록 해서 형식 1 체크
ptype1 = re.compile('[0-9]+')
# 알파벳 3개 - 아무거나(쉼표인데 아무거나로 합시다) - 공백 - 숫자 1개 - 공백
ptype2 = re.compile('[a-zA-Z+][\s\S]*[\s][0-9][\s]')
# 알파벳 3개 - 아무거나(쉼표인데 아무거나로 합시다) - 공백 - 숫자 2개 - 공백
ptype3 = re.compile('[a-zA-z+][\s\S]*[\s][0-9][0-9][\s]')

for i in range(len(news_sentiment_data)):
    target = news_sentiment_data.iloc[i,0]
    
    # 맨 앞 4글자가 숫자(연도)인 format 1 (2021-04-12T10:11:31Z)
    m1 = ptype1.match(target[:4])
    # 맨 앞 7글자가 Fri, 9 (Fri 쉼표 공백 숫자 공백) 형태
    m2 = ptype2.match(target[:7])
    # 맨 앞 8글자가 Fri, 09 (Fri 쉼표 공백 숫자 숫자 공백) 형태
    m3 = ptype3.match(target[:8])
    
    if m1:
        # convert format 1 to YYYY-MM-DD string
        news_sentiment_data.iloc[i,0] = target[:10]
    
    if m2:
        # convert format 2 to YYYY-MM-DD string
        news_sentiment_data.iloc[i,0]=target[11:15]+'-'+MonthCharToNum(target[7:10])+'-'+'0'+target[5]
    
    if m3:
        # convert format 3 to YYYY-MM-DD string
        news_sentiment_data.iloc[i,0]=target[12:16]+'-'+MonthCharToNum(target[8:11])+'-'+target[5:7]

날짜별 Sentiment는 날짜별 기사들의 sentiment 평균으로 합니다. 5, 20, 60, 180, 360일 이동 평균을 구하는데, 데이터 확보 문제로 지금은 5일 이동 평균만 유효합니다.

news_sentiment_data = news_sentiment_data.astype({'sent_score_adj' : 'float'})
periodic_score = news_sentiment_data.groupby(['publish_date'], as_index=False).mean()
periodic_score['sent_score_adj_avg5'] = periodic_score['sent_score_adj'].ewm(span=5).mean()
periodic_score['sent_score_adj_avg20'] = periodic_score['sent_score_adj'].ewm(span=20).mean()
periodic_score['sent_score_adj_avg60'] = periodic_score['sent_score_adj'].ewm(span=60).mean()
periodic_score['sent_score_adj_avg180'] = periodic_score['sent_score_adj'].ewm(span=180).mean()
periodic_score['sent_score_adj_avg360'] = periodic_score['sent_score_adj'].ewm(span=360).mean()

일자별 Sentiment 데이터를 보관합니다.

con = sqlite3.connect('news_periodic_score.db')
periodic_score.to_sql('news_periodic_score', con, if_exists='replace', index_label='id')
con.close()

4월 9일 데이터부터 사용 가능하니 S&P 500 지수 종가 데이터도 그렇게 가져옵니다. 주말, 공휴일 등은 직전 거래일 값으로 채워줍니다.

# start보다 1거래일 전까지 가져옴
start = '2021-04-09'
#end = datetime.today().strftime('%Y-%m-%d')
# 날짜 지정 가능
end = '2021-04-19'

sp500 = web.DataReader('^GSPC', 'yahoo', start, end)['Adj Close'].to_frame("S&P 500 Close")
sp500 = sp500.loc[start:]

# 빈 날짜를 직전 거래일 값으로 채워야 됨
sp500_adj = sp500.resample('D').first()
sp500_adj = sp500_adj.fillna(method='pad')

그림을 그려보기 위해 데이터를 잘라옵니다. 데이터가 확보되면 1일 sentiment와 S&P 500 종가, 5, 20, 60일 이동 평균과 S&P 500 종가로 2개 그림을 그릴 것입니다.

# 그림 그려야 됨
sp500_adj['Sentiment_1D'] = list(periodic_score['sent_score_adj'].tail(len(sp500_adj)))
sp500_adj['Sentiment_5D'] = list(periodic_score['sent_score_adj_avg5'].tail(len(sp500_adj)))
sp500_adj['Sentiment_20D'] = list(periodic_score['sent_score_adj_avg20'].tail(len(sp500_adj)))
sp500_adj['Sentiment_60D'] = list(periodic_score['sent_score_adj_avg60'].tail(len(sp500_adj)))

# secondary y-axis
fig = make_subplots(specs=[[{'secondary_y': True}]])

fig.add_trace(go.Scatter(x=list(sp500_adj.index), y=sp500_adj['Sentiment_1D'], name='News Sentiment 1D'), secondary_y=False,)
fig.add_trace(go.Scatter(x=list(sp500_adj.index), y=sp500_adj['S&P 500 Close'], name='S&P 500 Close'), secondary_y=True,)
fig.update_layout(title_text='News Sentiment and S&P 500 Index Close')
fig.update_xaxes(title_text='Date')
fig.update_yaxes(title_text="News Sentiment", secondary_y=False)
fig.update_yaxes(title_text="S&P 500 Close", secondary_y=True)

fig.show()

화면 캡처 2021-04-18 205615

# start보다 1거래일 전까지 가져옴
# 5일 이평 데이터가 4월 13일부터 정상임
start = '2021-04-13'
#end = datetime.today().strftime('%Y-%m-%d')
# 날짜 조정 가능
end = '2021-04-19'

sp500 = web.DataReader('^GSPC', 'yahoo', start, end)['Adj Close'].to_frame("S&P 500 Close")
sp500 = sp500.loc[start:]

# 빈 날짜를 직전 거래일 값으로 채워야 됨
sp500_adj = sp500.resample('D').first()
sp500_adj = sp500_adj.fillna(method='pad')
sp500_adj
# 그림 그려야 됨
sp500_adj['Sentiment_1D'] = list(periodic_score['sent_score_adj'].tail(len(sp500_adj)))
sp500_adj['Sentiment_5D'] = list(periodic_score['sent_score_adj_avg5'].tail(len(sp500_adj)))
sp500_adj['Sentiment_20D'] = list(periodic_score['sent_score_adj_avg20'].tail(len(sp500_adj)))
sp500_adj['Sentiment_60D'] = list(periodic_score['sent_score_adj_avg60'].tail(len(sp500_adj)))

# secondary y-axis
fig = make_subplots(specs=[[{'secondary_y': True}]])

fig.add_trace(go.Scatter(x=list(sp500_adj.index), y=sp500_adj['Sentiment_5D'], name='News Sentiment 5D'), secondary_y=False,)
#fig.add_trace(go.Scatter(x=list(sp500_adj.index), y=sp500_adj['Sentiment_20D'], name='News Sentiment 20D'), secondary_y=False,)
#fig.add_trace(go.Scatter(x=list(sp500_adj.index), y=sp500_adj['Sentiment_60D'], name='News Sentiment 60D'), secondary_y=False,)
fig.add_trace(go.Scatter(x=list(sp500_adj.index), y=sp500_adj['S&P 500 Close'], name='S&P 500 Close'), secondary_y=True,)
fig.update_layout(title_text='News Sentiment Moving Average and S&P 500 Index Close')
fig.update_xaxes(title_text='Date')
fig.update_yaxes(title_text="News Sentiment", secondary_y=False)
fig.update_yaxes(title_text="S&P 500 Close", secondary_y=True)

fig.show()

화면 캡처 2021-04-18 205644




© 2021.03. by JacobJinwonLee

Powered by theorydb