☝️

【解説記事】総合指数を総和法で求めるアルゴリズム

に公開

目的

この記事では、総合指数を総和法で求めるアルゴリズム を紹介します。🤔
指数 は、基準となる値を100とし、比較する値が基準値の100に対して幾つになるか?を表した数値です。CPI(消費者物価指数) 等が代表的な例になります。

尚、総合指数 は、複数の品目をまとめて全体の動きを見るための指数であるのに対して、一品目毎の 個別指数 が存在します。
例えば、個別指数 がキャベツだとすると、総合指数 はキャベツ、小松菜、人参の三つの品目をまとめた 指数 になります。

状況

今年は、アルゴリズム と、統計数理 に関するスキル強化を抱負として掲げています。
今月、財団法人 実務教育研究所の『現代統計実務講座』の受講を終え、復習も兼ね、この取り組みを始めました。今回で、十四回目です。🧐

複雑さ(課題)

統計量は、ギリシャ文字を使って、表されます。統計量を求めるには、多くの計算をする必要があります。このため、手順の理解なくして、統計量を求めることはできません。😓 そこで、計算手順を一手ずつ書き出し、フローチャートで可視化することを思いつきました。

総合指数を総和法で求める数式

総合指数を総和法 で求める数式です。

I = \frac{\sum_{i=1}^{n} p_{it}}{\sum_{i=1}^{n} p_{i0}} \times 100

数式の記号の意味

記号 読み方 役割
I Index (指数) 求めたい総合指数の値です。通常、基準時を100として算出します。
n 品目数 指数の計算に採用されている品目の総数を表します。
p_{i0} 基準時価格 「基準時点(過去の特定の年など)」における第 i 番目の品目の価格を表します。
p_{it} 比較時価格 「比較時点(現在など)」における第 i 番目の品目の価格を表します。
sum シグマ 「合計」を意味します。採用されている全品目の数値を合計します。

問い

この記事の問いは、総合指数を総和法を求めるアルゴリズムは、どうなっているか? です。

今回は、米類、粟類、芋類の架空の価格を例に、総合指数総和法 で求めます。

答え

いつも通りの順番で進めます。

  1. アルゴリズムの箇条書き
  2. フローチャート化
  3. 詳細設計
  4. プログラム化

結果

1. アルゴリズムの箇条書き

先ずは、いつもの箇条書きから始めます。

  1. 時系列データかどうかを確認する。
  2. データを昇順で並び替える。
  3. 品目を数える。
  4. 同じ時点の品目の価格を足す。
  5. 同じ時点の品目の価格を、基準時価格に設定する。
  6. 以降の時点の品目の価格を足す。
  7. 以降の時点の品目の価格を、比較時価格に設定する。
  8. 比較時価格を基準時価格で割る。
  9. 100を掛ける。

ポイントは、同じ時点の品目の価格を足し、基準時価格 に設定する点です。
基準時価格 を100として、以降の時点の価格、比較時価格 の指数を求めます。🤔

2. フローチャート化

  1. をフローチャートに変換します。

繰り返しはなく、一本道です。

3. 詳細設計

  1. の一本道のアルゴリズムを、詳細設計します。

データ構造

詳細設計に基づくデータ構造は、以下の通りです。

データ データ型 変数名
基準時価格 指数を求めるにあたり、分母となる基準時価格 浮動小数点型 sum_base_price
比較時価格 指数を求めるにあたり、分子となる比較時価格 浮動小数点型 sum_current_price
日付データ 指数を求めるにあたっての日付データ リスト型 dates
計算結果の蓄積 計算結果を蓄積しておく変数 リスト型 all_indices

4. プログラム化

詳細設計に基づくプログラミング

詳細設計を基に、プログラミングしたソースコードです。

import pandas as pd

def calculate_simple_aggregate_index(data):
    """
    単純総和法を用いて総合指数を計算する関数
    
    Args:
        data (pd.DataFrame): 'date'(日付/時点), 'item'(品目), 'price'(価格)を持つデータ
    Returns:
        pd.DataFrame: 日付ごとの総合指数
    """
    # 1. データの読み込み (引数として受け取り)
    df = data.copy()

    # 2. 時系列データかどうかを確認する
    if not pd.api.types.is_datetime64_any_dtype(df['date']):
        # 日付型でない場合は変換を試みる(変換不能なら終了)
        try:
            df['date'] = pd.to_datetime(df['date'])
        except Exception:
            print("エラー: 時系列データではありません。終了します。")
            return None

    # 3. データを昇順で並び替える
    df = df.sort_values(by=['date', 'item']).reset_index(drop=True)

    # 4. 品目を数える(ユニークな日付のリストを取得してループ準備)
    dates = sorted(df['date'].unique())
    all_indices = []

    # --- 基準時の設定フロー ---
    # 5. 最初の時点の品目価格を足す
    base_date = dates[0]
    base_prices = df[df['date'] == base_date]['price']
    
    # 全品目足したか? (sum関数で全要素を合計)
    sum_base_price = base_prices.sum()

    # 6. 基準時価格に設定する
    # 7. 100を掛ける (基準時は常に100)
    # ※アルゴリズムの手順通り、基準時価格を分母として保持します
    
    # --- 比較時の計算フロー ---
    # 各時点についてループ
    for current_date in dates:
        # 8. 次の時点の品目価格を足す
        current_prices = df[df['date'] == current_date]['price']
        
        # 全品目足したか?
        sum_current_price = current_prices.sum()

        # 9. 比較時価格に設定する
        # 10. 比較時価格を基準時価格で割る
        # 11. 100を掛ける
        index_value = (sum_current_price / sum_base_price) * 100
        
        all_indices.append({
            'date': current_date,
            'total_price': sum_current_price,
            'index': index_value
        })

    # 全期間計算したか?
    return pd.DataFrame(all_indices)

# --- 実行例:架空のデータ(米・粟・芋) ---
data_example = pd.DataFrame({
    'date': ['2024-01-01', '2024-01-01', '2024-01-01', 
             '2024-02-01', '2024-02-01', '2024-02-01'],
    'item': ['米', '粟', '芋', '米', '粟', '芋'],
    'price': [2500, 800, 400,  # 1月(合計: 3700)
              2600, 850, 420]   # 2月(合計: 3870)
})

result = calculate_simple_aggregate_index(data_example)
print(result)

リファクタリング

上記のソースコードを研ぎ澄ますと、以下になること。

  1. groupby によるループの撤廃 (高速化)
  2. ベクトル演算の導入 (簡潔化)
  3. エラー・ハンドリングの強化
  4. メモリ効率の向上
import pandas as pd

def calculate_simple_aggregate_index(data):
    """
    単純総和法を用いて総合指数を計算する(最適化版)
    """
    df = data.copy()

    # 1. & 2. 時系列変換とチェック(errors='coerce' で不正データを無効化)
    df['date'] = pd.to_datetime(df['date'], errors='coerce')
    if df['date'].isnull().any():
        print("エラー: 無効な日付が含まれているか、時系列データではありません。")
        return None

    # 3. & 4. データを日付でグルーピングし、各時点の合計価格を一括計算
    # アルゴリズムの「品目を数えて足す」工程をベクトル演算で処理
    summary_df = df.groupby('date')['price'].sum().sort_index().reset_index()
    summary_df.columns = ['date', 'total_price']

    # 5. & 6. 基準時価格(最初の行の合計価格)を取得
    sum_base_price = summary_df.iloc[0]['total_price']

    # 8. 〜 11. 全期間の指数を一括計算(列演算)
    # ループを使わず、列全体に対して一度に割り算と掛け算を行う
    summary_df['index'] = (summary_df['total_price'] / sum_base_price) * 100

    return summary_df

# --- 実行確認用 ---
data_example = pd.DataFrame({
    'date': ['2024-01-01', '2024-01-01', '2024-01-01', 
             '2024-02-01', '2024-02-01', '2024-02-01'],
    'item': ['米', '粟', '芋', '米', '粟', '芋'],
    'price': [2500, 800, 400, 2600, 850, 420]
})

print(calculate_simple_aggregate_index(data_example))

示唆

  • 総合指数の総和法 は、シンプルな指数で、基準時価格 を定め、比較時価格 を割ることで求められます。
  • 時系列データの最も若いデータを、基準時価格 に設定しています。
  • 総和法 は、極端に価格の違う品目をまとめる場合、価格が高い品目の変動に指数全体が影響を受ける性質があるため、実務では重みを加味した ラスパレイス式 等がよく使われるそうです。

結論

  • 指数 には、ラスパレースフィッシャー 等の種類があります。
  • 統計量そのものもさることながら、身近なスーパーで買える食材の価格の変動を分析するのも面白いと感じます。☺️
  • 暫く、指数 が続きます。🤔

出所

Discussion