🗂

身近なデータで試すPythonによる機械学習! その1  家庭の電気使用量と天気の関係を分析してみよう

に公開

こんにちは!データ分析や機械学習に興味はあるけど、何から始めればいいかわからない…そんな風に思っていませんか?
今回は、私たちの生活に身近な「家庭の電気使用量」と「天気」の関係を、Pythonを使って分析するプロセスをステップバイステップでご紹介します。難しい理論は一旦置いておいて、基本的なモデル作成の練習の題材として参考になればと思います!

この記事を読むとわかること:

実際のデータ(関西電力の電気使用量、気象庁の気象データ)を使ったデータ分析の基本的な流れ
Pandasライブラリを使ったデータの読み込みと前処理の方法
Scikit-learnライブラリを使った簡単な機械学習モデル(線形回帰)の作り方
モデルの性能を評価する方法(決定係数 R2スコア)
どの気象要素が電気使用量に影響を与えているかの簡単な考察

使うもの:

Python 3.x
Pandas ライブラリ (pip install pandas)
Scikit-learn ライブラリ (pip install scikit-learn)

データファイル:

Power.csv: 電気使用量データ
我が家の電気使用量について行ってみました。我が家は関西電力さんと契約をしているのですが、関西電力さんの「はぴeみる電」のホームページから日々の電気使用量をダウンロートできます。データは毎日、毎時間の使用量と簡単な気象情報からなっていますが、今回は毎日の電気使用量を目的変数として設定することにしました。ただし1ヶ月ごとのデータですので、1年間で試す場合はそれぞれのデータを集約する必要があります(今回はこの作業は表計算ソフトで地道に行いました)


(加工済みのデータ)

Weather.csv: 気象データ
気象庁のウェブサイトからダウンロードした、対応する年の日ごとの気象データ(地点:大阪など)になります。説明変数として使わない項目はあらかじめ表計算ソフトで削除して使っています。


(加工済みのデータ)

今回は取得したデータの結合やカラムの整理は表計算ソフト等で行っていますが、理想的にはこのような前処理にもPythonを使ってゆく方がよいかと思います。

Step 1: 準備 - ライブラリのインポートと定数定義

まずは、分析に必要なPythonライブラリをインポートします。データ操作にはpandas、機械学習モデルの構築と評価にはscikit-learnを使います。また、ファイル名や使用するカラム名などを定数として定義しておくと、後でコードを修正したり、別のデータで試したりする際に便利です。

import pandas as pd
import numpy as np # 今回は直接使いませんが、データ分析ではよく使います
from sklearn import linear_model
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score # R2スコア計算のために明示的にインポート

# --- 定数定義 ---
POWER_DATA_FILE = 'Power.csv'
WEATHER_DATA_FILE = 'Weather.csv'

# 関西電力データのカラム名
POWER_COLUMNS = {
    'Unnamed: 0': 'Date_Str', # 日付データ、一旦文字列として読み込む
    'Unnamed: 1': 'EleVol', # 電気使用量 (これが予測したい値)
    'Unnamed: 2': 'Weather_Summary', # 関電データでの天気
    'Unnamed: 3': 'Temp_Max_Power',  # 関電データでの最高気温
    'Unnamed: 4': 'Temp_Min_Power',  # 関電データでの最低気温
    'Unnamed: 5': 'SunVol'          # 関電データでの日照時間
}

# 気象庁データのカラム名(電気使用量の予測に使う特徴量)
WEATHER_FEATURES = [
    '平均気温', '最高気温', '最低気温', '平均風速', '最大風速', '平均現地気圧', '平均湿度'
]

TARGET_VARIABLE = 'EleVol' # 目的変数 (予測したいもの)
HOLIDAY_DAYS = ['Sat', 'Sun'] # 休日とする曜日

Step 2: データの読み込みと前処理

次に、CSVファイルを読み込んで、分析しやすい形に整える「前処理」を行います。この作業はデータ分析において非常に重要です。今回は、電力データと気象データをそれぞれ読み込み、整形する関数を作成しました。関数化することで、コードがスッキリし、何をしているかが分かりやすくなります。

2.1 関数の準備
データ読み込みと前処理を行う関数を定義します。

def load_and_preprocess_power_data(filepath: str, columns_map: dict, year: int = 2020) -> pd.DataFrame:
    """ 関西電力データを読み込み、前処理を行う関数 """
    # (ファイル読み込み、カラム名変更、日付処理、曜日・休日フラグ追加など)
    # ※詳細なコードは上記の修正コードを参照してください
    try:
        # header=5 で5行目までをスキップ、encoding='shift-jis' で文字コードを指定
        df = pd.read_csv(filepath, header=5, encoding='shift-jis')
    except FileNotFoundError:
        print(f"エラー: ファイルが見つかりません - {filepath}")
        return pd.DataFrame()
    except Exception as e:
        print(f"エラー: ファイル読み込み中に問題が発生しました - {filepath}, {e}")
        return pd.DataFrame()

    df.rename(columns=columns_map, inplace=True)

    # 'm/d' 形式の文字列を日付型に変換
    df['Date'] = pd.to_datetime(f'{year}/' + df['Date_Str'])
    # 曜日情報を追加 ('Mon', 'Tue'...)
    df.insert(1, 'DayOfWeek', df['Date'].dt.strftime("%a"))
    df.drop('Date_Str', axis=1, inplace=True) # 元の文字列日付は不要なので削除

    # 休日フラグ (土日ならTrue) を追加
    df['IsHoliday'] = df['DayOfWeek'].isin(HOLIDAY_DAYS)

    return df

def load_and_preprocess_weather_data(filepath: str, features: list) -> pd.DataFrame:
    """ 気象庁データを読み込み、前処理を行う関数 """
    # (ファイル読み込み、並び替え、必要カラム抽出など)
    # ※詳細なコードは上記の修正コードを参照してください
    try:
        # header=1 で1行目までをスキップ
        df = pd.read_csv(filepath, header=1, encoding='shift-jis')
    except FileNotFoundError:
        print(f"エラー: ファイルが見つかりません - {filepath}")
        return pd.DataFrame()
    except Exception as e:
        print(f"エラー: ファイル読み込み中に問題が発生しました - {filepath}, {e}")
        return pd.DataFrame()

    # 気象庁データは通常、最新の日付が上に来ることが多い
    # 電力データ (1月始まり) に合わせるため、データの順番を逆にする
    df_reversed = df.iloc[::-1].reset_index(drop=True)

    # 定義した特徴量のカラムだけを選択
    df_selected = df_reversed[features]

    return df_selected

ポイント:
pd.read_csv(): CSVファイルを読み込むためのPandas関数。headerで行数、encodingで文字コードを指定します。Shift-JISは日本語Windows環境でよく使われます。
df.rename(): データフレームのカラム名を変更します。
pd.to_datetime(): 文字列を日付型に変換します。日付の計算や曜日の取得が容易になります。
df['Date'].dt.strftime("%a"): 日付型データから曜日('Mon', 'Tue'など)を取得します。
df.iloc[::-1]: データフレームの行を逆順にします。
df.reset_index(drop=True): 逆順にした後、元のインデックスを削除して新しい連番のインデックスを振り直します。
df['IsHoliday'] = df['DayOfWeek'].isin(HOLIDAY_DAYS): 'DayOfWeek'列の値がHOLIDAY_DAYSリスト(['Sat', 'Sun'])に含まれていればTrue、そうでなければFalseとなる新しい列 'IsHoliday' を作成します。

2.2 関数の実行とデータの結合
定義した関数を使って、実際にデータを読み込みます。

# --- メイン処理 ---

# 1. データの読み込みと前処理
power_df = load_and_preprocess_power_data(POWER_DATA_FILE, POWER_COLUMNS)
weather_df = load_and_preprocess_weather_data(WEATHER_DATA_FILE, WEATHER_FEATURES)

# データが正常に読み込めたか確認
if power_df.empty or weather_df.empty:
    print("データの読み込みに失敗したため、処理を中断します。")
else:
    # 2. データの結合 (インデックスベースで結合)
    # power_df と weather_df の行数が同じであることを前提としています
    if len(power_df) == len(weather_df):
        # reset_index() でインデックスを揃えてから横方向に結合 (axis=1)
        merged_df = pd.concat([power_df.reset_index(drop=True), weather_df.reset_index(drop=True)], axis=1)
        print("データの結合が完了しました。")
        print("結合後のデータサンプル:")
        print(merged_df.head()) # 最初の5行を表示
        
    else:
        print(f"エラー: 電力データ ({len(power_df)}行) と気象データ ({len(weather_df)}行) の行数が一致しません。結合できません。")
        merged_df = pd.DataFrame() # エラーの場合は空のデータフレームを設定

pd.concat() を使って、電力データと気象データを横に結合し、merged_df という一つのデータフレームにまとめます。

Step 3: 特徴量エンジニアリング - データの分割

一般的に、休日の電気の使われ方は平日と異なると考えられます(例:在宅時間が長い、業務用の電力消費が少ないなど)。そこで、より精度の高いモデルを作るために、データを「休日」と「平日」に分けて、それぞれ別のモデルを作ることにします。
Step 2で追加した IsHoliday 列を使ってデータをフィルタリングします。

if not merged_df.empty:
        # 3. 休日と平日のデータに分割
        holiday_df = merged_df[merged_df['IsHoliday'] == True]
        weekday_df = merged_df[merged_df['IsHoliday'] == False]

        print(f"\n休日データ数: {len(holiday_df)}")
        print(f"平日データ数: {len(weekday_df)}")

Step 4: モデルの構築と学習

いよいよ機械学習モデルを作ります!今回は、最も基本的な回帰モデルの一つである線形回帰 (Linear Regression) を使います。線形回帰は、気象データ(説明変数 X)と電気使用量(目的変数 Y)の関係を直線的な式で表そうとするモデルです。
式: Y ≈ w1X1 + w2X2 + ... + wn*Xn + b
ここで、Yが電気使用量、X1, X2,...が平均気温、最高気温などの気象データ、w1, w2,...がそれぞれの気象データが電気使用量にどれだけ影響するかを示す係数、bは切片(全ての気象データが0のときの予測値)です。モデルを学習するとは、データに最もよく当てはまる係数wと切片bを見つけることです。

4.1 目的変数と説明変数の設定
休日データ、平日データそれぞれについて、予測したい値(目的変数 Y)である電気使用量 (EleVol) と、予測に使う値(説明変数 X)である気象データ (WEATHER_FEATURES) を設定します。

# 4. 目的変数と説明変数の設定
        X_holiday = holiday_df[WEATHER_FEATURES]
        y_holiday = holiday_df[TARGET_VARIABLE]
        X_weekday = weekday_df[WEATHER_FEATURES]
        y_weekday = weekday_df[TARGET_VARIABLE]

4.2 モデル学習・評価関数の準備
休日モデルと平日モデルで同じ処理(データ分割、学習、評価)を行うため、これも関数化しておきましょう。

def train_evaluate_model(X: pd.DataFrame, y: pd.Series, model_type: str) -> dict:
    """ 指定されたデータで線形回帰モデルを学習し、評価する関数 """
    # (データ分割、モデル初期化、学習、予測、評価、結果表示)

    # データを学習用 (train) とテスト用 (test) に分割
    # test_size=0.25 は、全体の25%をテストデータに使うという意味
    # random_state=42 は、毎回同じように分割するための乱数シード(再現性のため)
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)

    # モデルの準備(箱を用意するイメージ)
    model = linear_model.LinearRegression()

    # モデルの学習 (訓練データを使って、最適な係数と切片を見つける)
    model.fit(X_train, y_train)

    # 学習済みモデルを使って予測
    y_train_pred = model.predict(X_train) # 訓練データで予測(学習に使ったデータ)
    y_test_pred = model.predict(X_test)   # テストデータで予測(モデルが初めて見るデータ)

    # モデルの性能評価 (R2スコア)
    # R2スコアは、モデルがデータの変動をどれだけ説明できているかを示す指標 (0~1の値、1に近いほど良い)
    r2_train = r2_score(y_train, y_train_pred)
    r2_test = r2_score(y_test, y_test_pred)

    # 係数 (各気象要素の影響度) の取得
    coefficients = pd.DataFrame(model.coef_, columns=['係数'], index=X.columns)

    # 結果の表示
    print(f"--- {model_type}モデル ---")
    print(f"訓練データのR2スコア: {r2_train:.4f}")
    print(f"テストデータのR2スコア: {r2_test:.4f}") # ← こちらのスコアが未知のデータに対する性能
    print("係数:")
    print(coefficients)
    print("-" * (len(model_type)+8))

    # 結果を辞書で返す (後で利用できるように)
    return {
        'model_type': model_type,
        'r2_train': r2_train,
        'r2_test': r2_test,
        'coefficients': coefficients,
        'model': model
    }

ポイント:
train_test_split(): データを訓練データとテストデータに分割します。モデルは訓練データを使って学習し、テストデータを使ってその性能(未知のデータに対する予測能力)を評価します。これは、モデルが学習データだけに過剰に適合(過学習)してしまうのを防ぐために重要です。
linear_model.LinearRegression(): Scikit-learnの線形回帰モデルクラスをインスタンス化(準備)します。
model.fit(X_train, y_train): 訓練データを使ってモデルを学習させます。
model.predict(X): 学習済みのモデルを使って、新しいデータXに対する予測値Yを計算します。
r2_score(y_true, y_pred): 実際の値 (y_true) とモデルの予測値 (y_pred) から決定係数(R2スコア)を計算します。R2スコアは、モデルがデータのばらつきをどれだけうまく説明できているかを示す指標で、1に近いほど良いモデルとされます。
model.coef_: 学習されたモデルの係数(各説明変数の重み)を取得します。

4.3 モデルの学習と評価の実行
準備した関数を使って、休日モデルと平日モデルをそれぞれ学習・評価します。

# 5. モデルの学習と評価
        print("\n--- モデル学習と評価開始 ---")
        holiday_results = train_evaluate_model(X_holiday, y_holiday, "休日")
        weekday_results = train_evaluate_model(X_weekday, y_weekday, "平日")
        print("--- モデル学習と評価終了 ---")

実行すると、休日モデルと平日モデルそれぞれの訓練データとテストデータに対するR2スコア、および各気象データの係数が表示されます。

Step 5: 結果の解釈と考察

さて、モデルの学習と評価が終わりました。結果を見てみましょう。


(作成したモデルの評価結果)

R2スコア:
訓練データとテストデータのR2スコアを比較します。テストデータのスコアが極端に低い場合、モデルが訓練データに過学習している可能性があります。
スコアの値自体が、モデルの予測精度を示します。例えば、R2スコアが0.6であれば、モデルが電気使用量の変動の約60%を気象データで説明できている、と解釈できます。どのくらいのスコアを目指すかは目的によりますが、まずは現状を確認しましょう。
R2スコアを見る限り、我が家の電気使用量は気象データだけではうまく説明できないようです(当然かもしれませんが・・・)。

係数:
各気象データの係数を見ると、どの要素が電気使用量にどれくらい影響を与えているかのヒントが得られます。
正の係数: その気象データの値が大きいほど、電気使用量が増加する傾向があることを示します。(例:夏の最高気温が高いと冷房使用で電気使用量増)
負の係数: その気象データの値が大きいほど、電気使用量が減少する傾向があることを示します。(例:冬の最低気温が高い(=寒さが緩む)と暖房使用が減り電気使用量減)
係数の絶対値の大きさ: 電気使用量への影響度の大きさを示唆しますが、変数のスケール(単位)が異なるため、単純比較には注意が必要です。(標準化などの前処理を行うと比較しやすくなります)
休日と平日で係数が異なる場合、それぞれの生活パターンの違いが影響していると考えられます。
考察の例:
休日/平日ともに、最高気温や最低気温の係数が(絶対値として)大きい場合、気温が電気使用量(冷暖房)に強く影響していることがわかります。
湿度の係数が正であれば、湿度が高いと(冷房や除湿機の使用が増え)電気使用量が増える傾向があるかもしれません。
風速の係数はどうでしょうか?風が強い日は窓を開けにくく冷暖房が増える、あるいは逆に換気が進んで減る、など様々な可能性が考えられます。

限界と今後の改善点:

今回は単純な線形回帰モデルを使いましたが、良い評価値を示すモデルは得られませんでした。電気使用量と天気の関係はもっと複雑かもしれません。
気象データ以外の要因(祝日、イベント、家族構成の変化など)も電気使用量に影響します。これらの情報を特徴量として追加できれば、モデルの精度が上がるかもしれません。
データの期間(今回は1年分)を長くしたり、他の年のデータで試したりすることで、より一般的な傾向が見えるかもしれません。

まとめ

今回は、家庭の電気使用量と気象データという身近なテーマで、Pythonを使ったデータ分析と機械学習モデル作成の基本的な流れを紹介しました。
データの準備: PandasでCSVを読み込み、日付処理やカラム名変更などの前処理を行った。
特徴量エンジニアリング: 曜日情報から休日/平日のフラグを作成し、データを分割した。
モデル構築: Scikit-learnで線形回帰モデルを準備し、訓練データで学習 (fit) した。
モデル評価: テストデータでモデルの性能 (R2スコア) を評価し、係数を確認した。
完璧なモデルを作るのは簡単ではありませんが、まずはデータを読み込んで、基本的なモデルを作ってみることで、データへの理解が深まり、次のステップ(モデル改善や特徴量追加など)に進むことができます。
公開されている機械学習用のデータセットは有用ですが、ご自身のデータや興味のある他のデータで行えばより親近感を持って取り組めるかもしれませんね。皆さんの参考になれば幸いです。

作成コード全体(参考)

import pandas as pd
import numpy as np
from sklearn import linear_model
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score

# --- 定数定義 ---
POWER_DATA_FILE = 'Year2020_EV.csv'
WEATHER_DATA_FILE = 'Weather2020.csv'
POWER_COLUMNS = {
    'Unnamed: 0': 'Date_Str', 'Unnamed: 1': 'EleVol',
    'Unnamed: 2': 'Weather_Summary', 'Unnamed: 3': 'Temp_Max_Power',
    'Unnamed: 4': 'Temp_Min_Power', 'Unnamed: 5': 'SunVol'
}
WEATHER_FEATURES = [
    '平均気温', '最高気温', '最低気温', '平均風速', '最大風速', '平均現地気圧', '平均湿度'
]
TARGET_VARIABLE = 'EleVol'
HOLIDAY_DAYS = ['Sat', 'Sun']

# --- 関数定義 ---
def load_and_preprocess_power_data(filepath: str, columns_map: dict) -> pd.DataFrame:
    try:
        df = pd.read_csv(filepath, header=5, encoding='shift-jis')
    except FileNotFoundError:
        print(f"エラー: ファイルが見つかりません - {filepath}")
        return pd.DataFrame()
    except Exception as e:
        print(f"エラー: ファイル読み込み中に問題が発生しました - {filepath}, {e}")
        return pd.DataFrame()
    df.rename(columns=columns_map, inplace=True)
    df['Date'] = pd.to_datetime('2020/'+df['Date_Str'])
    df.insert(1, 'DayOfWeek', df['Date'].dt.strftime("%a"))
    df.drop('Date_Str', axis=1, inplace=True)
    df['IsHoliday'] = df['DayOfWeek'].isin(HOLIDAY_DAYS)
    return df

def load_and_preprocess_weather_data(filepath: str, features: list) -> pd.DataFrame:
    try:
        df = pd.read_csv(filepath, header=1, encoding='shift-jis')
    except FileNotFoundError:
        print(f"エラー: ファイルが見つかりません - {filepath}")
        return pd.DataFrame()
    except Exception as e:
        print(f"エラー: ファイル読み込み中に問題が発生しました - {filepath}, {e}")
        return pd.DataFrame()
    df_reversed = df.iloc[::-1].reset_index(drop=True)
    df_selected = df_reversed[features]
    return df_selected

def train_evaluate_model(X: pd.DataFrame, y: pd.Series, model_type: str) -> dict:
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
    model = linear_model.LinearRegression()
    model.fit(X_train, y_train)
    y_train_pred = model.predict(X_train)
    y_test_pred = model.predict(X_test)
    r2_train = r2_score(y_train, y_train_pred)
    r2_test = r2_score(y_test, y_test_pred)
    coefficients = pd.DataFrame(model.coef_, columns=['係数'], index=X.columns)
    print(f"--- {model_type}モデル ---")
    print(f"訓練データのR2スコア: {r2_train:.4f}")
    print(f"テストデータのR2スコア: {r2_test:.4f}")
    print("係数:")
    print(coefficients)
    print("-" * (len(model_type)+8))
    return {
        'model_type': model_type, 'r2_train': r2_train, 'r2_test': r2_test,
        'coefficients': coefficients, 'model': model
    }

# --- メイン処理 ---
power_df = load_and_preprocess_power_data(POWER_DATA_FILE, POWER_COLUMNS)
weather_df = load_and_preprocess_weather_data(WEATHER_DATA_FILE, WEATHER_FEATURES)

if power_df.empty or weather_df.empty:
    print("データの読み込みに失敗したため、処理を中断します。")
else:
    if len(power_df) == len(weather_df):
        merged_df = pd.concat([power_df.reset_index(drop=True), weather_df.reset_index(drop=True)], axis=1)
        print("データの結合が完了しました。")
        print("結合後のデータサンプル:"); print(merged_df.head())
        

        if not merged_df.empty:
            holiday_df = merged_df[merged_df['IsHoliday'] == True]
            weekday_df = merged_df[merged_df['IsHoliday'] == False]
            print(f"\n休日データ数: {len(holiday_df)}")
            print(f"平日データ数: {len(weekday_df)}")

            X_holiday = holiday_df[WEATHER_FEATURES]
            y_holiday = holiday_df[TARGET_VARIABLE]
            X_weekday = weekday_df[WEATHER_FEATURES]
            y_weekday = weekday_df[TARGET_VARIABLE]

            print("\n--- モデル学習と評価開始 ---")
            holiday_results = train_evaluate_model(X_holiday, y_holiday, "休日")
            weekday_results = train_evaluate_model(X_weekday, y_weekday, "平日")
            print("--- モデル学習と評価終了 ---")
    else:
        print(f"エラー: 電力データ ({len(power_df)}行) と気象データ ({len(weather_df)}行) の行数が一致しません。結合できません。")

Discussion