10 海面高度でみるENSOと長期変化

SLA(海面高度偏差)データを用いて、ENSOコンポジット、長期トレンド、p値マップを作成する。08と09で学んだ方法を、別の海洋データへ応用する課題である。

講義名
データサイエンス
使用言語
Python
到達目標
同じ解析手法を SST だけでなく SLA にも適用し、ENSO と長期変化の違いを説明できるようになる

1. 背景:なぜ海面高度を見るのか

これまでの回では、SOI、Niño3.4、SSTを使ってENSOや長期トレンドを調べてきた。 ここでは、海面高度偏差(SLA: Sea Level Anomaly)を使う。

SSTは海面の温度を表す。一方、SLAは海面の高さの偏差であり、海水の熱膨張、風による水の吹き寄せ、 海洋循環の変化などを反映する。つまり、SSTよりも海洋の力学的な変化を見やすい場合がある。

この課題の狙いは、08のENSOコンポジットと09のトレンド解析を、SLAデータに応用することである。

2. この回の課題

以下の3つの図を作成し、SSTで得られた結果と比較する。

  1. SLA長期トレンドマップ
  2. SLAトレンドのp値マップ
  3. El Niño / La Niña時のSLAコンポジット
提出するもの
  • 完成したPythonコード
  • 作成された図(PNG)
  • SSTのENSOコンポジット・SSTトレンドとの比較コメント

3. 使用データ

このページと同じディレクトリに次のファイルを置く。

ファイル内容
sla_monthly_light_2deg_1993_2025.ncCMEMS月平均SLAを約2度格子へ軽量化したデータ。変数は sla(time, latitude, longitude)
sstoi.indicesNiño3.4 anomalyを含むENSO指数データ。
SLAの単位は通常 m である。図では見やすくするため、トレンドは mm/year、コンポジットは cm に変換する。

4. 計算の流れ

  1. SLAデータを読み込む
  2. Niño3.4 anomalyを読み込み、El Niño / La Niña月を判定する
  3. 各格子点でSLAを時間方向に線形回帰する
  4. 傾きとp値を保存する
  5. SLAトレンドマップとp値マップを描く
  6. El Niño月・La Niña月だけを抽出して平均する
  7. El Niño、La Niña、差分のSLAコンポジット図を描く
STEP 1

5. データの読み込み

import numpy as np
import pandas as pd
import xarray as xr
import matplotlib.pyplot as plt
from scipy import stats

sla_file = "______________________________.nc"
nino_file = "______________"

start_year = ______
end_year = ______
alpha = 0.05

ds = xr.open_dataset(sla_file)

sla = ds["____"]
lat = ds["________"].values
lon = ds["_________"].values
time_sla = pd.to_datetime(ds["time"].values)

sla = sla.sel(time=slice(f"{start_year}-01-01", f"{end_year}-12-31"))
time_sla = pd.to_datetime(sla["time"].values)

print(sla)

ポイント

STEP 2

6. Niño3.4によるENSO判定

colnames = [
    "YR", "MON",
    "NINO12", "ANOM12",
    "NINO3",  "ANOM3",
    "NINO4",  "ANOM4",
    "NINO34", "ANOM34"
]

nino_df = pd.read_csv(
    nino_file,
    sep=r"\s+",
    skiprows=1,
    names=colnames
)

nino_df["time"] = pd.to_datetime(
    dict(year=nino_df["YR"], month=nino_df["MON"], day=15)
)

nino_df = nino_df.set_index("time")
nino34 = nino_df["________"].astype(float)

nino34_smooth = nino34.rolling(
    window=______,
    center=True
).mean()

el_event = nino34_smooth >= ______
la_event = nino34_smooth <= -______

ポイント

より厳密に行う場合は、08で使った detect_runs 関数を使って「5か月以上継続」の条件を加えてもよい。
STEP 3

7. SLAの長期トレンドとp値

09で行ったSSTトレンド解析と同じ考え方で、各格子点におけるSLAの時間変化を線形回帰する。

years = time_sla.year + (time_sla.month - ______) / 12.0
years = np.asarray(years, dtype=float)

def calc_trend_pvalue(x, y):
    x = np.asarray(x, dtype=float)
    y = np.asarray(y, dtype=float)

    mask = np.isfinite(x) & np.isfinite(y)
    x = x[mask]
    y = y[mask]

    n = len(x)
    if n < 3:
        return np.nan, np.nan, np.nan, np.nan

    slope, intercept = np.polyfit(x, y, 1)
    r = np.corrcoef(x, y)[0, 1]

    yhat = slope * x + intercept
    residual = y - yhat

    dof = n - 2
    sse = np.sum(residual**2)
    sxx = np.sum((x - np.mean(x))**2)

    mse = sse / dof
    se_slope = np.sqrt(mse / sxx)

    tval = slope / se_slope
    pval = 2 * (1 - stats.t.cdf(np.abs(tval), df=dof))

    return slope, pval, r, n

ポイント

STEP 4

8. トレンドマップとp値マップの描画

sla_np = sla.values
ntime, nlat, nlon = sla_np.shape

trend = np.full((nlat, nlon), np.nan)
pval = np.full((nlat, nlon), np.nan)

for i in range(nlat):
    for j in range(nlon):
        y = sla_np[:, i, j]
        slope, p, r, n = calc_trend_pvalue(_____, _____)

        trend[i, j] = _____
        pval[i, j] = _____

sig_mask = pval < alpha

trend_mm = trend * ______

def plot_global_map(data, title, cbar_label, save_name,
                    cmap="RdBu_r", vmin=None, vmax=None, sig=None):
    fig, ax = plt.subplots(figsize=(14, 6))

    pcm = ax.pcolormesh(
        lon, lat, data,
        shading="auto",
        cmap=cmap,
        vmin=vmin,
        vmax=vmax
    )

    ax.set_xlim(-180, 180)
    ax.set_ylim(-80, 80)
    ax.set_xlabel("Longitude")
    ax.set_ylabel("Latitude")
    ax.set_title(title)
    ax.grid(True, alpha=0.3)

    cbar = fig.colorbar(pcm, ax=ax, pad=0.02)
    cbar.set_label(cbar_label)

    if sig is not None:
        yy, xx = np.where(sig)
        ax.scatter(lon[xx], lat[yy], s=2, c="k", alpha=0.45, label=f"p < {alpha}")
        ax.legend(loc="lower left")

    plt.tight_layout()
    plt.savefig(save_name, dpi=300, bbox_inches="tight", facecolor="white")
    plt.show()

ポイント

STEP 5

9. ENSOコンポジットの計算

enso_df = pd.DataFrame({
    "nino34": nino34,
    "nino34_smooth": nino34_smooth,
    "el": el_event,
    "la": la_event
}).dropna()

tmp = enso_df.reindex(time_sla)

el_mask = tmp["el"].fillna(False).values
la_mask = tmp["la"].fillna(False).values

sla_el = sla.isel(time=________).mean(dim="time", skipna=True)
sla_la = sla.isel(time=________).mean(dim="time", skipna=True)
sla_diff = ________ - ________

sla_el_cm = sla_el.values * ______
sla_la_cm = sla_la.values * ______
sla_diff_cm = sla_diff.values * ______

ポイント

STEP 6

10. ENSOコンポジット図の描画

fig, axes = plt.subplots(3, 1, figsize=(14, 15), constrained_layout=True)

panels = [
    (sla_el_cm, "El Niño composite SLA", "SLA (cm)", -15, 15),
    (sla_la_cm, "La Niña composite SLA", "SLA (cm)", -15, 15),
    (sla_diff_cm, "El Niño - La Niña SLA", "SLA difference (cm)", -20, 20),
]

for ax, (data, title, label, vmin, vmax) in zip(axes, panels):
    pcm = ax.pcolormesh(
        lon, lat, data,
        shading="auto",
        cmap="________",
        vmin=vmin,
        vmax=vmax
    )
    ax.set_xlim(-180, 180)
    ax.set_ylim(-80, 80)
    ax.set_xlabel("Longitude")
    ax.set_ylabel("Latitude")
    ax.set_title(title)
    ax.grid(True, alpha=0.3)

    cbar = fig.colorbar(pcm, ax=ax, pad=0.02)
    cbar.set_label(label)

plt.savefig("sla_enso_composite.png", dpi=300, bbox_inches="tight", facecolor="white")
plt.show()

ポイント

11. 考察課題:SSTとSLAは何が違うか

作成したSLAのENSOコンポジット、SLA長期トレンド、p値マップを見て、 これまでに作成したSSTのENSOコンポジットおよびSST長期トレンドと比較しなさい。

考察せよ
  • El Niño − La Niña の差分図について、SSTとSLAで似ている海域・異なる海域を考察せよ。
  • 赤道太平洋において、SSTとSLAがどのように変化しているかを考察せよ。
  • SLAトレンドは全球で一様に上昇しているか。地域差がある場合、その特徴を考察せよ。
  • SSTトレンドとSLAトレンドの空間分布は一致しているか。一致しない場合、その理由を考察せよ。
  • p値が小さい格子点が多い場合、それは「物理的に重要である」ことを意味するのか考察せよ。
注意
p値は「変化量の大きさ」を表しているわけではない。 変化量の大きさはトレンドの値を見て判断し、その値が統計的に0と区別できるかどうかをp値で判断する。

12. 解答の表示

課題提出後、確認用としてパスワードを入力すると、完全スクリプトと作成例の図を表示する。