Pythonデータ解析のおさらい

これまでの講義で行ってきた、CSV・NetCDF の読み込み、前処理、時系列解析、相関・回帰、空間データ解析、図化までを一度整理する。

講義名
データサイエンス
対象
これまでの復習と今後の準備
到達目標
Pythonでデータを読み、整え、解析し、図にして解釈する流れを説明できるようになる
STEP 1

1. これまで何をやってきたか

この授業では、Pythonの文法そのものを暗記することよりも、実際の海洋・気候データを使って、データ解析の流れを身につけることを重視してきた。

最初は、タヒチとダーウィンの海面気圧から南方振動指数(SOI)を計算した。その後、SOIの移動平均、自己相関、フーリエ変換、スペクトル解析、SSTとの相関、フィルタ処理、ENSOコンポジット、さらに海面高度偏差(SLA)から地衡流や渦度を計算するところまで進んだ。

内容扱ったデータ主な解析
SOIの計算タヒチ・ダーウィン海面気圧平均、偏差、標準化
移動平均と自己相関SOI時系列平滑化、自己相関、周期性の確認
フーリエ変換・スペクトルSOIや人工的な波時間領域から周波数領域へ変換
SOIとSSTの比較SOI、NINO index相関、回帰、ラグ相関
フィルタ処理SOI、NINO3.4ローパス、ハイパス、バンドパス
ENSOコンポジットNINO3.4、SSTイベント抽出、コンポジット平均
SLA・ADT解析NetCDF形式の海面高度データ地図表示、地衡流、渦度、EKE
つまり、テーマは毎回違っていても、Pythonで行っている作業には共通する流れがある。
STEP 2

2. Python解析の共通パターン

どの回でも、ほぼ同じ順番で解析している。大事なのは、いきなり計算式を書くのではなく、まずデータを読み、形を確認し、欠損値を処理し、必要な部分を取り出してから解析することである。

読む
確認する
整える
選ぶ
計算する
描く
解釈する

典型的なコードの流れ

# 1. ライブラリを読み込む
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 2. データを読む
df = pd.read_csv("data.csv")

# 3. 中身を確認する
print(df.head())
print(df.columns)
print(df.shape)

# 4. 欠損値を処理する
df = df.dropna()

# 5. 必要な列を取り出す
x = df["TIME"].to_numpy()
y = df["SOI"].to_numpy()

# 6. 解析する
y_mean = np.mean(y)
y_anom = y - y_mean

# 7. 図にする
plt.plot(x, y_anom)
plt.xlabel("Time")
plt.ylabel("Anomaly")
plt.show()

この流れは、CSVでもNetCDFでもほとんど同じである。ただし、CSVでは pandas、NetCDFでは xarray を使うことが多い。

確認
  • 解析前に head()columns を確認するのはなぜか。
  • 欠損値を処理しないまま平均や相関を計算すると、何が起こるか。
STEP 3

3. CSVデータのおさらい

SOI、NINO index、時系列データの多くは、CSVやテキスト形式の表データとして扱った。このようなデータでは pandas を使う。

CSV・テキストデータの読み込み

import pandas as pd

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

pd.read_csv() はCSVだけでなく、空白区切りやタブ区切りのテキストデータも読むことができる。空白区切りのときは sep=r"\s+" を使った。

コード意味
pd.read_csv()表形式データを読み込む
sep=","カンマ区切り
sep=r"\s+"空白が1個以上ある場所で区切る
na_values=-99.9-99.9のような欠損値をNaNとして読む

DataFrameとは何か

pandasで読み込んだ表は DataFrame と呼ばれる。Excelの表に近いものだと思えばよい。行と列があり、列名を使ってデータを取り出せる。

print(df.head())      # 最初の5行を表示
print(df.columns)   # 列名を表示
print(df.shape)     # 行数・列数を表示

列を取り出す

time = df["TIME"]
soi  = df["SOI"]

df["SOI"] は、DataFrameの中からSOIという列だけを取り出す操作である。

欠損値を取り除く

df = df.dropna(subset=["SOI"]).reset_index(drop=True)

欠損値があると、平均、相関、回帰などの計算結果が nan になることがある。そのため、計算前に欠損値を取り除く。

期間を選ぶ

df2 = df[(df["YEAR"] >= 1951) & (df["YEAR"] <= 1997)]

角括弧 [] の中に条件を書くと、その条件を満たす行だけを取り出せる。この操作は、解析対象期間をそろえるときに非常に重要である。

pandasからNumPy配列へ変換する

x = df["SOI"].to_numpy()

pandasの列はそのままでも計算できるが、自己相関やFFTなどでは NumPy 配列に変換して扱うことが多い。

確認
  • DataFrame は何に近いデータ構造か。
  • df["SOI"] は何を取り出しているか。
  • dropna() はどのようなときに必要か。
STEP 4

4. NetCDFデータのおさらい

SSTやSLAのような地図データは、CSVのような2次元の表ではなく、時間・緯度・経度を持つ多次元データである。このようなデータは、NetCDF形式で配布されることが多い。

NetCDFの読み込み

import xarray as xr

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

xr.open_dataset() でNetCDFファイルを開くと、Dataset が得られる。Datasetには、緯度、経度、時間、SLA、ADT、流速など、複数の変数が入っている。

用語意味
DatasetNetCDFファイル全体。複数の変数を含む。
DataArrayDatasetの中にある1つの変数。例:SLA、SST、ADT。
dimension次元。例:time, latitude, longitude。
coordinate座標。例:各時刻、各緯度、各経度。

変数を取り出す

sla = ds["sla"]
lat = ds["latitude"]
lon = ds["longitude"]

CSVでは df["SOI"] のように列を取り出した。NetCDFでは ds["sla"] のように変数を取り出す。

範囲を切り出す

sub = ds.sel(
    longitude=slice(135, 150),
    latitude=slice(25, 40)
)

sel() は座標値を使ってデータを選ぶ操作である。たとえば、黒潮周辺だけを切り出したい場合に使う。

条件でマスクする

sla2 = sla.where(np.isfinite(sla))

where() は条件を満たす場所だけを残し、条件を満たさない場所をNaNにする。海だけ残す、欠損を除く、特定のしきい値以上だけ残す、という処理でよく使う。

CSVとNetCDFの違い

項目CSVNetCDF
主なライブラリpandasxarray
データ構造多次元配列
SOI, NINO indexSST, SLA, ADT
選び方列名・行番号time, latitude, longitude
代表的コードdf["SOI"]ds["sla"]
NetCDFでは、次元の順番が time, latitude, longitude なのか、latitude, longitude, time なのかを必ず確認する。次元を勘違いすると、図も計算も間違える。
STEP 5

5. 時系列解析のおさらい

SOIやNINO3.4のように、時間とともに変化するデータを時系列という。時系列解析では、値そのものだけでなく、周期性、遅れ、平滑化、周波数成分を見る。

移動平均

移動平均は、前後の値を平均して、短い時間スケールのギザギザを弱める方法である。

df["SOI_13mo"] = df["SOI"].rolling(window=13, center=True).mean()
引数意味
window=1313か月分を平均する
center=True平均値を窓の中央の時刻に置く
mean()窓の中の平均を計算する

偏差と標準化

気候データでは、値そのものよりも「平年からどれだけずれているか」を見ることが多い。これを偏差、または anomaly という。

anomaly = value − climatology
clim = df["SOI"].mean()
anom = df["SOI"] - clim

さらに標準偏差で割ると、単位の違うデータを比較しやすくなる。これを標準化という。

standardized value = (value − mean) / standard deviation
z = (df["SOI"] - df["SOI"].mean()) / df["SOI"].std()

自己相関

自己相関は、同じ時系列を少しずらして、自分自身とどれくらい似ているかを見る方法である。周期性がある時系列では、特定のラグで相関が高くなる。

x1 = soi[:-lag]
x2 = soi[lag:]

num = np.sum(x1 * x2)
den = np.sqrt(np.sum(x1**2) * np.sum(x2**2))
r = num / den

lag は時間のずれを表す。月データなら lag=12 は12か月のずれである。

フーリエ変換とスペクトル

時系列を時間方向に見るだけでは、何か月周期の変動が強いのかは分かりにくい。フーリエ変換を使うと、時系列を周期成分に分解できる。

見方わかること
時間領域いつ増えたか、いつ減ったか
周波数領域何か月周期の変動が強いか
x = soi - np.mean(soi)
X = np.fft.fft(x)
power = np.abs(X)**2
確認
  • 移動平均をかけると、どのような変動が弱くなるか。
  • anomaly は何から何を引いたものか。
  • 自己相関と普通の相関は何が違うか。
STEP 6

6. 関係を見る解析

SOIとNINO3.4、SOIとSSTのように、2つのデータの関係を調べるときには、相関係数、回帰直線、決定係数、ラグ相関などを使う。

相関係数

相関係数は、2つの変数がどれくらい一緒に増減するかを表す数値である。値は -1 から 1 の範囲をとる。

相関係数意味
1に近い一方が増えると、もう一方も増える
0に近い直線的な関係が弱い
-1に近い一方が増えると、もう一方は減る
mask = np.isfinite(x) & np.isfinite(y)
r = np.corrcoef(x[mask], y[mask])[0, 1]

mask は、xとyの両方が欠損していない場所だけを選ぶための条件である。

回帰直線

回帰直線は、xが変化したときにyがどのように変化するかを、直線で近似する方法である。

y = ax + b
a, b = np.polyfit(x[mask], y[mask], 1)
y_fit = a * x + b
記号意味
a傾き。xが1増えたとき、yがどれだけ変わるか。
b切片。x=0のときのy。

決定係数 R²

決定係数は、回帰直線がyの変動をどれくらい説明できるかを表す。

R² = 1 − SSres / SStot
y_obs = y[mask]
y_pred = a * x[mask] + b

ss_res = np.sum((y_obs - y_pred)**2)
ss_tot = np.sum((y_obs - np.mean(y_obs))**2)
r2 = 1 - ss_res / ss_tot

相関係数が高いほど、回帰直線で説明できる割合も大きくなる。ただし、相関が高いことは、必ずしも因果関係を意味しない。

ラグ相関

ラグ相関は、2つの時系列の片方を時間方向にずらしながら相関を計算する方法である。どちらの変化が先に起こるのかを考える手がかりになる。

lags = np.arange(-24, 25)
rs = []

for lag in lags:
    if lag < 0:
        x1 = x[:lag]
        y1 = y[-lag:]
    elif lag > 0:
        x1 = x[lag:]
        y1 = y[:-lag]
    else:
        x1 = x
        y1 = y

    mask = np.isfinite(x1) & np.isfinite(y1)
    rs.append(np.corrcoef(x1[mask], y1[mask])[0, 1])
相関係数やp値を見るときは、データ数だけでなく、自己相関にも注意する。時系列データでは隣り合う月が独立ではないことが多い。
STEP 7

7. 空間データ解析のおさらい

SSTやSLAのようなデータは、時間だけでなく、緯度・経度方向にも広がっている。このようなデータでは、地図として描くだけでなく、空間的な勾配や流速を計算することができる。

2次元マップを描く

plt.pcolormesh(lon, lat, sla, shading="auto")
plt.colorbar(label="SLA (m)")
plt.xlabel("Longitude")
plt.ylabel("Latitude")
plt.show()

pcolormesh は、緯度経度格子上の値を色で表示する関数である。SST、SLA、渦度、EKEなどの地図表示で使う。

緯度経度を距離に直す

緯度経度は角度であり、メートルではない。空間微分を計算するには、1度が何メートルに相当するかを考える必要がある。

Re = 6371000.0
meters_per_deg_lat = np.pi * Re / 180.0
meters_per_deg_lon = meters_per_deg_lat * np.cos(np.deg2rad(lat))

経度1度の距離は緯度によって変わる。赤道付近では長く、高緯度では短くなる。

SLAの勾配から地衡流を求める

海面高度に傾きがあると、地衡流が生じる。SLAを η とすると、地衡流は次のように表される。

ug = −(g/f) ∂η/∂y
vg = (g/f) ∂η/∂x
記号意味
η海面高度偏差または海面高度
g重力加速度
fコリオリパラメータ
ug東西方向の地衡流
vg南北方向の地衡流

相対渦度とEKE

流速が求まると、流れの回転の強さや、渦に伴う運動エネルギーも計算できる。

ζ = ∂v/∂x − ∂u/∂y
EKE = 1/2 (u² + v²)

相対渦度は、流れが時計回り・反時計回りにどれくらい回転しているかを表す。EKEは、流速が強い場所で大きくなり、黒潮や渦の活動が強い場所を示す指標になる。

ベクトルを重ねる

plt.pcolormesh(lon, lat, sla, shading="auto")
plt.quiver(lon[::3], lat[::3], u[::3, ::3], v[::3, ::3])
plt.colorbar(label="SLA (m)")
plt.show()

quiver を使うと、地図上に流速ベクトルを重ねることができる。ただし、すべての格子点に矢印を描くと見づらくなるので、[::3] のように間引くことが多い。

確認
  • なぜ緯度経度をメートルに直す必要があるのか。
  • SLAの勾配が大きい場所では、地衡流はどうなるか。
  • EKEが大きい場所は、どのような海域を示していると考えられるか。
STEP 8

8. 最後に:どの解析でも同じ考え方

ここまで扱ったテーマは、SOI、ENSO、SST、SLA、地衡流などさまざまである。しかし、Pythonでデータ解析を行うときの基本的な考え方は共通している。

段階やること代表的なコード
読むCSVやNetCDFを読み込むpd.read_csv(), xr.open_dataset()
確認する列名、次元、サイズ、欠損値を確認するhead(), columns, shape, print(ds)
整える欠損値処理、期間選択、範囲選択を行うdropna(), sel(), where()
計算する平均、偏差、相関、回帰、スペクトル、勾配を計算するmean(), np.corrcoef(), np.polyfit(), differentiate()
描く折れ線、散布図、地図、ベクトルを描くplot(), scatter(), pcolormesh(), quiver()
解釈する図や数値が何を意味するのか考える相関が高い理由、周期の意味、流れの向きなどを説明する
Pythonで大切なのは、コードを丸暗記することではない。どの段階で何をしているのかを理解することである。

最低限覚えておきたいこと

復習課題
  1. CSVデータとNetCDFデータの違いを説明せよ。
  2. anomaly と standardization の違いを説明せよ。
  3. 相関係数と回帰直線の違いを説明せよ。
  4. ラグ相関を使うと何がわかるか説明せよ。
  5. SLAから地衡流を求めるとき、なぜ空間微分が必要なのか説明せよ。
次の解析では、ここで整理した「読む、整える、選ぶ、計算する、描く、解釈する」という流れを自分で組み立てられるようになることを目指す。