16 最終講義:実データで復習するデータ解析

これまで学んだ操作を、実データを使って確認する。ファイルを読む、中身を見る、時刻を作る、共通期間をそろえる、関数を書く、図とCSVを保存する、という流れを最終課題につなげる。

講義名
データサイエンス
目的
最終課題に必要な処理を実データで確認する
対応課題
KADAI1: Chl分類 / KADAI2: 自己相関

1. 今日の位置づけ

この講義では、Pythonの文法そのものだけでなく、海洋・気候データを実際に扱うための一連の流れを学んできた。最終課題では、その流れを自分で組み立てる。

データを読む → 中身を見る → 前処理する → 解析する → 図表にする → 保存する → 解釈する

このページでは、説明を読むだけでなく、短いコードを実行しながら復習する。最終課題のコードは長く見えるが、実際にはここで確認する基本操作を組み合わせたものである。

大事なのは「コードを覚える」ことではなく、「どの処理が何のために必要なのか」を説明できることである。

2. データ解析の基本フロー

データ解析では、いきなり計算に入らない。まずファイルを読み、データの形を確認し、解析できる形に整える。

  1. ファイル名と保存場所を確認する。
  2. Pythonでデータを読み込む。
  3. 列名、次元、時刻、欠損値を確認する。
  4. 必要な変数だけを取り出す。
  5. 平均、偏差、標準化、月別平均などの前処理を行う。
  6. 関数を使って、同じ処理を繰り返し使えるようにする。
  7. 図や表を作る。
  8. CSV、NetCDF、PNGとして保存する。
  9. 図や表から読み取れることを文章で説明する。
確認:最終課題では「画面に図が出た」だけでは終わりではない。作成した図や表を、ファイルとして保存するところまでを評価対象にする。

3. ライブラリと出力フォルダ

まず、解析で使うライブラリを読み込む。図やCSVを保存するためのフォルダも作っておく。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path
import xarray as xr

out_dir = Path("review_outputs")
out_dir.mkdir(exist_ok=True)

print("準備完了")
Path の意味:Path("review_outputs") は、出力先フォルダを表す。mkdir(exist_ok=True) は、そのフォルダがなければ作る、すでにあればそのまま使う、という意味である。

4. 実習A:SOIデータを読み、図を保存する

まずは表形式の時系列データを読む。ここでは SOI_timeseries_updated.txt を使う。

soi = pd.read_csv("SOI_timeseries_updated.txt", sep=r"\s+")

print(soi.head())
print(soi.columns)
print(soi.shape)

確認すること

  1. SOIの列名は何か。
  2. 年を表す列名は何か。
  3. 月を表す列名は何か。
  4. データは何行あるか。

列名を確認したら、SOIの時系列図を描く。列名が自分のファイルと違う場合は、下の YEAR, MONTH, SOI を実際の列名に合わせる。

soi["date"] = pd.to_datetime(dict(
    year=soi["YEAR"],
    month=soi["MONTH"],
    day=1
))

soi = soi.dropna(subset=["SOI"])

plt.figure(figsize=(12, 4))
plt.plot(soi["date"], soi["SOI"])
plt.axhline(0, color="k", linewidth=0.8)
plt.xlabel("Year")
plt.ylabel("SOI")
plt.title("SOI time series")
plt.grid(True, alpha=0.3)
plt.savefig(out_dir / "review_soi_timeseries.png", dpi=300, bbox_inches="tight")
plt.show()
ここで確認したいことは、ファイルを読み、列名を見て、日付を作り、図として保存する、という基本の流れである。

5. 実習B:SOIとNiño3.4を共通期間にそろえる

2つの時系列を比較するときは、同じ年月どうしを比べる必要がある。ここではSOIとNiño3.4を日付で結合し、共通期間だけを残す。

Niño3.4のファイル sstoi.indices は、列名や区切りがデータによって違う可能性がある。まず中身を確認する。

nino = pd.read_csv("sstoi.indices", sep=r"\s+", comment="#")
print(nino.head())
print(nino.columns)
print(nino.shape)

列名を確認した上で、日付列を作る。ここでは例として、年の列を YR、月の列を MON、Niño3.4の列を NINO34 としている。

# 必要に応じて、自分のファイルの列名に合わせて変更する
nino["date"] = pd.to_datetime(dict(
    year=nino["YR"],
    month=nino["MON"],
    day=1
))

nino = nino[["date", "NINO34"]].dropna()

日付をキーにして、SOIとNiño3.4を結合する。

merged = pd.merge(
    soi[["date", "SOI"]],
    nino[["date", "NINO34"]],
    on="date",
    how="inner"
)

merged = merged.dropna(subset=["SOI", "NINO34"])

print(merged.head())
print(merged.tail())
print(merged.shape)
inner merge:how="inner" は、両方のデータに存在する日付だけを残す指定である。期間が違う2つのデータを比較するときに便利である。

6. 標準化する

SOIとNiño3.4は単位も値の範囲も違う。そのまま重ねると、どちらの変動が大きいのか比較しにくい。そこで標準化を行う。

標準化値 = (元の値 − 平均) / 標準偏差
def standardize(x):
    x = np.asarray(x, dtype=float)
    return (x - np.nanmean(x)) / np.nanstd(x)

merged["SOI_std"] = standardize(merged["SOI"])
merged["NINO34_std"] = standardize(merged["NINO34"])

plt.figure(figsize=(12, 4))
plt.plot(merged["date"], merged["SOI_std"], label="SOI standardized")
plt.plot(merged["date"], merged["NINO34_std"], label="Niño3.4 standardized")
plt.axhline(0, color="k", linewidth=0.8)
plt.xlabel("Year")
plt.ylabel("Standardized anomaly")
plt.title("Standardized SOI and Niño3.4")
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig(out_dir / "review_soi_nino34_standardized.png", dpi=300, bbox_inches="tight")
plt.show()
標準化の意味:平均を0、標準偏差を1にすることで、単位の違うデータを同じ図に重ねて比較しやすくする。

7. 関数を作る理由

同じ処理を何度も使う場合は、関数にしておくとよい。関数を作ると、コードが短くなり、間違いも見つけやすくなる。

関数を作る利点

  • 同じ処理を何度も書かなくてよい。
  • 条件を変えたいとき、関数の中だけを直せばよい。
  • コードの意味が読みやすくなる。

自己相関では、同じ時系列を時間方向にずらして相関を計算する。この処理はSOIにもNiño3.4にも使うので、関数にする。

def corr_no_index_alignment(a, b):
    """pandasのindex自動整列を避けて相関係数を計算する。"""
    a = np.asarray(a, dtype=float)
    b = np.asarray(b, dtype=float)
    mask = np.isfinite(a) & np.isfinite(b)
    if mask.sum() < 3:
        return np.nan
    return np.corrcoef(a[mask], b[mask])[0, 1]
注意:pandas の Series.corr() は index を自動的にそろえる。そのため、自己相関やラグ相関では、ずらしたつもりのデータが同じ時刻どうしで比較されてしまうことがある。

8. 実習C:自己相関を計算する

自己相関は、同じ時系列を時間方向にずらして比較する方法である。ラグが大きくなっても自己相関が高い場合、その時系列は過去の状態を長く保っていると考えられる。

lag = 1:ある月の値と、1か月後の値の相関を調べる

Pythonの番号の付け方

Pythonでは、配列の番号は1からではなく0から始まる。要素が n 個ある場合、最初の要素は x[0]、最後の要素は x[n-1] である。

x = [10, 20, 30, 40, 50]

x[0] = 10
x[1] = 20
x[2] = 30
x[3] = 40
x[4] = 50

lag = 1 では何を比べるのか

ここで x は、SOIやNiño3.4の値を時間順に並べた配列である。

x = [x[0], x[1], x[2], ..., x[n-1]]

x[:-1] は最後の1個を除いた配列、x[1:] は最初の1個を除いた配列である。

x[:-1] = [x[0], x[1], x[2], ..., x[n-2]]
x[1:]  = [x[1], x[2], x[3], ..., x[n-1]]

したがって、lag = 1 では次のように「ある月の値」と「1か月後の値」を比べている。

x[0]   と   x[1]
x[1]   と   x[2]
x[2]   と   x[3]
...
x[n-2] と   x[n-1]

自己相関を計算する関数

def autocorrelation(x, max_lag=36):
    x = np.asarray(x, dtype=float)
    r_list = []
    for lag in range(max_lag + 1):
        if lag == 0:
            r = 1.0
        else:
            r = corr_no_index_alignment(x[:-lag], x[lag:])
        r_list.append(r)
    return np.array(r_list)

SOIとNiño3.4の自己相関を計算する

max_lag = 36
lags = np.arange(max_lag + 1)

soi_ac = autocorrelation(merged["SOI_std"], max_lag=max_lag)
nino_ac = autocorrelation(merged["NINO34_std"], max_lag=max_lag)

acf_df = pd.DataFrame({
    "lag_month": lags,
    "SOI_autocorr": soi_ac,
    "NINO34_autocorr": nino_ac
})

acf_df.to_csv(out_dir / "review_autocorrelation.csv", index=False)
print(acf_df.head())

自己相関を図にする

plt.figure(figsize=(10, 5))
plt.plot(lags, soi_ac, marker="o", label="SOI")
plt.plot(lags, nino_ac, marker="o", label="Niño3.4")
plt.axhline(0, color="k", linewidth=0.8)
plt.axhline(0.5, linestyle="--", label="r = 0.5")
plt.xlabel("Lag (months)")
plt.ylabel("Autocorrelation")
plt.title("Autocorrelation of SOI and Niño3.4")
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig(out_dir / "review_autocorrelation.png", dpi=300, bbox_inches="tight")
plt.show()

考えること

  1. SOIとNiño3.4では、どちらの自己相関がゆっくり下がるか。
  2. 自己相関が 0.5 を下回るのは、それぞれ何か月後か。
  3. 差は大きいと言えるか。それとも小さいと言えるか。

9. 実習D:Chl NetCDFを読み、月別気候値を作る

KADAI1では、10年分の月別Chlデータから、12か月の気候値を自分で作る。ここではNetCDFを読み、変数と次元を確認する。

ds = xr.open_dataset("GlobColour_CHL1_monthly_100km_AV_201601_202512.nc")
print(ds)

変数名を確認したら、Chlを取り出す。ここでは変数名を CHL1 とする。

chl = ds["CHL1"]
print(chl.sizes)
print(chl.time.values[0], chl.time.values[-1])

Chlは0以下にならないため、0以下の値は欠損として扱う。その後、log10変換する。

chl = chl.where(chl > 0)
logchl = np.log10(chl)

10年分の月別データから、12か月気候値を作る。

chl_clim = logchl.groupby("time.month").mean("time", skipna=True)
print(chl_clim)
print(chl_clim.sizes)
10年分の月別データ → 同じ月だけを平均 → 12か月気候値

1月のlog10 Chl気候値を図にする

jan = chl_clim.sel(month=1)

plt.figure(figsize=(10, 5))
plt.pcolormesh(jan["lon"], jan["lat"], jan, shading="auto")
plt.colorbar(label="log10(CHL)")
plt.xlabel("Longitude")
plt.ylabel("Latitude")
plt.title("January climatology of log10(CHL)")
plt.savefig(out_dir / "review_chl_january_climatology.png", dpi=300, bbox_inches="tight")
plt.show()
この処理がKADAI1の前半である。KADAI1では、この12か月気候値をK-means分類の特徴量として使う。

10. K-meansで使う特徴量

K-meansでは、各データ点を特徴量ベクトルとして扱う。KADAI1では、各格子点の12か月気候値を特徴量にする。

ある格子点 = [Jan, Feb, Mar, ..., Dec] という12個の値

つまり、K-meansは各格子点の緯度・経度そのものではなく、12か月のChl季節変化パターンが似ているかどうかで分類する。

X = np.column_stack([
    chl_clim.sel(month=m).values.ravel()
    for m in range(1, 13)
])

print(X.shape)
考察のポイント:クラスタマップの色だけを見るのではなく、各クラスタの平均季節変化を見て、「何が似ている海域なのか」を説明する。

11. 結果をCSV・PNGで出力する

解析結果は、Jupyter上で見るだけでなく、ファイルとして保存する。

CSVとして保存

acf_df.to_csv(out_dir / "autocorrelation.csv", index=False)

図として保存

plt.savefig(out_dir / "figure.png", dpi=300, bbox_inches="tight")

NetCDFとして保存

cluster_da.to_netcdf(out_dir / "kmeans_cluster_map.nc")
提出前には、PNGとCSVを実際に開いて、中身が空でないことを確認する。

12. 最終課題との対応

KADAI1:10年分Chlデータから季節変化パターンを分類する

10年分の月別Chlデータを読み、12か月気候値を自分で作成し、K-meansで海域を分類する。

NetCDFxarray月別気候値log10変換K-meansPNG/CSV出力

KADAI2:SOIとNiño3.4の自己相関から持続性を比較する

SOIとNiño3.4を共通期間にそろえ、標準化し、自己相関を計算する。どちらがより長い持続性をもつか、またその差は大きいと言えるかを考察する。

CSV/TXTpandas共通期間標準化自己相関PNG/CSV出力
穴埋めコードと提出物の詳細は 最終課題ページ を参照する。

13. 提出前チェックリスト

確認項目内容
入力データ指定されたファイルを読み込んでいるか。
中身の確認head, columns, shape, print(ds) などで確認したか。
前処理欠損値、log変換、標準化、共通期間抽出などを適切に行っているか。
解析K-meansまたは自己相関の処理が正しく実行されているか。
タイトル、軸ラベル、凡例、カラーバーがあるか。
出力PNGとCSVが保存されているか。
考察図や表から読み取れることを、自分の言葉で説明しているか。
提出前の注意:コードが途中で止まる場合は、最後まで実行できたとは言えない。上から順番に再実行し、出力ファイルが作成されることを確認する。