どこにでもいる30代SEの学習ブログ

主にプログラミング関連の学習内容。読んだ本の感想や株式投資についても書いてます。

pandas-datareaderでTOPIX500銘柄の株価上昇率を算出

f:id:predora005:20210201003956j:plain
<pandas-datareaderでTOPIX500銘柄の株価上昇率を算出>*1

前回はpandas-datareaderで米国株の株価を取得しました。

predora005.hatenablog.com

今回は、東証一部上場銘柄の株価を取得し、業種ごとの株価変動を可視化しました。

f:id:predora005:20210211200716p:plain

[1] 東証上場銘柄を取得する

[1-1] JPX(日本取引所グループ)から取得

東証上場銘柄の一覧は、JPX(日本取引所グループ)のページから取得します。

その他統計資料 | 日本取引所グループ

中身は次のようなデータになっています。

f:id:predora005:20210211133201p:plain

[1-2] CSVに変換して読み込み

ExcelCSV形式に保存したのち、PythonからpandasのDataFrameとして読み込みます。

import pandas as pd

tse_list = pd.read_csv('data_j.csv', header=0)

[2] TOPIX500銘柄の業種ごとの株価上昇率を算出

東証上場銘柄のうち、TOPIX500銘柄にターゲットを絞ります。ちなみに、全銘柄だと4,086銘柄あります*2

[2-1] TOPIX500銘柄のデータを抽出

'市場・商品区分'で東証一部上場銘柄を抽出します。次に、'規模区分'でTOPIX500銘柄を抽出します。

# TOPIX500の銘柄を抽出する
tse1 = tse_list[tse_list['市場・商品区分'] == '市場第一部(内国株)']
topix500 = tse1[(tse1['規模区分'] == 'TOPIX Core30') | 
                (tse1['規模区分'] == 'TOPIX Large70') |
                (tse1['規模区分'] == 'TOPIX Mid400')]

いったん東証一部上場銘柄を抽出していますが、省略してTOPIX500銘柄を抽出することも可能です。

なお、TOPIXについては、下記ページに詳しく載っています。

TOPIX(東証株価指数) | 日本取引所グループ

[2-2] 業種ごとの銘柄数を確認

業種ごとの銘柄数を確認すると、電気機器の49銘柄が最多です。

# 33業種コード,33業種区分ごとの銘柄数を算出
category_count = topix500.groupby(['33業種コード','33業種区分']).size()
print(category_count)
# 33業種コード  33業種区分    
# 1050        鉱業              1
# 2050        建設業            20
# 3050        食料品            28
# 3100        繊維製品           5
# 3150        パルプ・紙          3
# 3200        化学              46
# 3250        医薬品            22
# 3300        石油・石炭製品       3
# 3350        ゴム製品           4
# 3400        ガラス・土石製品      8
# 3450        鉄鋼              7
# 3500        非鉄金属           7
# 3550        金属製品           6
# 3600        機械              32
# 3650        電気機器           49
# 3700        輸送用機器         20
# 3750        精密機器           10
# 3800        その他製品          10
# 4050        電気・ガス業         13
# 50          水産・農林業         2
# 5050        陸運業             24
# 5100        海運業             2
# 5150        空運業             2
# 5200        倉庫・運輸関連業      2
# 5250        情報・通信業         32
# 6050        卸売業             23
# 6100        小売業             33
# 7050        銀行業             26
# 7100        証券、商品先物取引業  5
# 7150        保険業             6
# 7200        その他金融業        9
# 8050        不動産業           12
# 9050        サービス業          25
# dtype: int64

[2-3] 各銘柄の株価上昇率を計算

ここでは、2020/11/2〜2021/2/5の株価上昇率を計算します。

[2-3-1] 業種コード・業種区分を抽出

まずは、業種コード・業種区分を抽出します。

# 33業種コード,33業種区分を抽出
industry_category = topix500.groupby(['33業種コード','33業種区分']).groups.keys()
print(industry_category)
# dict_keys([('1050', '鉱業'), ('2050', '建設業'), ('3050', '食料品'), 
# ('3100', '繊維製品'), ('3150', 'パルプ・紙'), ('3200', '化学'), 
# ('3250', '医薬品'), ('3300', '石油・石炭製品'), ('3350', 'ゴム製品'), 
# ('3400', 'ガラス・土石製品'), ('3450', '鉄鋼'), ('3500', '非鉄金属'), 
# ('3550', '金属製品'), ('3600', '機械'), ('3650', '電気機器'), 
# ('3700', '輸送用機器'), ('3750', '精密機器'), ('3800', 'その他製品'), 
# ('4050', '電気・ガス業'), ('50', '水産・農林業'), ('5050', '陸運業'), 
# ('5100', '海運業'), ('5150', '空運業'), ('5200', '倉庫・運輸関連業'), 
# ('5250', '情報・通信業'), ('6050', '卸売業'), ('6100', '小売業'), 
# ('7050', '銀行業'), ('7100', '証券、商品先物取引業'), ('7150', '保険業'), 
# ('7200', 'その他金融業'), ('8050', '不動産業'), ('9050', 'サービス業')])

[2-3-2] 業種ごとに株価を取得

抽出した、業種コード・業種区分ごとに株価を取得します。期間は3ヶ月分よりも長く取得したいのですが、Stooqの株価取得には制限があります。具体的な基準は分かりませんが、一定回数か容量を超えると空のDataFrameが返ってくるので、3ヶ月分に留めています。

# 2020/11/2〜2021/2/5の株価を取得する
base_date = datetime.datetime(2020, 1, 6)
end_date=datetime.datetime(2021, 2, 5)

# 業種単位の株価上昇率格納用のDataFrameを用意する
category_df = None

# 業種ごとに変動率を計算する
for category in industry_category:
    
    category_code = category[0]     # 33業種コード
    category_class = category[1]    # 33業種区分
    
    # 指定した業種の銘柄を抽出
    brands = topix500[topix500['33業種区分'] == category_class]
    
    # 銘柄コードの末尾に.JPを付加する
    symbols = []
    for code in brands['コード']:
        symbols.append('{0:d}.JP'.format(code))

    # 指定銘柄コードの株価を取得する
    stock_price = web.DataReader(symbols, 'stooq', start=base_date, end=end_date)
    print(stock_price)
    # Attributes   Close                 ...  Volume                  ...   
    # Symbols    1414.JP 1721.JP 1801.JP ... 1883.JP  1893.JP 1911.JP ...   
    # Date                               ...                          ...   
    # 2021-02-05  4600.0  3315.0  3545.0 ...  262000   877200  476600 ...   
    # 2021-02-04  4630.0  3245.0  3510.0 ...  304100   864200  547700 ...   
    # ...            ...     ...     ... ...     ...      ...     ... ...   
    # 2020-11-04  5200.0  2789.0  3325.0 ...   85900  1494600  520500 ...   
    # 2020-11-02  5130.0  2678.0  3280.0 ...  140500   647000  409200 ...   

TOPIX500銘柄すべての株価を一度に取得し、業種単位に分割することも可能と思われます。しかし、一度に大量のデータは扱いたくないので、業種単位で取得しています。

[2-3-3] 各銘柄の株価上昇率を計算

株価を取得した銘柄ごとに株価の上昇率を計算します。計算結果はディショクナリにいったん格納し、最後にDataFrameに変換します。

    # 銘柄ごとに上昇率を計算し、ディショクナリに格納する
    dict = {}
    for symbol in symbols:
        
        # 基準日付からの上昇率を計算する
        base_date_str = base_date.strftime('%04Y-%02m-%02d')
        base_price = stock_price.loc[base_date_str][('Close',symbol)]
        increase_rate = stock_price[('Close',symbol)] / base_price.iloc[0]
        
        # ディクショナリに格納する
        dict[symbol] = increase_rate
        
    # ディクショナリからDataFrame作成
    df = pd.DataFrame(dict)

[2-4] 業種ごとの平均値, 標準偏差を算出

業種ごとの株価上昇率について、平均値と標準偏差を計算します。計算結果はDataFrameに追加していきます。

    # 業種内銘柄の上昇率の平均値を計算する
    mean = df.mean(axis='columns')
    std = df.std(axis='columns')
    
    # DataFrameに業種単位の上昇率と、上昇率の標準偏差を格納する
    if category_df is None:
        category_df = pd.concat([mean, std], axis=1)
        category_df.columns = pd.MultiIndex.from_tuples(
            [(category_class, '上昇率'), (category_class, '標準偏差')])
    else:
        category_df[(category_class, '上昇率')] = mean
        category_df[(category_class, '標準偏差')] = std
    
    print(category_df)
    #             鉱業              建設業          
    #             上昇率    標準偏差  上昇率     標準偏差
    # Date                                         
    # 2021-02-05  1.308617  NaN     1.170162  0.130083
    # 2021-02-04  1.274549  NaN     1.157672  0.130904
    # ...         ...       ...     ...       ...
    # 2020-11-04  1.054108  NaN     1.007299  0.013290
    # 2020-11-02  1.000000  NaN     1.000000  0.000000
    # 
    # [65 rows x 4 columns]

鉱業はTOPIX500銘柄が「国際石油開発帝石」しかないため、標準偏差が計算できずNaNとなっています。複数銘柄ある場合には標準偏差が計算されます。

[2-5] 折れ線グラフで株価上昇率を可視化

まずは、業種ごとに株価上昇率を折れ線グラフで可視化します。2020年11月〜2021年2月は、バイデン大統領の当選や金融緩和の継続もあり、全体的に株価が上昇していました。

f:id:predora005:20210211200716p:plain

import matplotlib.pyplot as plt
import matplotlib.dates as mdates

# 上昇率のみを抽出し、業種の数を取得する
increase_rate_df = category_df.loc[:, pd.IndexSlice[:, '上昇率']]
industry_num = len(increase_rate_df.columns)

# rows, colsを決定し、Figureを取得
rows, cols = (6, 6)
fig = plt.figure(figsize=(20, 16))

# 上昇率の最大値と最小値を取得
min_y = increase_rate_df.min().min()
max_y = increase_rate_df.max().max()

# 業種ごとに折れ線グラフを表示する
for i in range(industry_num):
    
    # Axesを取得
    ax = fig.add_subplot(rows, cols, i+1)
    
    # 銘柄名
    category_name = increase_rate_df.columns[i][0]
    
    # 上昇率を取得
    increate_rate = increase_rate_df.loc[:, pd.IndexSlice[category_name, '上昇率']]
    
    # 折れ線グラフを表示
    x = increase_rate_df.index
    y = increate_rate
    ax.plot(x, y, label=category_name)
    
    # 目盛り線を表示
    ax.grid(color='gray', linestyle='--', linewidth=0.5)
    
    # X軸の目盛り位置を設定
    ax.xaxis.set_major_locator(mdates.MonthLocator())
    
    # Y軸の範囲を設定
    ax.set_ylim(min_y, max_y) 
    
    # グラフのタイトルを追加
    ax.set_title(category_name)
    
# 不要な余白を削る
plt.tight_layout()

# グラフを表示
fig.show()

[2-6] 棒グラフで株価上昇率を可視化

次は上昇率を棒グラフで可視化します。

f:id:predora005:20210211200803p:plain

棒グラフを表示する前に、一度DataFrameを作成し直しています。これは、上昇率でソートするためです。元のDataFrameはMultiIndexになっており、そのままの形ではソートが難しかったためです。

# 最新日付を取得する
latest_date = category_df.index.max()
latest_date_str = latest_date.strftime('%04Y-%02m-%02d')

# 最新日付の上昇率, 標準偏差を抽出する
increase_rate = category_df.loc[latest_date_str, pd.IndexSlice[:, '上昇率']]
std = category_df.loc[latest_date_str, pd.IndexSlice[:, '標準偏差']]

# 業種名をリストで取得
categories= [col[0] for col in increase_rate.columns]

# 上昇率と標準偏差を列にもつDataFrameを作成し、上昇率で降順ソートする
df = pd.DataFrame({'上昇率': increase_rate.values[0], '標準偏差': std.values[0]}, 
                    index=categories)
df = df.sort_values('上昇率', ascending=False)
print(df)
#              上昇率      標準偏差
# 海運業        1.329285   0.148943
# 電気機器      1.313898   0.194788
# 非鉄金属      1.312967   0.141995
# 鉱業          1.308617  NaN
# 石油・石炭製品  1.291704   0.1598
# ...(以下略)...

barhで可視化します。xerrを使用して、標準偏差をエラーバーとして表示しています。

# 図と座標軸を取得
fig = plt.figure(figsize=(10, 10))
ax = fig.add_subplot(1,1,1)

# 棒グラフに表示するデータを準備
x = np.arange(len(categories))
y = df['上昇率']
yerr = df['標準偏差']
label = df.index

# 棒グラフ表示
ax.barh(x, y, xerr=yerr, tick_label=label)

# 目盛り線を表示
ax.grid(axis='x', color='gray', linestyle='--', linewidth=0.5)

# 不要な余白を削る
plt.tight_layout()

# グラフを表示
fig.show()

[3] TOPIX500銘柄の銘柄ごとの株価上昇率を算出

[3-1] 銘柄ごとの株価上昇率を算出

業種ごとの平均値, 標準偏差を算出したのと同様の方法で行います。pandas-datareaderによる株価取得は業種単位で行い、そののち銘柄ごとの株価上昇率を計算します。

# 33業種コード,33業種区分を抽出
industry_category = topix500.groupby(['33業種コード','33業種区分']).groups.keys()

# 銘柄単位の株価上昇率格納用のディクショナリを用意する
brand_dict = {}
brand_count = 0

# 業種ごとに変動率を計算する
for category in industry_category:
    
    category_code = category[0]     # 33業種コード
    category_class = category[1]    # 33業種区分
    
    # 指定した業種の銘柄を抽出
    brands = topix500[topix500['33業種区分'] == category_class]
    
    # 銘柄コードの末尾に.JPを付加する
    symbols = []
    for code in brands['コード']:
        symbols.append('{0:d}.JP'.format(code))
        
    # 指定銘柄コードの株価を取得する
    stock_price = web.DataReader(symbols, 'stooq', start=base_date)
    
    # 銘柄ごとに上昇率を計算し、ディショクナリに格納する
    dict = {}
    for symbol in symbols:
        
        # 銘柄ごとに上昇率を計算し、ディショクナリに格納する
        # 基準日付からの上昇率を計算する
        base_date_str = base_date.strftime('%04Y-%02m-%02d')
        base_price = stock_price.loc[base_date_str][('Close',symbol)]
        increase_rate = stock_price[('Close',symbol)] / base_price.iloc[0]
        
        # ディクショナリに格納する
        dict[symbol] = increase_rate
        
    # ディクショナリからDataFrame作成
    df = pd.DataFrame(dict)
    
    # 最新日付を取得する
    latest_date = stock_price.index.max()
    latest_date_str = latest_date.strftime('%04Y-%02m-%02d')
    
    # 銘柄単位の株価上昇率をディクショナリに格納する
    for i, symbol in enumerate(symbols):
        
        code = brands['コード'].iloc[i]
        name = brands['銘柄名'].iloc[i]
        increase_rate = df.loc[latest_date_str, symbol].iloc[0]
        
        # '33業種コード','33業種区分', 'コード', '銘柄名', '上昇率'])
        brand_dict[brand_count] = [category_code, category_class, code, name, increase_rate]
        brand_count += 1
        
# 銘柄単位の株価上昇率を格納したDataFrameを作成する
brand_df = pd.DataFrame.from_dict(
                brand_dict, orient="index", 
                columns=['33業種コード','33業種区分', 'コード', '銘柄名', '上昇率'])

業種ごとの株価上昇率は日単位で算出していましたが、銘柄については最新日付の株価上昇率のみとしています。

[3-2] 株価上昇率と上位10, 下位10銘柄を確認

[3-2-1] 上位10銘柄

まずは上位10銘柄を確認します。

# 上昇率で降順ソート
df_sorted = brand_df.sort_values('上昇率', ascending=False)

# 上位銘柄を抽出
df_top = df_sorted.head(10)

電気機器・輸送用機器銘柄の上昇が目立ちます。

33業種区分 銘柄名 上昇率
電気機器 コニカミノルタ 1.854610
電気機器 シャープ 1.839968
電気機器 ジーエス・ユアサ コーポレーション 1.838553
輸送用機器 川崎重工業 1.784906
輸送用機器 マツダ 1.675393
情報・通信業 コナミホールディングス 1.661905
その他金融業 東京センチュリー 1.648956
輸送用機器 日産自動車 1.634646
輸送用機器 ヤマハ発動機 1.589777
機械 IHI 1.581

[3-2-2] 下位10銘柄

次に下位10銘柄を確認します。

# 上昇率で昇順ソート
df_sorted = brand_df.sort_values('上昇率', ascending=True)

# 下位銘柄を抽出
df_bottom = df_sorted.head(10)

銀行業が多めであり、最大で17%弱の減少です。2020年11月〜2021年2月は、TOPIXが17%近く上昇していることもあってか、極端に大きな下落にはなっていませんでした。

33業種区分 銘柄名 上昇率
銀行業 滋賀銀行 0.835329
銀行業 九州フィナンシャルグループ 0.857708
繊維製品 ゴールドウイン 0.859155
情報・通信業 光通信 0.867015
サービス業 ベネッセホールディングス 0.871878
食料品 東洋水産 0.872659
小売業 ウエルシアホールディングス 0.874092
銀行業 山口フィナンシャルグループ 0.886364
食料品 カゴメ 0.888743
銀行業 中国銀行 0.894793

終わりに

pandas-datareaderでTOPIX500銘柄の株価を取得し、業種ごとの株価上昇率を可視化しました。以前にスクレイピングで株価を取得しましたが、pandas-datareaderの方が簡単に株価を取得できました。

predora005.hatenablog.com

ただ、取得できるデータ数が限られるという利用制限があります。一度取得したデータを保存しておく等の工夫は必要だとは感じました。過去データはpandas-datareaderで取得・保存しておいて、最新の株価はスクレイピングで取得するのがよいのかもしれません。

*1:StockSnapによるPixabayからの画像

*2:2021/2/8時点