ARIMAモデルで簡単予測！カテエネの過去データから売電料金の未来を予測

## 売電料金減ってる・・・？
ソーラーパネルを積んで数年経ちますが、昔に比べて売電料金が減っている気がする。
とはいえ、過去のデータを取っていないので、具体的にどうなっているのか・・・と考えた時に、カテエネのサイトから見れるのではと思いました。
カテエネを見ると、過去3年分くらいまではCSVで取得できるようでした。
これを使って、過去のデータを解析するとともに、将来の売電料金を予想させてみました。

## カテエネからのCSVダウンロード方法

ログインして、 **使用量・料金実績** をクリック
![スクリーンショット 2024-07-02 15.13.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3741957/21ee2678-15fb-28f9-cd36-80ab89fb9c0d.png)

**料金と使用量** をクリック
![スクリーンショット 2024-07-02 15.14.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3741957/7f29142a-e2f2-21af-a359-ecddff3ff5d7.png)

**データダウンロード** をクリック
![スクリーンショット 2024-07-02 15.14.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3741957/b64d28be-9c3b-56d7-c02a-c9cdb31ae4e0.png)

売電契約を選択し、**全ての期間** にチェックしてダウンロードをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3741957/556802e8-df5a-64fa-aa04-d6fcff56a3f6.png)



## カテエネから取得できるCSVのデータ
* 過去3年分がまとまったCSVがダウンロードできる
* Shift_JISエンコード
* 1行目には不要な説明があるので読み飛ばした方が良い

## 将来を予測させるためのモデル
時系列データの解析に適したモデルとしてARIMAモデルを使用しました。ARIMAは過去のデータに基づいて将来を予測する統計モデルで、特にトレンドや季節性を持つデータに対して有効です。
SARIMAモデル（季節性ARIMA）は、季節性のパターンを考慮する点でARIMAよりも適していますが、今回は単純なARIMAモデルを試しました。

## コード

`pandas` `matplotlib` `statsmodels` をインストールし、以下のようなコードを書いて実行します
```
import os

import matplotlib.font_manager as fm
import matplotlib.pyplot as plt
import pandas as pd
from statsmodels.tsa.arima.model import ARIMA

DATA_DIR = 'data'  # CSVファイルが保存されているディレクトリ
DIST_DIR = 'dist'  # 出力ディレクトリ
TARGET_COLUMN = '再生可能エネルギーからの電力受給契約'
DATE_COLUMN = 0
PRICE_COLUMN = 12

def load_and_filter_csv_files(directory):
    """
    ディレクトリ内のすべてのCSVファイルを読み込み、
    特定の条件に一致するデータをフィルタリングして結合します。

    Parameters:
    directory (str): CSVファイルが保存されているディレクトリのパス。

    Returns:
    DataFrame: フィルタリングされたデータを含むデータフレーム。
    """
    all_data = pd.DataFrame()
    for filename in os.listdir(directory):
        if filename.endswith('.csv'):
            filepath = os.path.join(directory, filename)
            try:
                df = pd.read_csv(filepath, encoding='shift_jis', skiprows=1, header=0)
                df = df.dropna(subset=[df.columns[DATE_COLUMN], df.columns[PRICE_COLUMN], df.columns[5]])
                df = df[df[df.columns[5]] == TARGET_COLUMN]
                filtered_df = df.iloc[:, [DATE_COLUMN, PRICE_COLUMN]]
                all_data = pd.concat([all_data, filtered_df])
            except Exception as e:
                print(f"Error reading {filename}: {e}")
    return all_data

def preprocess_data(df):
    """
    データフレームを前処理し、日付をインデックスに設定し、頻度情報を追加します。

    Parameters:
    df (DataFrame): 前処理するデータフレーム。

    Returns:
    DataFrame: 前処理されたデータフレーム。
    """
    df.columns = ['date', 'price']
    df['date'] = pd.to_datetime(df['date'], format='%Y%m')
    df['price'] = df['price'].astype(str).str.replace(',', '').astype(float)
    df.sort_values('date', inplace=True)
    df.set_index('date', inplace=True)
    df.index.freq = 'MS'
    return df

def train_arima_model(df):
    """
    ARIMAモデルをトレーニングします。

    Parameters:
    df (DataFrame): トレーニングに使用するデータフレーム。

    Returns:
    ARIMAResultsWrapper: トレーニングされたARIMAモデル。
    """
    model = ARIMA(df['price'], order=(5, 1, 0))
    model_fit = model.fit()
    return model_fit

def predict_future_prices(model_fit, df, months=12):
    """
    トレーニングされたARIMAモデルを使用して、将来の売電料金を予測します。

    Parameters:
    model_fit (ARIMAResultsWrapper): トレーニングされたARIMAモデル。
    df (DataFrame): 元のデータフレーム。
    months (int): 予測する月数。

    Returns:
    DataFrame: 予測された売電料金を含むデータフレーム。
    """
    forecast = model_fit.get_forecast(steps=months)
    forecast_dates = [df.index.max() + pd.DateOffset(months=i) for i in range(1, months + 1)]
    future_df = pd.DataFrame({'date': forecast_dates, 'predicted_price': forecast.predicted_mean})
    return future_df

def plot_predictions(df, future_df):
    """
    実際の売電料金と予測された売電料金をプロットし、画像として保存します。

    Parameters:
    df (DataFrame): 実際の売電料金を含むデータフレーム。
    future_df (DataFrame): 予測された売電料金を含むデータフレーム。
    """
    if not os.path.exists(DIST_DIR):
        os.makedirs(DIST_DIR)
    output_file = os.path.join(DIST_DIR, 'price_prediction.png')

    # 日本語フォントの設定
    font_path = '/System/Library/Fonts/ヒラギノ角ゴシック W3.ttc'  # macOSの例
    prop = fm.FontProperties(fname=font_path)

    plt.figure(figsize=(10, 6))
    plt.plot(df.index, df['price'], label='実際の売電料金')
    plt.plot(future_df['date'], future_df['predicted_price'], label='予測売電料金', linestyle='--')
    plt.xlabel('日付', fontproperties=prop)
    plt.ylabel('売電料金 (円)', fontproperties=prop)
    plt.title('売電料金の予測', fontproperties=prop)
    plt.legend(prop=prop)
    plt.grid(True)
    plt.savefig(output_file)
    plt.show()

def predict():
    """
    データを読み込み、前処理し、ARIMAモデルをトレーニングし、
    将来の売電料金を予測してプロットします。
    """
    # データの読み込みとフィルタリング
    all_data = load_and_filter_csv_files(DATA_DIR)
    if all_data.empty:
        print("No data found.")
        return

    # データの前処理
    processed_data = preprocess_data(all_data)

    # モデルのトレーニング
    model_fit = train_arima_model(processed_data)

    # 将来の売電料金を予測
    future_predictions = predict_future_prices(model_fit, processed_data, months=12)

    # 予測結果をグラフとして出力
    plot_predictions(processed_data, future_predictions)

```

rye なら、`predict` を `[project.scripts]` に定義すれば以下のように実行できます
```
$ rye run predict
```
実行するとグラフ画像が作成されて表示されます

## 予測させてみた実際のデータ

![price_prediction.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3741957/56968a7f-411b-c979-6d5d-1b7ea568706c.png)
雪国なので冬の売電はかなり落ち込みますね
過去3年のデータしか無いのでイマイチですが・・・
たぶん1月はもっと低いはず
SARIMAのほうが良かったかな・・・

## まとめ
グラフを見ると、ここ3年くらいでは売電は過剰に減っているという感じでも無さそうでした。

予測に関しては、より長期的なデータや、気候変動、電力市場の変化などの外部要因も考慮に入れることで、さらに精度の高い予測が可能になるかもしれません。
また、SARIMAモデルなど、他の時系列モデルも今度試してみようと思います。