【Python】測定配列をつなぐか重ねるかを選ぶ

PythonのTopに戻る

同じ形の測定配列をまとめるとき、新しい「試行」軸を作るならstack、既存の「時刻」軸を延ばすならconcatenateを使う。どちらも配列をまとめる関数だが、出力の軸数が違う。後から平均する軸や、時刻ラベルとの対応を間違えないために、まとめる前に出力shapeを決めておく。

最小例で確かめる

以下は説明用に作った小さなデータである。実測データや実行速度の測定結果ではない。コード全体をexample.pyとして保存すれば、入力ファイルを別途用意せずに実行できる。assertは、この例で成り立つべき形や値を確認するために入れてある。

import numpy as np
a = np.array([[1, 10], [2, 20], [3, 30]])
b = np.array([[4, 40], [5, 50], [6, 60]])
trials = np.stack([a, b], axis=0)
timeline = np.concatenate([a, b], axis=0)
channels = np.concatenate([a, b], axis=1)
print("trial/time/channel:", trials.shape)
print("time/channel:", timeline.shape)
print("more channels:", channels.shape)
print("trial mean:", trials.mean(axis=0).tolist())
print("time values:", timeline[:, 0].tolist())
try:
    np.stack([a, b[:2]])  # 意図的な長さ不一致
except ValueError:
    print("expected: ValueError")
else:
    raise AssertionError("different trial lengths were accepted")
assert trials.shape == (2, 3, 2)
assert timeline.shape == (6, 2) and channels.shape == (3, 4)
np.testing.assert_array_equal(trials[0], a)
np.testing.assert_array_equal(trials[1], b)
np.testing.assert_array_equal(timeline[:3], a)
np.testing.assert_allclose(trials.mean(axis=0), [[2.5,25],[3.5,35],[4.5,45]])

実行結果

trial/time/channel: (2, 3, 2)
time/channel: (6, 2)
more channels: (3, 4)
trial mean: [[2.5, 25.0], [3.5, 35.0], [4.5, 45.0]]
time values: [1, 2, 3, 4, 5, 6]
expected: ValueError

新しい軸か既存の軸かで選ぶ

aとbはともに時刻×チャンネルの(3, 2)配列である。stack([a, b], axis=0)では先頭に長さ2の新しい軸ができ、試行×時刻×チャンネルの(2, 3, 2)になる。trials[0]で最初の試行を丸ごと取り出せるので、試行ごとのまとまりを失わずに保存できる。

concatenate([a, b], axis=0)は既存の時刻軸をつなぐため、(6, 2)になる。これはaの3時刻に続いてbの3時刻が並ぶ設計である。別々の試行で時刻がどちらも0から始まっているなら、単純につないだだけで一つの連続した時系列になったとは言えない。時間の意味を別途決める必要がある。

チャンネルを増やすなら対応時刻を確認する

axis=1でconcatenateすると、同じ時刻に観測した別チャンネルを横へ並べる形になり、出力は(3, 4)となる。行数が同じであることは必要だが、それだけでは時刻が一致する保証にならない。測定時刻の配列も照合し、必要なら先に時刻で対応付ける。

列の並びも同様である。aの列が温度、圧力で、bの列が圧力、温度なら、stackはエラーなく動いても試行平均が意味を失う。ndarrayには通常列名がないので、軸ラベルを別に持つか、読み込み時点で統一する。shapeと単位と並びをひとまとまりの契約として扱おう。

集約する軸が変わる

trials.mean(axis=0)は試行方向の平均であり、各時刻・各チャンネルの対応を保って(3, 2)の配列を返す。timeline.mean(axis=0)なら6時刻全体のチャンネル平均になる。どちらもaxis=0だが、その軸が表す対象が異なるため、得られる数値の意味も変わる。

出力shapeを見れば、今どの情報を残しているかを追いやすい。関数の入口と出口に「試行、時刻、チャンネル」と軸順をコメントで記し、既知の要素が同じ位置関係で残るか検査するとよい。例ではtrials[0]とaの一致、連結結果の先頭3行とaの一致を確かめている。

長さが違う試行をどう扱うか

stackはすべての入力shapeが同じであることを要求する。一方、concatenateは連結する軸以外の長さがそろっていればよい。試行ごとの観測数が異なる場合、試行軸へ積むために適当に切り捨てるとデータを失う。NaNで埋める、各試行を別に持つ、共通時刻へ補間するなど、解析目的に沿った選択が必要である。

多数の配列をループ内で一つずつconcatenateすると、既存部分を何度もコピーする形になりやすい。まずリストへ結果を集め、最後に一度まとめる方が分かりやすいことが多い。ただし最終配列がメモリへ収まるとは限らないので、サイズ見積もりと処理方式も確認する。型の混在で結果dtypeが変わる点にも注意しておこう。

動作確認環境と参考資料

Linux・CPython 3.12.14、NumPy 2.3.5、pandas 2.2.3、SciPy 1.17.0、Matplotlib 3.10.8の環境で掲載コードを実行した。使用するライブラリはコード冒頭のimportを参照してほしい。公式資料の最新版と、この実行確認版は区別している。数値の末尾や表の表示幅は環境によって変わることがある。

関連するTips

PythonのTopに戻る