tottocoslowlifer / tide

2024年のプロジェクト
0 stars 0 forks source link

データ分析: 欠損値補完について2 #5

Closed victor-von-pooh closed 5 months ago

victor-von-pooh commented 6 months ago

こちらより

欠損値補完の処理として, メタ情報を考慮した補完を行いたい.

2011年1月1日〜2021年12月31日までの11年間のデータを用いて, 以下のメタ情報を付加した重回帰分析によって補完する方法を考えてみる.

重回帰分析

重回帰分析は, 説明変数 $x_i (i = 1, ..., n)$ と目的変数 $y$ について, 偏回帰係数 $a_i (i = 0, 1, ..., n)$ をモデルの学習によって決定し,

$$y = a_0 + a_1x_1 + \cdots + a_nx_n$$

という回帰式を作ることを目的とする分析である.

復習資料:

  1. 機械学習入門③重回帰分析を学ぼう
  2. tus-os-2023/Data_Analysis
  3. Scikit-Learn, Linear Regression

今回は, 目的変数を時刻 $t$ の潮位に, 説明変数を時刻 $t$ の,

の計7個とする.

対応していただくこと

まずは2種類のファイル

を呼び出し, tide/data/Moji_Tide_2011-2021/csv/9010_2011-2021.csv と上手く時刻毎に結合された DataFrame を作成してみましょう.

tottocoslowlifer commented 6 months ago

ありがとうございます. Moji_analysis.ipynbを更新しましたので,ご確認の程よろしくお願いいたします.

victor-von-pooh commented 6 months ago

@tottocoslowlifer 確認しました. 非常に良くできていると思いました. 3点だけコメントさせて頂きます.

2つ目については関数を作成するのが良いかなと思います.

3つ目は少し問題があります. 理由としては, 潮位のデータの1日が0〜23時なのに対し, 下関の気象データは1〜24時になっているからです.

以上のご対応をよろしくお願い致します.

tottocoslowlifer commented 6 months ago

対応が完了いたしました. ご確認の程よろしくお願いいたします.

victor-von-pooh commented 6 months ago

お疲れ様です. ご対応いただきありがとうございます. 大変申し訳ございませんが, 色々と考えた方がいいことがあるみたいなので, このセクションを最初からやり直しましょう.

まず, 前提として関数はセクション冒頭にまとめましょう. この際, 関数間は2行空けます. また, 返り値は基本的には用意しましょう.

やっていただくこと

順を追ってやってみましょう.

  1. 3つの DataFrame を作成する関数を作成する
  2. 門司の DataFrame と月齢の DataFrame を統合する
  3. 2で作ったデータをフラットにし, 気象の DataFrame と合わせる

それぞれでの注意事項を説明します.

3つの DataFrame を作成する関数を作成する

これらを呼び出す関数を作成します.

門司の DataFrame

6個目のセルの出力と同じものが出てくるような関数を用意しましょう. 基本的に6個目のセルまでにやったことをまとめれば良いので,

def call_moji_tide() -> pd.DataFrame:
    df = pd.read_csv("../data/Moji_Tide_2011-2021/csv/9010_2011-2021.csv").drop("9010(門司の観測値コード)", axis=1)

    date_cols = ["20xx年", "xx月", "xx日"]

    df["年月日"] = [
        datetime.date(
            2000 + int(df["20xx年"][i]),
            int(df["xx月"][i]),
            int(df["xx日"][i])
        )for i in range(len(df))
    ]
    df = df.drop(date_cols, axis=1)

    date_keys = []
    for i in range(len(df)):
        if df["年月日"][i] not in date_keys:
            date_keys.append(df["年月日"][i])

    am_cols = [f"{i}時の潮高値(cm)" for i in range(12)]
    pm_cols = [f"{i}時の潮高値(cm)" for i in range(12,24)]

    new_df_list = []
    for i in range(len(date_keys)):
        tmp = df[df["年月日"] == date_keys[i]].reset_index().drop("index", axis=1)
        am_data = [item for item in tmp[tmp["1(午前)/2(午後)"] == 1][am_cols].values[0]]
        pm_data = [item for item in tmp[tmp["1(午前)/2(午後)"] == 2][am_cols].values[0]]
        new_df_list.append(am_data + pm_data)

    time_cols = am_cols + pm_cols
    df = pd.concat([pd.DataFrame(date_keys, columns=["年月日"]), pd.DataFrame(new_df_list, columns=time_cols)], axis=1)

    date_dropped_df = df.drop("年月日", axis=1)
    index = list(date_dropped_df.dropna(how="all").index)

    return df.iloc[index].reset_index().drop("index", axis=1)

こんな感じで良いと思います.

月齢の DataFrame

こちらは,

  1. df = pd.read_csv("../data/Moon_2011-2021/Mooncal_2011-2021.csv") で生の DataFrame を呼び出す
  2. ["こよみ", "気象庁", "MIRC"] の列について, それぞれのデータを {"小潮": 1, "中潮": 2, "大潮": 3, "若潮": 4, "長潮": 5} のようにラベリングし, 整数値を格納する

    1. ラベリング用の関数を作ると楽かも

      def get_label(df: pd.DataFrame, col: str) -> pd.DataFrame:
          labels_dict = {"小潮": 1, "中潮": 2, "大潮": 3, "若潮": 4, "長潮": 5}
      
          col_list = df[col].to_list()
          labels = [labels_dict[item] for item in col_list]
          df = df.drop(col, axis=1)
          df[col] = labels
      
          return df
  3. ["年月日"]datetime.date の型に直す

という作業をしましょう.

スクリーンショット 2024-03-12 21 04 48

こんな感じの DataFrame ができると良いかと思います.

気象の DataFrame

こちらは,

  1. df = pd.read_csv("../data/Shimonoseki_2011-2021/Shimonoseki.csv").rename(columns={"Unnamed: 0": "年月日時"}) で生の DataFrame を呼び出す
  2. ["年月日時"]datetime.datetime の型に直す
    1. datetime.datedatetime.time を用意し, datetime.datetime.combine() メソッドを使うと簡単
    2. この際, 24時の表記を翌日の0時に直す
    3. datetime.timedelta を使うと楽
  3. ["降水量(mm)", "気温(℃)"] について, float 型で扱えるようにデータそのものを書き換える
    1. -- は0, /// は欠損値, その他はその値(float 型)
    2. 欠損値を float 型として使うには float("nan") とする
    3. その他の中に 17.8) などのエラーが含まれるのでこれをちゃんと通るように処理するのがポイント

という作業をしましょう. こちらも

スクリーンショット 2024-03-12 21 21 55

こんな感じの DataFrame ができると良いかと思います.

最後に

一つのセルで,

df = call_moji_tide()
moon_df = call_mooncal()
shimonoseki_df = call_shimonoseki()

のように呼び出せると良いと思います.

門司の DataFrame と月齢の DataFrame を統合する

merged_df = pd.merge(df, moon_df, on="年月日", how="inner")

おそらくこの1行で出来ます.

2で作ったデータをフラットにし, 気象の DataFrame と合わせる

ここで, データを一元化する関数を作成しましょう. 引数に how などを入れて, 同じ関数内に if 文の分岐を入れ,

これらを選択できるようにしましょう.

スクリーンショット 2024-03-12 21 29 51

こんな感じの DataFrame ができると良いかと思います.

victor-von-pooh commented 6 months ago

すみません, 補足です.

次の日の値と差を取り, 24分割する

こちらについて, 最終日(2021-12-31)は欠損値ではなく前日と同じ差分を足して埋めてみましょう.

tottocoslowlifer commented 6 months ago

お疲れ様です.先ほど,notebookをコミットいたしました.

最後の

2で作ったデータをフラットにし, 気象の DataFrame と合わせる

に取り組んでいるところなのですが, 30個目のセルのように,meta_df年月日時の型がTimestamp型?になってしまい,行き詰っているところです.

よろしくお願いいたします.

victor-von-pooh commented 6 months ago

@tottocoslowlifer ありがとうございます. 迅速な対応で助かります.

全体的によく書けています. 関数の中身も的確です◎

以下コメントになります.

コメント

まずはコードを確認してのコメントになります.

1個目のセル

import datetime as dt とエイリアスをつけることがあるのは知らなかったです. 冗長な書き方が防げて良いですね◎

現在, import numpy as np がどこにも使われていないようなので, この1文はコメントアウトしてしまいましょうか(一応後で出てくるかもしれないため).

24個目のセル

from datetime import timedelta は無くても良いかなと思います. timedelta を使うのが call_shimonoseki() 関数の上から9行目の1箇所だけなので, ここを

dt_ymd = dt.date(dt_ymd.year, dt_ymd.month, dt_ymd.day) + dt.timedelta(days=1)

とするのが良いと思います. どうしても必要であれば, 1個目のセルに入れましょう.

call_mooncal() 関数及び call_shimonoseki() 関数は非常に良く書けていて素晴らしいです◎

一方で, full_merge() 関数ですが,

という感じだと思います. ただし, ここで使われた処理のアイデアは良く, これを応用して merged_df をフラット(1時間毎の DataFrame)にしましょう.

26個目のセル

ここは確認として moon_df を呼び出したのかと思われますが, アップロードのときは無くても大丈夫です. もしくは, 3個セルを使って dfshimonoseki_df も表示させると良いかなと思います(moon_df のみあるのが不自然という話でした).

29個目以降のセル

これらは今調整中と見ています. 出来次第関数は24個目のセルの中に入れていきましょう.

質問に対するコメント

30個目のセルのように,meta_df年月日時の型がTimestamp型?になってしまい,行き詰っているところです.

こちらに対する回答ですが, Timestamp 型になることが理想でした(なので間違ってはいないです). ではどのようにすれば良いかということですが, 27個目のセル同様, pd.merge(df_1, df_2, on="年月日時", how="inner") を使ってみましょう. この df_1 には merged_df をフラットにした DataFrame を, df_2 には shimonoseki_df を入れるという感じで繋げます. このとき, pd.merge() の2つの引数 onhow はそれぞれ,

という役割を担っています.

現在, merged_df["年月日"] にも shimonoseki_df["年月日時"] にも, Timestamp 型で要素が含まれているのでこのカラムを基準にマージできることを目指しましょう.

tottocoslowlifer commented 6 months ago

お疲れ様です. 月齢データと門司データを合わせてフラット化したもの(moon_to_flat(...))に対し、最後に気象データ(shimonoseki_df)をマージしようとしているのですが,

スクリーンショット 2024-03-14 14 13 12

というエラーが出てしまいました.

一方で,

スクリーンショット 2024-03-14 14 21 44

ひとつ下のセル(31と書かれたもの)を見る限り,型は揃っているように見えます.

何が原因なのでしょうか. よろしくお願いいたします.

victor-von-pooh commented 6 months ago

@tottocoslowlifer ご対応ありがとうございます. 以下コメントになります.

コメント

いくつかポイントがあります.

まず, flat_merge() 関数の引数に shimonoseki_df を使う必要はないと思います. 使い方としては, 関数内1行目〜6行目で shimonoseki_df年月日時 をデータとしてまとめていることかと思われますが,

meta_append = []

for i in range(len(shimonoseki_df)):
    ymd = shimonoseki_df.loc[i,"年月日時"]
    if dt.datetime.date(ymd) in list(df["年月日"]):
        meta_append.append([ymd])

この中にもいくつか注意事項があります.

  1. if dt.datetime.date(ymd) in list(df["年月日"]): の1行の中に df があるのは NG
  2. この部分は,
    meta_append = [[shimonoseki_df.loc[i,"年月日時"]] for i in range(len(shimonoseki_df)) if dt.datetime.date(shimonoseki_df.loc[i,"年月日時"]) in list(merged_df["年月日"])]

    の1行で書ける

  3. この状態の meta_append は要らない

という感じです.

続いて, 一旦 moon_to_flat() 関数の話をします. こちらは手順として flat_merge() 関数と地続きなので, 関数を分ける必要はないと思います. また, how については, 1 or 2 というのはあまり良くないです. 理由としては, それ以外を指定してしまった場合の処理が考慮されていないからです. ここは, 差分を取る場合は "diff" とし, それ以外はそのまま入れるというような感じにしましょう.

ここまでを受けて, 一つの関数 flatten() を作成する手順を提案します.

flatten() 関数作成に向けて

3つに分けて紹介します.

  1. ["年月日", "潮位"] に関して
  2. ["こよみ", "気象庁", "MIRC"] に関して
  3. ["黄経差", "月齢"] に関して

["年月日", "潮位"] に関して

まず, merged_df は,

スクリーンショット 2024-03-15 15 34 25

のようになっているので, "年月日" のカラムと "n時の潮高値(cm)" と書かれているカラムを利用します.

time_cols = [f"{i}時の潮高値(cm)" for i in range(24)]

times = []
data = []

まずこのように初期状態を用意します. ここに, merged_df の長さ分の for 文を使って append していきます.

for i in range(len(merged_df)):
    times += [dt.datetime.combine(merged_df["年月日"][i], dt.time(time)) for time in range(24)]
    data += merged_df[time_cols].iloc[i].to_list()

これで出来上がりです. 以下にまとめてみたので, 一旦どのような DataFrame が出来ているか確認してみましょう.

time_cols = [f"{i}時の潮高値(cm)" for i in range(24)]

times = []
data = []

for i in range(len(merged_df)):
    times += [dt.datetime.combine(merged_df["年月日"][i], dt.time(time)) for time in range(24)]
    data += merged_df[time_cols].iloc[i].to_list()

pd.DataFrame({"年月日時": times, "潮位": data})

スクリーンショット 2024-03-15 15 42 28

このようになるかと思われます.

["こよみ", "気象庁", "MIRC"] に関して

こちらは特に時間毎の値を変更する必要はないので, そのまま引き伸ばすようにしましょう.

label_cols = ["こよみ", "気象庁", "MIRC"]
labels = []

for col in label_cols:
    tmp = []
    for i in range(len(merged_df)):
        tmp += [merged_df[col][i] for _ in range(24)]
    labels.append(tmp)

pd.DataFrame({"こよみ": labels[0], "気象庁": labels[1], "MIRC": labels[2]})

スクリーンショット 2024-03-15 15 45 20

["黄経差", "月齢"] に関して

ここは, how という引数を使って分岐を入れます. まず, 黄経差と月齢のデータリストを表す変数 emd, mp を定義します.

emd = []
mp = []

続いて, how="diff" 以外はデータを引き伸ばすようにするので, if 文の処理を how="diff" の場合に, else 文の中をそのままの場合にします.

how="diff" の場合,

for i in range(len(merged_df)):
    if i == len(merged_df) - 1:
        emd_diff = (merged_df["黄経差"][i] - merged_df["黄経差"][i - 1]) / 24
        mp_diff = (merged_df["月齢"][i] - merged_df["月齢"][i - 1]) / 24

    else:
        emd_diff = (merged_df["黄経差"][i + 1] - merged_df["黄経差"][i]) / 24
        mp_diff = (merged_df["月齢"][i + 1] - merged_df["月齢"][i]) / 24

    emd += [merged_df["黄経差"][i] + emd_diff * j for j in range(24)]
    mp += [merged_df["月齢"][i] + mp_diff * j for j in range(24)]

という処理をします. この for 文内の if 文の分岐では, 最終日については前日との差をそのまま使うようにしています.

それ以外の場合,

for i in range(len(merged_df)):
    emd += [merged_df["黄経差"][i] for _ in range(24)]
    mp += [merged_df["月齢"][i] for _ in range(24)]

で良いと思います. まとめると,

how = "diff"

emd = []
mp = []

if how == "diff":
    for i in range(len(merged_df)):
        if i == len(merged_df) - 1:
            emd_diff = (merged_df["黄経差"][i] - merged_df["黄経差"][i - 1]) / 24
            mp_diff = (merged_df["月齢"][i] - merged_df["月齢"][i - 1]) / 24

        else:
            emd_diff = (merged_df["黄経差"][i + 1] - merged_df["黄経差"][i]) / 24
            mp_diff = (merged_df["月齢"][i + 1] - merged_df["月齢"][i]) / 24

        emd += [merged_df["黄経差"][i] + emd_diff * j for j in range(24)]
        mp += [merged_df["月齢"][i] + mp_diff * j for j in range(24)]

else:
    for i in range(len(merged_df)):
        emd += [merged_df["黄経差"][i] for _ in range(24)]
        mp += [merged_df["月齢"][i] for _ in range(24)]

pd.DataFrame({"黄経差": emd, "月齢": mp})

スクリーンショット 2024-03-15 15 54 45

というようになると思います.

flatten() 関数

今までのをまとめます.

def flatten(df: pd.DataFrame, how: str="same") -> pd.DataFrame:
    time_cols = [f"{i}時の潮高値(cm)" for i in range(24)]
    label_cols = ["こよみ", "気象庁", "MIRC"]

    times = []
    data = []
    labels = []
    emd = []
    mp = []

    for i in range(len(df)):
        times += [dt.datetime.combine(df["年月日"][i], dt.time(time)) for time in range(24)]
        data += df[time_cols].iloc[i].to_list()

    for col in label_cols:
        tmp = []
        for i in range(len(df)):
            tmp += [df[col][i] for _ in range(24)]
        labels.append(tmp)

    if how == "diff":
        for i in range(len(df)):
            if i == len(df) - 1:
                emd_diff = (df["黄経差"][i] - df["黄経差"][i - 1]) / 24
                mp_diff = (df["月齢"][i] - df["月齢"][i - 1]) / 24

            else:
                emd_diff = (df["黄経差"][i + 1] - df["黄経差"][i]) / 24
                mp_diff = (df["月齢"][i + 1] - df["月齢"][i]) / 24

            emd += [df["黄経差"][i] + emd_diff * j for j in range(24)]
            mp += [df["月齢"][i] + mp_diff * j for j in range(24)]

    else:
        for i in range(len(df)):
            emd += [df["黄経差"][i] for _ in range(24)]
            mp += [df["月齢"][i] for _ in range(24)]

    return pd.DataFrame(
        {
            "年月日時": times,
            "潮位": data,
            "黄経差": emd,
            "月齢": mp,
            "こよみ": labels[0],
            "気象庁": labels[1],
            "MIRC": labels[2],
        }
    )

そして, このようにします.

df = call_moji_tide()
moon_df = call_mooncal()
shimonoseki_df = call_shimonoseki()

merged_df = pd.merge(df, moon_df, on="年月日", how="inner")
flattened_df_same = flatten(merged_df)
flattened_df_diff = flatten(merged_df, how="diff")

スクリーンショット 2024-03-15 16 08 50

質問に対するコメント

print(type(merged_df["年月日"][0]))

これを見ると,

<class 'datetime.date'>

となっているようですね. また,

print(type(shimonoseki_df["年月日時"][0]))

を見ると,

<class 'datetime.datetime'>

のようになっています. すなわち, 門司のデータと月齢データをマージした時点では, merged_df["年月日"] のデータ及び shimonoseki_df["年月日時"] のデータの型は <class 'pandas._libs.tslibs.timestamps.Timestamp'> ではなく, datetime のクラスになっているようですね. つまり, Timestamp 型というのは間違いだったようです.

また,

ひとつ下のセル(31と書かれたもの)を見る限り,型は揃っているように見えます.

こちらについては,

shimonoseki_df["年月日時"]

を実行すると,

スクリーンショット 2024-03-15 16 33 26

と出てきており, dtype: object になっています. 一方,

moon_to_flat(2,meta_df,merged_df)["年月日時"]

を実行すると,

スクリーンショット 2024-03-15 16 35 04

なので dtype: datetime64[ns] と気象データとは異なりますね. 型が完全に一致しなかったということですね.

上のコメントを施せばおそらくマージできるようになると思いますが, flatten() 関数を作らない場合はまた別途相談しましょう.

tottocoslowlifer commented 6 months ago

ありがとうございます. コメントアウトを加えた上で再度コミットいたしましたので,ご確認の程よろしくお願いいたします.

victor-von-pooh commented 6 months ago

確認できました. ご対応ありがとうございます.

それでは, flattened_df_same で良いので, shimonoseki_df とマージしてみましょう.

preprocessed_df = pd.merge(flattened_df_same, shimonoseki_df, on="年月日時", how="inner")
preprocessed_df

これを実行し, エラーが出ないかを確認しましょう. これができたら重回帰分析にかけてみましょう.

X: ["黄経差", "月齢", "こよみ", "気象庁", "MIRC", "降水量(mm)", "気温(℃)"] y: ["潮位"]

この際, ["潮位", "降水量(mm)", "気温(℃)"] には欠損値が含まれていることに注意しましょう.

tottocoslowlifer commented 6 months ago

先ほどと同じエラーが出てしまいました.

スクリーンショット 2024-03-15 17 21 35
victor-von-pooh commented 6 months ago

@tottocoslowlifer 原因を調査してみました. どうやら, call_shimonoseki() 関数内の

for i in range(len(df)):
    time = df.loc[i, "年月日時"]
    dt_ymd = dt.datetime.strptime(time.split("日")[0]+"日", "%Y年%m月%d日")
    dt_time = time.split("日")[1]

    if dt_time == "24時":
        dt_ymd = dt.date(dt_ymd.year, dt_ymd.month, dt_ymd.day) + dt.timedelta(days=1)
        dt_time = "0時"
    dt_time = dt.datetime.strptime(dt_time, "%H時")

    dt_ymd = dt.date(dt_ymd.year, dt_ymd.month, dt_ymd.day)
    dt_time = dt.time(dt_time.hour)
    df.loc[i, "年月日時"] = dt.datetime.combine(dt_ymd, dt_time)

の最後の行で, 直接値を書き換えていたのが良くなかったみたいです. 例えば, for 文の前に

times = []

というのを用意し, 上記最後の行を

times.append(dt.datetime.combine(dt_ymd, dt_time))

のようにして一旦リストとして持っておいて, 後で DataFrame にするというのが良いかなと思います. なるべく call_shimonoseki() 関数の原型を崩さないように, かつフォーマットを揃えるように次のように書き換えてみました.

def call_shimonoseki() -> pd.DataFrame:
    df = pd.read_csv("../data/Shimonoseki_2011-2021/Shimonoseki.csv").rename(columns={"Unnamed: 0": "年月日時"})

    times = []
    for i in range(len(df)):
        time = df.loc[i, "年月日時"]
        dt_ymd = dt.datetime.strptime(time.split("日")[0]+"日", "%Y年%m月%d日")
        dt_time = time.split("日")[1]

        if dt_time == "24時":
            dt_ymd = dt.date(dt_ymd.year, dt_ymd.month, dt_ymd.day) + dt.timedelta(days=1)
            dt_time = "0時"
        dt_time = dt.datetime.strptime(dt_time, "%H時")

        dt_ymd = dt.date(dt_ymd.year, dt_ymd.month, dt_ymd.day)
        dt_time = dt.time(dt_time.hour)
        times.append(dt.datetime.combine(dt_ymd, dt_time))

    rain = []
    temp = []
    item_dict = {"降水量(mm)": rain, "気温(℃)": temp}
    for i in range(len(df)):
        for item in ["降水量(mm)", "気温(℃)"]:
            if df.loc[i, item] == "--":
                item_dict[item].append(float(0))
            elif df.loc[i, item] == "///":
                item_dict[item].append(float("nan"))
            else:
                df.loc[i, item] = "".join(re.findall(r"\d+\.\d+", str(df.loc[i, item])))
                if df.loc[i, item] == "":
                    df.loc[i, item] =- float("nan")
                item_dict[item].append(float(df.loc[i, item]))

    return pd.DataFrame({"年月日時": times, "降水量(mm)": rain, "気温(℃)": temp})

書き方に質問があればお答えします. もし納得いただけたら, 同様にして call_mooncal() 関数も直してみましょう.

これでしっかりとデータの結合が出来るはずです. よろしくお願いいたします.

tottocoslowlifer commented 5 months ago

できました!!!

victor-von-pooh commented 5 months ago

お疲れ様です.

call_mooncal() 関数の中で, ["こよみ", "気象庁", "MIRC"] のラベリングが抜けてしまっているようですので, そちらもご対応よろしくお願い致します.

引き続き, こちらの重回帰分析にかけるところまでをよろしくお願い致します.

tottocoslowlifer commented 5 months ago

お疲れ様です.

  1. 訓練データには欠損値を含まない期間のものを使用する
  2. テストデータに関しても,欠損値を含むものは取り除く

という方向性で進めております. セル31で, 上の2を実装したつもりなのですが,セル32のコメントアウトの箇所を戻すと,以下のようなエラーが出ます.

スクリーンショット 2024-03-19 15 59 23

欠損値を含むデータを取り除ききれていないということなのでしょうか. よろしくお願いいたします.

victor-von-pooh commented 5 months ago

ありがとうございます. いくつかコメントがあります.

コメント

24個目のセル

非常に細かいことで申し訳ありません. 各所の黄経差を表す単語, "longtitude" $\rightarrow$ "longitude" に直しましょう.

25個目のセル

ここは消してしまいましょう.

27, 28個目のセル

このままで大丈夫なのですが, "date" の情報は DataFrame どうしのマージの際に必要だったものなので,

preprocessed_df_diff = pd.merge(flattened_df_diff, shimonoseki_df, on="date", how="inner").drop("date", axis=1)

のようにして削除してしまいましょう.

重回帰分析のセクションについて

こちらは「欠損値補完」の「メタ情報を用いた補完」の中に入っている項目なので, 「#」は4つにしましょう.

29個目のセル

欠損値の確認は大事ですね◎

30個目のセル

ヒストグラムを出していただいていますが, 意図があまりわかりませんでした. ここから相関係数の算出や, PCA(主成分分析)などをして特徴量の重要度を割り出すなどをしないのであれば不要な気もします.

31個目のセル

いただいた質問ですが, なぜ2015〜2016年の欠損値が無いデータで学習する際にエラーが起きるかというと, この期間の中に "rainfall(mm)", temperature(℃) の欠損値があるからです. これらを削除しない限りは上手くいきません. また, 欠損値は落としてから train_test_split() をしましょう.

訓練データには欠損値を含まない期間のものを使用する

こちらですが, 基本的にデータは使えるだけ使いましょう. なぜなら, 今の欠損値補完は時系列データとしてでなく, テーブルデータとして見ているからです. 時間依存が無いので, より多くのデータを用いることができます.

手順としては,

  1. "rainfall(mm)", temperature(℃) の欠損が見られるデータを全て削除する
  2. "tide level" の欠損が見られるデータを全て削除する
  3. X, y を作る
    X = preprocessed_df_same[["longtitude", "moon phase", "calendar", "JMA", "MIRC", "rainfall(mm)", "temperature(℃)"]]
    y = preprocessed_df_same["tide level"]
  4. 重回帰分析にかける
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.50, random_state=8192)
    regressor = LinearRegression().fit(X_train, y_train)

train_test_split()random_state を好きな値で構いませんので設定しましょう(shuffle=False は要らないです).

tottocoslowlifer commented 5 months ago

ありがとうございます. コミットができましたので,ご確認の程よろしくお願いいたします.

victor-von-pooh commented 5 months ago

@tottocoslowlifer ご対応ありがとうございます. 非常に綺麗なコーディングです◎ 2点コメントがあります.

これを直して次の作業をしていただきます.

作業内容

作ったモデル regressorX_train を使って predict() し, y_train との誤差を算出しましょう. 誤差については平均絶対誤差と平均二乗誤差を使ってみましょう.

また, ここまでを関数化してみましょう. train_predict() などとし, 引数を Xy にして, 関数内で2種類の誤差について print() し, 返り値にモデルを持ってきましょう. すなわち, return regressor とします.

予測値についてはそれぞれ四捨五入で整数値に変換してから誤差を計算しましょう.

tottocoslowlifer commented 5 months ago

ありがとうございます. 再度コミットいたしました.

victor-von-pooh commented 5 months ago

@tottocoslowlifer ありがとうございます. コードが簡潔で素晴らしいです◎ すみません, 追加で少し補正したいことがあります.

これが終わったら, 今までのことについて質問があればお答えします.

tottocoslowlifer commented 5 months ago

完了いたしました. 質問に関しては,その都度,聞かせていただいていたので,今は特にありません. ありがとうございます.

victor-von-pooh commented 5 months ago

@tottocoslowlifer ありがとうございます. 最後に1点のみ, 忘れていたことがありましたのでご対応のほどよろしくお願い致します.

次のように変えれば大丈夫です.

def train_predict(X, y, split_rate):
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=split_rate, random_state=316)
    regressor = LinearRegression().fit(X_train, y_train)

    y_pred = regressor.predict(X_test)
    for i in range(len(y_pred)):
        y_pred[i] = round(y_pred[i])

    mae = np.mean(abs(y_pred - y_test))
    mse = np.mean((y_pred - y_test) ** 2)
    print("MAE train: ", mae)
    print("MSE train: ", mse)

    return regressor
tottocoslowlifer commented 5 months ago

ありがとうございます.コミットいたしました. 一点だけ質問なのですが,重回帰分析の前に 標準化を行うべきケースと行わなくてもよいケース の違いがわからずにいます.

「説明変数の数字のスケールが異なる場合は標準化を行う」という認識でいたのですが,今回の場合なら必要ないのでしょうか.

よろしくお願いいたします.

victor-von-pooh commented 5 months ago

ご対応ありがとうございました.

一点だけ質問なのですが,重回帰分析の前に 標準化を行うべきケースと行わなくてもよいケース の違いがわからずにいます.

非常に良い質問だと思います. こちらについては, 「重回帰分析『だから』標準化が要らない」と回答させていただきます. 他の手法, 例えばニューラルネットワークなどであれば, そのスケールの違いから学習が困難になる場合が多いのですが, 重回帰分析では,

$$ y = \boldsymbol{a} \cdot \boldsymbol{X}, \quad \boldsymbol{a} = \left( a_1, a_2, \cdots, a_n \right), \quad \boldsymbol{X} = \left( x_1, x2, \cdots, x{n-1}, 1 \right) $$

と表され, 偏回帰係数 $\boldsymbol{a}$ を学習します. このとき, $a_n$ は切片を表します. 仮に $x_i$ についてスケーリングするとしましょう. $std_i$ をこの $x_i$ についての標準偏差, $\bar{x_i}$ を $x_i$ についての平均とすると, 標準化の式は

$$ standard_i = \frac{x_i - \bar{x_i}}{std_i} $$

となります. ここで, $standard_i$ が新しい $x_i$ の代わりになるのですが, 一方で

$$ x_i = standard_i \times std_i + \bar{x_i} $$

と表せ, $x_i$ の標準偏差及び平均は標本空間内では定数と見做せるため, ここに $a_i$ をかけると

$$ a_i \times x_i = a_i \times \left( standard_i \times std_i + \bar{x_i} \right) = \left( a_i \times std_i \right) \times standard_i + a_i \times \bar{x_i} $$

となり, 式全体への寄与としては, $a_i$ に定数 $std_i$ をかけ, $a_n$ に $a_i \times \bar{x_i}$ を足すだけになります. つまり, ただ式を線形変換しただけなので, 結果の予測値については影響を及ぼさないということです. これが重回帰分析の強みであり, 今回の欠損値補完のために手法として選んだ理由になります.

tottocoslowlifer commented 5 months ago

なるほど,重回帰分析の場合は,標準化の操作がスケールへ何ら影響しないということであっていますでしょうか.

ご回答ありがとうございました.

victor-von-pooh commented 5 months ago

重回帰分析の場合は,標準化の操作がスケールへ何ら影響しないということであっていますでしょうか.

標準化をすることはデータの前処理としての過程であり, 特徴量のスケールを変更しているので, この考え方は少し違うように感じます. 「標準化をする」 = 「特徴量のスケールを揃える」です. 一方で, 重回帰分析による予測の際は, このスケールの「違い」についての影響を受けないということです.

tottocoslowlifer commented 5 months ago

ありがとうございます. 表現が不適切でした. 標準化の操作によりスケールを変更することが,結果に影響しないということでしょうか?

victor-von-pooh commented 5 months ago

標準化の操作によりスケールを変更することが,結果に影響しないということでしょうか?

そうですね, それは正しいと思います.

tottocoslowlifer commented 5 months ago

分かりました.ありがとうございます.

他の事柄に関しては,現時点では質問がないです. よろしくお願いいたします.

victor-von-pooh commented 5 months ago

ありがとうございます.

それでは次の話に参りたいと思いますが, 今回の重回帰分析の結果を見て「思ったほど良くない」と感じていただけると嬉しいです. では, 本当にこの方法でモデルを作ったことが良くなかったのかを考察してみましょう.

振り返り

まず, 「メタ情報を用いた補完」では, 3種類の DataFrame の情報を用いてそれらを結合し, 不適切なデータを取り除いて, 次のように説明変数と目的変数を設定しました.

X : ["longitude", "moon phase", "calendar", "JMA", "MIRC", "rainfall(mm)", "temperature(℃)"] y : ["tide level"]

この Xy を重回帰分析で学習させました. しかし, よく考えると説明変数の中には目的変数の値に直接影響させるファクターはなく, 補助的なものが多く含まれているだけといった感じです.

過去の情報を使ってみる

説明変数の中に, 過去(例えば直近10時間)の潮位のデータを特徴量として加えてみるということを考えます. 具体的には, preprocessed_df を作った後, "tide level" を1時間ずつずらしたカラムを用意し(上の例だと10個シフト), 欠損するデータを全て省いてから説明変数を次の n + 7 個にします.

X : ["tide level shift 1h", "tide level shift 2h", ..., "tide level shift nh", "longitude", "moon phase", "calendar", "JMA", "MIRC", "rainfall(mm)", "temperature(℃)"]

作業内容

追加で1点

先ほどの, train_predict() 関数の print() 部分にある train の文字を削除していただきたいです.

print("MAE: ", mae)
print("MSE: ", mse)
tottocoslowlifer commented 5 months ago

対応が完了いたしました. ご確認の程よろしくお願いいたします.

victor-von-pooh commented 5 months ago

@tottocoslowlifer ありがとうございます. 以下コメントになります.

コメント

精度も非常に高くなっていて, 処理としては概ね正しいです. 一方で最後のところで, 今まで使っていた降水量等のデータがなくなっているので, コード面でいくつか直していただきたいです. ご対応をよろしくお願い致します.

24個目のセル

tide_shift() 関数について, 以下の変更を加えてみましょう.

32個目のセル

次のようにしましょう.

preprocessed_df_same_shift, new_cols = tide_shift(preprocessed_df_same, 10)
cols = ["longitude", "moon phase", "calendar", "JMA", "MIRC", "rainfall(mm)", "temperature(℃)"] + new_cols
X = preprocessed_df_same_shift[cols]
y = preprocessed_df_same_shift["tide level"]
regressor = train_predict(X, y, 0.50)
tottocoslowlifer commented 5 months ago

夜分に申し訳ありません. 対応が完了いたしました.

victor-von-pooh commented 5 months ago

@tottocoslowlifer ご対応ありがとうございました. それではこの issue で最後のステップになりますが, 上で作った regressor を使って, 元の潮位のデータを全て補完してみましょう.

tottocoslowlifer commented 5 months ago

お疲れ様です. 方向性を全く理解できていない状態なので,補完の操作の流れを示していただけると幸いです. 申し訳ありません.よろしくお願いいたします.

victor-von-pooh commented 5 months ago

方向性を全く理解できていない状態なので

こちらは,

  1. 重回帰分析のモデルを使って欠損値補完をするということの意味がわからない
  2. 欠損値補完をするロジックは理解しているが, それをコードに書き起こせない

でいうとどちらでしょうか?

tottocoslowlifer commented 5 months ago

1です.

victor-von-pooh commented 5 months ago

ありがとうございます.

まず, 機械学習というものは, 次のような分析方法です(出典: 機械学習 | 用語解説 | 野村総合研究所(NRI)).

データを分析する方法の1つで, データから「機械」が自動で「学習」し, データの背景にあるルールやパターンを発見する方法. 近年では, 学習した成果に基づいて「予測・判断」することが重視されるようになった.

機械学習や深層学習などの本質は, 学習させること自体ではなく, そこから機械が導いたロジックに基づいて「予測・判断」をすることにあります. 今回の場合, なぜ重回帰分析をしたのかを考えていただきたいです. 重回帰分析は, 教師あり学習の「回帰」問題で扱われる代表的なモデル構造であり, 説明変数から目的変数を予測する機械学習アルゴリズムです. 今までの「メタ情報を用いた補完」では, いくつかの説明変数を用意し, それに対する線形写像である(ことが望ましい)目的変数を予測するということを重回帰分析を用いて行いました.

説明変数:

X(ある日のある時刻における) = [
    "黄経差", "月齢", "こよみ", "気象庁",
    "MIRC", "降水量", "気温",
    "1時間前の潮位", "2時間前の潮位", "3時間前の潮位",
    "4時間前の潮位", "5時間前の潮位", "6時間前の潮位",
    "7時間前の潮位", "8時間前の潮位", "9時間前の潮位", "10時間前の潮位",
]

目的変数:

y(ある日のある時刻における) = ["潮位"]

さて, これである日のある時刻の説明変数が揃っている場合, これらの情報を基にその時刻の潮位を機械が教えてくれるようになりました. これが現在の状況です.

今の notebook の一番最後のセルの一番最後の regressor という変数は, 平均絶対誤差が 2.80, 平均二乗誤差が 13.11 でこの潮位を予測できるモデルとなっています. すなわち, 説明変数分のデータがあれば欠損された潮位のデータを, 本来の値から3前後だけしかズレないで予測してくれるということです. 元の潮位のデータは28個目のセルより97個欠損していることが分かります. これら97個の時刻における説明変数のデータを用いてこのモデルにかけることで, 欠損されたデータを補うことができるということになります.

コーディングに移る前に, 今一度やってきたことでわからないことが無いかを確認いただき, 上記の説明に納得していただきたく存じます.

tottocoslowlifer commented 5 months ago

ありがとうございます.

ある日のある時刻の説明変数が揃っている場合, これらの情報を基にその時刻の潮位を機械が教えてくれる

とあるのですが,欠損値を含むデータにregressorを用いる際,

例えば $y$ にあたる潮位データが欠けていて, さらにその行の説明変数にあたるデータも欠損値を含む というケースはないのでしょうか.(1時間前の潮位もnanであるケース,降水量もnanであるケースなど)

それとも,処理の上で

pre_df = pre_df.dropna(subset=["rainfall(mm)", "temperature(℃)"])
pre_df = pre_df.dropna(subset=X)

としてこれらのケースを除くのでしょうか.

よろしくお願いいたします.

victor-von-pooh commented 5 months ago

ありがとうございます. 良いご質問です.

結論から言うと, それはご自身で確認いただきたいところです. 潮位のデータとその他のデータが同時に欠損している場合が無いかを調べ, 無ければそのまま regressor に入れる, ある場合はもう一度重回帰分析のモデルを作り直すのが良いでしょう. 今回の「欠損値補完」のセクションでは, 「統計的情報を用いた補完」を含めて欠損値を補完することが目的です.

tottocoslowlifer commented 5 months ago

お疲れ様です. 調べてみた結果,tide levelのデータが連続して欠損している箇所が複数,ありました.

そのため,これらの箇所周辺で"tide level shift nh"が欠損値ばかりになり,説明変数として機能しないような気がします.

重回帰分析モデルの説明変数を再度,見直すべきでしょうか.

victor-von-pooh commented 5 months ago

潮位のデータのみが連続して欠損している場合は, 一つずつ補ってずらすというように再帰的にモデルに入れてみると良いと思います. モデルの説明変数を見直す必要はありません.

tottocoslowlifer commented 5 months ago

ありがとうございます. 取り組んでみたのですが行き詰まってしまいました.

スクリーンショット 2024-03-22 23 43 44 スクリーンショット 2024-03-22 23 43 59

お手数ですが,ZOOMミーティングを設定させていただけたら幸いです. よろしくお願いいたします.

victor-von-pooh commented 5 months ago

承知致しました. それではまたカレンダー内の「空」の部分に予定を入れておいてください. Zoom の情報は以下の通りになります.

https://tus-ac-jp.zoom.us/j/92156768099?pwd=VU1MOFdiNFVkdzhBZlNTVXRRWnVpUT09

ミーティング ID: 921 5676 8099 パスコード: 302739

よろしくお願い致します.

victor-von-pooh commented 5 months ago

欠損値の補完が完了したため close とします. お疲れ様でした.