1.本投稿の目的
このブログはAidemy Premiumのカリキュラムの一環で、受講修了条件を満たすために公開しています。
今年2024年4月より、Pythonの文法のイロハから機械学習のモデル・ディープラーニングの手法など、データサイエンスの基礎を学習しております。駆け足で学習してきましたが、ひとまず形にするところまで来ています。
今回はモデルの精度はさておき、一番簡単な線形回帰を使って簡単な機械学習に挑戦します!
2.自己紹介
簡単に自己紹介させていただきますと、都内で金融機関に勤める30代の普通のサラリーマンです。
私自身は高校3年までは数学ⅢCまで学び、一応”理系”だったわけですが、その後の大学ではいわゆる文転し、文系の大学を卒業しました。(理系の大学も受かっていたのになぜそっちに行かなかったのか、、、汗)
よって、数学は全然得意じゃないし、プログラミングも業務で簡単なエクセルVBAをちょこっと書く程度。
統計も概念だけを”なんとなく”理解している、そんなレベルです。
そんなことを言い訳にしてプログラミングから逃げ続けてきたのですが、やはり自分の中でイノベーションを起こしたくPythonに挑戦してみることにしました!
3.今回の分析テーマ
私自身金融機関に勤めているということもあり、株価予測に挑戦したいと思います。
とはいえ株価予測の変数は膨大すぎますし、私自身機械学習モデルの勉強を進めれば進めるほど、その難しさを痛感しています。
よって今回は機械学習モデルの基本的なものを使って「こんなことができるんだ~」というレベルにとどめます。
そして、Aidemyの講座ではアプリ開発も学ぶことができるので、私としては「効率よく株価データ等を収集し、現状のデータを素早くきれいに可視化する」を最終目標にしたいと思います。
その可視化されたデータを眺めて、それを基に自分なりに考えたほうが、今のところは株価を正しく予測できると思っています。(いつ追い越されることやら)
では、なぜそれを今回のテーマにしないのか?といいますと、私自身が特に深いことを考えずにデータ分析コースを選択したため、一応は機械学習モデルの成果物を作らなければいけないという事情があります(笑)
従いまして、本分析は株価予測の精緻さを示すものではないことを予めお伝えしておきます。
4.今回の分析の流れ
今回はNVIDIA(以下NVDA)の翌日の株価予測に挑戦します。
NVIDIAだけのデータだけでは面白みがないため、SOX(フィラデルフィア半導体)指数の株価データ、TSMC(台湾積体電路製造股份有限公司、NVIDIAが設計した半導体を実際に工場で作っている会社です)の株価データも追加して検証してみます。
最終的にNVIDIAだけのデータを使って株価を予測するのが良いのか、SOXやTSMCのデータも加えたほうが精度が改善するのか比べてみます。
機械学習の手法は、線形回帰(重回帰分析)、ラッソ回帰、リッジ回帰、ElasticNet回帰を取り扱います。
【参考】SOX指数 (ソックスしすう)
SOX指数は米国のフィラデルフィア証券取引所が算出、公表している株価指数で、別名「フィラデルフィア半導体指数」とも呼ばれています。半導体の製造や流通、販売等を手がける30銘柄で構成されています。半導体企業の株価が上昇するとSOX指数も高くなり、一般的に世の中の景気が良いと判断されます。
https://www.smbcnikko.co.jp/terms/eng/s/E0113.html
【参考】TSMC(Wikipedia)
https://ja.wikipedia.org/wiki/%E5%8F%B0%E6%B9%BE%E7%A9%8D%E4%BD%93%E9%9B%BB%E8%B7%AF%E8%A3%BD%E9%80%A0
今回の開発環境です。
(OS、エディター)
Windows11
Google Colaboratory
(使用言語)
python
(モデル)
線形回帰
ラッソ回帰
リッジ回帰
ElasticNet回帰
5.具体的なコード
早速ですが今回作ったモデルのご紹介に入ります。
まず最初にライブラリのインポートと必要なデータを取得します。
NVIDIAの株価のティッカーシンボルがNVDAのため、以下NVDAと表示します。
同様にSOX指数をSOX、TSMCをTSMと表示します。
# ライブラリのインポート
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
import plotly.graph_objs as go
from plotly.subplots import make_subplots
データはYahoo! finance USからcsvファイルにて取得しています。(スクレイピングなどができるとカッコイイですが、今回は愚直にダウンロードし所定のフォルダに保存します。Pandasのdatareaderを使うこともできそうですね)
https://finance.yahoo.com/
# GoogleドライブのMydrive内に保存したcsvファイルをPandasのデータフレームに格納
df_nvda = pd.read_csv("/content/drive/MyDrive/NVDA.csv")
# ’Date’カラムを編集しやすくするためにデータタイム型に変換
df_nvda['Date'] = pd.to_datetime(df_nvda['Date'])
# 上記委のNVDAと全く同様の処理
df_sox = pd.read_csv("/content/drive/MyDrive/SOX.csv")
df_sox['Date'] = pd.to_datetime(df_sox['Date'])
df_tsm = pd.read_csv("/content/drive/MyDrive/TSM.csv")
df_tsm['Date'] = pd.to_datetime(df_tsm['Date'])
NVDAとTSMCのデータフレームを結合する作業を行います。
#外部結合したデータフレームをmerged_dfに格納
#カラムデータの名前に同一のものがあるため、_nvda、_tsmとつけて区別できるようにする
merged_df = pd.merge(df_nvda, df_tsm, on ='Date', how='outer', suffixes=['_nvda', '_tsm'])
#merged_dfにさらにSOX指数のデータも外部結合させます
merged_df_v2 = pd.merge(merged_df, df_sox, on = 'Date', how = 'outer', suffixes=['','_sox'])
#またdfに戻します(merged_dfと入力するのが面倒なため)
df = merged_df_v2.sort_values('Date')
カラム名を確認します。
↓一回目の結合までは引数suffixesがワークしてnvda、tsmとついたカラム名に変更ができるのですが、二回目の結合時にどうしてもうまくカラム名変更ができなかったため、力業でカラム名を変更します。
df.columns
Index(['Date', 'Open_nvda', 'High_nvda', 'Low_nvda', 'Close_nvda',
'Adj Close_nvda', 'Volume_nvda', 'Open_tsm', 'High_tsm', 'Low_tsm',
'Close_tsm', 'Adj Close_tsm', 'Volume_tsm', 'Open', 'High', 'Low',
'Close', 'Adj Close', 'Volume'],
dtype='object')
df = df.rename(columns={'Open':'Open_sox',
'High':'High_sox',
'Low':'Low_sox',
'Close':'Close_sox',
'Adj Close':'Adj Close_sox',
'Volume':'Volume_sox'})
続いて、結合したので欠損値があるかの確認を行います。
merged_df.isnull().sum()
Date 0
Open_nvda 1194
High_nvda 1194
Low_nvda 1194
Close_nvda 1194
Adj Close_nvda 1194
Volume_nvda 1194
Open_tsm 869
High_tsm 869
Low_tsm 869
Close_tsm 869
Adj Close_tsm 869
Volume_tsm 869
Open 0
High 0
Low 0
Close 0
Adj Close 0
Volume 0
dtype: int64
十分にデータがあるため、欠損値のある行はすべてdropna()で削除します
df = df.dropna()
df.isnull().sum()
Date 0
Open_nvda 0
High_nvda 0
Low_nvda 0
Close_nvda 0
Adj Close_nvda 0
Volume_nvda 0
Open_tsm 0
High_tsm 0
Low_tsm 0
Close_tsm 0
Adj Close_tsm 0
Volume_tsm 0
Open 0
High 0
Low 0
Close 0
Adj Close 0
Volume 0
dtype: int64
上記の通り欠損値が削除されました。
データの中身をチェックします。特に問題なさそうです。
merged_df.info()
<class 'pandas.core.frame.DataFrame'>
Index: 7708 entries, 6839 to 6838
Data columns (total 19 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Date 7708 non-null datetime64[ns]
1 Open_nvda 6514 non-null float64
2 High_nvda 6514 non-null float64
3 Low_nvda 6514 non-null float64
4 Close_nvda 6514 non-null float64
5 Adj Close_nvda 6514 non-null float64
6 Volume_nvda 6514 non-null float64
7 Open_tsm 6839 non-null float64
8 High_tsm 6839 non-null float64
9 Low_tsm 6839 non-null float64
10 Close_tsm 6839 non-null float64
11 Adj Close_tsm 6839 non-null float64
12 Volume_tsm 6839 non-null float64
13 Open 7708 non-null float64
14 High 7708 non-null float64
15 Low 7708 non-null float64
16 Close 7708 non-null float64
17 Adj Close 7708 non-null float64
18 Volume 7708 non-null int64
dtypes: datetime64[ns](1), float64(17), int64(1)
memory usage: 1.2 MB
ここから格納したdfの可視化作業を行います。
ひとまずNVDA、SOX、TSMCのデータが揃っている期間について株価の推移を見てみることにします。(SOX、TSMCも同様の手順のためコードは割愛します)
# 2000/1/1 - 2024/6/7 の期間を指定
start_date = '2000-01-01'
end_date = '2024-06-07'
mask = (df['Date'] >= start_date) & (df['Date'] <= end_date)
df = df.loc[mask]
df = df.sort_values(by='Date')
# NVDA株価の時系列データ
fig = make_subplots(rows=1, cols=1)
# Add trace for Close Price
fig.add_trace(
go.Scatter(x=df['Date'], y=df['Adj Close_nvda'], mode='lines', name='NVIDIA Close Price', line=dict(color='blue')),
)
# Update layout
fig.update_layout(
title='NVDAの株価推移',
xaxis_title='Date',
yaxis_title='Close Price',
showlegend=True,
)
# Show the plot
fig.show()
株価推移を見ると、NVDA、SOX、TSMの間にはコロナショック後に相関性が高まっているような感じに見えました。
そのため(コロナショック真っ只中の2020年はデータから除外し)2021年以降の関連性について調べてみることにします。
# 2000/1/1 - 2024/6/7 の期間を指定
new_start_date = '2021-01-01'
end_date = '2024-06-07'
mask = (df['Date'] >= new_start_date) & (df['Date'] <= end_date)
df_new = df.loc[mask]
df_new
ここで、説明変数を追加します
NVDAの移動平均株価を短期5日間、中期10日間、長期15日間の3種類を追加します。
移動平均にはPandasのRollingメソッドを使用しました。
#移動平均を追加
SMA1 = 5 #短期5日
SMA2 = 10 #中期10日
SMA3 = 25 #長期15日
df_new['SMA1'] = df_new['Adj Close_nvda'].rolling(SMA1).mean() #短期移動平均の算出
df_new['SMA2'] = df_new['Adj Close_nvda'].rolling(SMA2).mean() #中期移動平均の算出
df_new['SMA3'] = df_new['Adj Close_nvda'].rolling(SMA3).mean() #長期移動平均の算出
# 特徴量を描画して確認
plt.figure(figsize=(15, 6))
plt.plot(df_new['Adj Close_nvda'], label='Close', color='orange')
plt.plot(df_new['SMA1'], label='SMA1', color='red')
plt.plot(df_new['SMA2'], label='SMA2', color='blue')
plt.plot(df_new['SMA3'], label='SMA3', color='green')
plt.xlabel('Date')
plt.ylabel('USD')
plt.legend()
plt.show()
もう少し説明変数を加えてみます。
①始値と終値の差分を計算。
②前日終値との差分'Close_diff'を追加。(diffメソッド)。
③最後に、目的変数の翌日終値'Close_next'を追加。shiftメソッドで1日前にずらし、翌日の終値を計算。
前日終値との差分'Close_diff'は初めのデータが、翌日の終値'Close_next'は最後のデータが欠損値となっているため、欠損値の処理を行います
# OpenとCloseの差分を実体Bodyとして計算
df_new['Body'] = df_new['Open_tsm'] - df_new['Adj Close_nvda']
# 前日終値との差分Close_diffを計算
df_new['Close_diff'] = df_new['Adj Close_nvda'].diff(1)
# 目的変数となる翌日の終値Close_nextの追加
df_new['Close_next'] = df_new['Adj Close_nvda'].shift(-1)
df_new
一部欠損値が出ています。(移動平均の期間に満たない部分(SMA1、SMA2、SMA3)、前日終値との差分の初日(Close_diff)、翌日の終値(Close_Next))
df_new = df_new.dropna()
df_new
データの準備ができ始めてきたので線形回帰の各モデルをインポートしていきます。
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import Lasso
from sklearn.linear_model import Ridge
from sklearn.linear_model import ElasticNet
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
models = {
'Linear Regression(線形回帰)': LinearRegression(),
'Lasso(ラッソ回帰)': Lasso(),
'Ridge(リッジ回帰)': Ridge(),
'Elastic(ElasticNet回帰)': ElasticNet()
}
それぞれの回帰の説明
線形回帰(重回帰分析)は予測したいデータ1つに対して複数のデータを用いて分析する手法です。
一方で、回帰分析は過去のデータから学習して未来の予測に役立てるわけですが、その弱点として過去のデータを適合しすぎて”過学習”を起こしてしまうという点が挙げられます。
それを和らげるアプローチを”汎化”と呼び、ラッソ回帰、リッジ回帰、ElasticNet回帰などがあるようですので、それらも同時に計算します。
続いて学習器に入力するデータを作成していきます。
df_new.columns
Index(['Open_nvda',
'High_nvda',
'Low_nvda',
'Close_nvda',
'Adj Close_nvda',
'Volume_nvda',
'Open_tsm',
'High_tsm',
'Low_tsm',
'Close_tsm',
'Adj Close_tsm',
'Volume_tsm',
'Open_sox',
'High_sox',
'Low_sox',
'Close_sox',
'Adj Close_sox',
'Volume_sox',
'SMA1',
'SMA2',
'SMA3',
'Body',
'Close_diff',
'Close_next'],
dtype='object')
学習器に入力するデータを編集していきます。
本ブログでは二つの分析を行っていきます。
①まず一つ目は、NVDAの株価データ、NVDAに関連するデータのみを使って、学習モデルに学習させて精度を確認します。
②二つ目は、これまで準備してきたNVDA、SOX、TSMのデータを全て使用して、同様に精度を確認します。
まずはこのデータフレームの中からNVDAに関する説明変数のみを取り出します。
X = df_new.drop(['Open_tsm',
'High_tsm',
'Low_tsm',
'Close_tsm',
'Adj Close_tsm',
'Volume_tsm',
'Open_sox',
'High_sox',
'Low_sox',
'Close_sox',
'Adj Close_sox',
'Volume_sox',
'Volume',
'Close_next'],axis=1)
y = df_new['Close_next']
#2023年末までを訓練データ、2024年以降をテストデータとして格納
X_train = X.loc[:'2023-12-30']
X_test = X.loc['2024-01-02':]
y_train = y.loc[:'2023-12-30']
y_test = y.loc['2024-01-02':]
学習器に訓練データとテストデータを入力し、モデルを評価します。
for name, model in models.items():
# Train the model
model.fit(X_train, y_train)
# Make predictions
y_pred = model.predict(X_test)
# Evaluate the model
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print(f'{name}:')
print(f'Mean Squared Error (MSE): {mse}')
print(f'Root Mean Squared Error (RMSE): {rmse}')
print(f'Mean Absolute Error (MAE): {mae}')
print(f'R-squared (R2): {r2}')
print('------------------------------------')
それぞれの評価モデルについては下記で一覧で説明されており、分かりやすいです。
NRIさんの動画が分かりやすかったです。
Linear Regression:
Mean Squared Error (MSE): 778.4749427670104
Root Mean Squared Error (RMSE): 27.90116382459718
Mean Absolute Error (MAE): 19.936868429009948
R-squared (R2): 0.97109414936768
------------------------------------
Lasso:
Mean Squared Error (MSE): 788.7933598751241
Root Mean Squared Error (RMSE): 28.085465277882154
Mean Absolute Error (MAE): 20.11351348330656
R-squared (R2): 0.9707110122783487
------------------------------------
Ridge:
Mean Squared Error (MSE): 779.0741734625585
Root Mean Squared Error (RMSE): 27.911900212320884
Mean Absolute Error (MAE): 19.951542564332666
R-squared (R2): 0.9710718991036982
------------------------------------
Elastic:
Mean Squared Error (MSE): 789.4092905495859
Root Mean Squared Error (RMSE): 28.096428430488917
Mean Absolute Error (MAE): 20.068403901451674
R-squared (R2): 0.9706881419210671
------------------------------------
決定係数(R2)はいずれのモデルも0.97越え、当てはまりは非常に良い結果となりました。
それでは次にNVIDIA、SOX、TSMC全ての株価データも追加して予測モデルに学習させます。
#df_newからSOX指数の出来高(Volume_sox)を除外(SOX指数の出来高はゼロなので)、同時に目的変数も除外
X = df_new.drop(['Volume_sox','Close_next'],axis=1)
y = df_new['Close_next']
#2023年末までを訓練データ、2024年以降をテストデータとして格納
X_train = X.loc[:'2023-12-30']
X_test = X.loc['2024-01-02':]
y_train = y.loc[:'2023-12-30']
y_test = y.loc['2024-01-02':]
for name, model in models.items():
# Train the model
model.fit(X_train, y_train)
# Make predictions
y_pred = model.predict(X_test)
# Evaluate the model
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print(f'{name}:')
print(f'Mean Squared Error (MSE): {mse}')
print(f'Root Mean Squared Error (RMSE): {rmse}')
print(f'Mean Absolute Error (MAE): {mae}')
print(f'R-squared (R2): {r2}')
print('------------------------------------')
Linear Regression:
Mean Squared Error (MSE): 766.3168210756977
Root Mean Squared Error (RMSE): 27.682428019877477
Mean Absolute Error (MAE): 19.922013528693352
R-squared (R2): 0.9726927820214478
------------------------------------
Lasso:
Mean Squared Error (MSE): 783.5611343436225
Root Mean Squared Error (RMSE): 27.992162016243448
Mean Absolute Error (MAE): 20.15376943680406
R-squared (R2): 0.9720782917631802
------------------------------------
Ridge:
Mean Squared Error (MSE): 766.1547954845569
Root Mean Squared Error (RMSE): 27.67950135903024
Mean Absolute Error (MAE): 19.89159105972337
R-squared (R2): 0.9726985557014894
------------------------------------
Elastic:
Mean Squared Error (MSE): 781.9196116895635
Root Mean Squared Error (RMSE): 27.962825531222045
Mean Absolute Error (MAE): 20.13667979955643
R-squared (R2): 0.9721367863905958
------------------------------------
6.結果
説明変数にNVDAの各種データのみを入れたパターン(パターン1)と、
説明変数にNVDA、SOX、TSMのデータを盛り込んだパターン(パターン2)を
比べてみると、わずかにですがいずれのモデルも改善していることが読み取れます。
説明変数を加えることで少しだけ改善させることができました!
#パターン1、パターン2の差分
diff_mse = mse_v2 - mse
diff_rmse = rmse_v2 - rmse
diff_mae = mae_v2 - mae
diff_r2 = r2_v2 - r2
print(f'mseの改善幅: {diff_mse}' )
print(f'rmseの改善幅: {diff_rmse}' )
print(f'maeの改善幅: {diff_mae}' )
print(f'R2の改善幅: {diff_r2}' )
<出力結果>
mseの改善幅: -0.5307190112670241
rmseの改善幅: -0.009488114610515908
maeの改善幅: 0.24432667495741
R2の改善幅: 1.8911838194690667e-05
7.考察、今後の課題
今回はデータ分析の第一歩として、”データの前処理”、”機械学習”、”可視化する”という一連のプロセスを体験してみたかったため、線形回帰を中心に機械学習モデルを当てはめてみました。
次は冒頭に申し上げたように、株価データを素早く可視化して、自身の分析に活かすアプリを作っていきたいと思います!(Aidemyで学習するうちに、こちらに重点を置きたいと思うようになりました。)
さらに最後の課題として、時系列分析を学んでLSTMモデルを実装して株価予測をしてみようと思っています。
以上、長文をお読みいただきありがとうございました。