こんにちは、ぼりたそです!
今回は、NumPyで形状が異なる配列同士を計算できる「ブロードキャスト」について解説します。
配列に1つの数値を足したり、表の列ごとに異なる値を掛けたりするときに使われる仕組みです。一方で、形状の組み合わせによってはエラーになったり、意図とは違う位置の値で計算されたりします。
- ブロードキャストとは何か
- 2次元配列に列ごと・行ごとの値を足す方法
- 計算できる形状を右端から確認するルール
(3,)と(3, 1)の違い- エラーになる場合と、エラーにならない間違い
それでは順番に確認していきましょう!
ブロードキャストとは?
ブロードキャストは、一定のルールに従って、形状が異なる配列の要素を対応させて計算する仕組みです。
まずはNumPyを読み込みます。以降のコードは、この読み込みを実行した状態を前提とします。
import numpy as np配列と1つの数値の計算もブロードキャスト

次の計算では、1つの数値10を配列の各要素に足しています。
a = np.array([10, 20, 30])
print(a + 10)
# 実行結果:
# [20 30 40]計算結果は、aに[10, 10, 10]を足した場合と同じです。このように、必要な位置で同じ値を繰り返し使うイメージで考えられます。
ただし、**説明図のように、入力データを毎回すべて複製しているわけではありません。**ブロードキャストでは、同じ入力値を必要な位置で参照できます。通常のa + 10の結果を保存する配列は別に必要です。
列ごとに異なる値を足す

2行3列の配列に、要素数3の配列を足してみましょう。
data = np.array([
[10, 20, 30],
[40, 50, 60]
])
offset = np.array([1, 2, 3])
print(data + offset)
# 実行結果:
# [[11 22 33]
# [41 52 63]]1列目には1、2列目には2、3列目には3が加わります。offsetの3つの値が、各行で繰り返し使われています。
data.shapeは(2, 3)、offset.shapeは(3,)です。形状は異なりますが、右端のサイズ3が対応するため計算できます。
列ごとに補正値を足す、列ごとの基準値を引く、といった処理に使える書き方です。
行ごとに異なる値を足す

今度は、1行目に100、2行目に200を足してみましょう。
行ごとの値を使う場合は、2行1列の形状にします。
data = np.array([[10, 20, 30], [40, 50, 60]])
offset = np.array([100, 200]).reshape(2, 1)
print(offset)
print(data + offset)
# 実行結果:
# [[100]
# [200]]
# [[110 120 130]
# [240 250 260]]offsetの形状は(2, 1)です。各行の1つの値が、その行の3列すべてに使われます。
reshape(2, 1)は、2つの値を2行1列にする操作です。行ごとの値を使いたい場合は、長さ1の列の軸を残すことがポイントです。
ブロードキャストできる形状のルール

ブロードキャストでは、2つの配列の形状を右端から比較します。対応する各軸が、次のどちらかを満たす必要があります。
- サイズが同じ
- どちらか一方のサイズが1
次元数が異なる場合は、少ない側の左にサイズ1の軸があるものとして考えます。サイズ1の軸は、相手のサイズに対応します。
例えば、(2, 3)と(3,)は、判定上、(2, 3)と(1, 3)として比較できます。
| 比較する位置 | 左の配列 | 右の配列 | 判定 |
|---|---|---|---|
| 右端 | 3 | 3 | 同じなので計算できる |
| その左 | 2 | 1 | 一方が1なので計算できる |
結果の形状は(2, 3)です。これは判定のための考え方であり、元のoffset.shapeが自動的に(1, 3)へ書き換わるわけではありません。
よく使う形状の組み合わせ
| 配列1の形状 | 配列2の形状 | 結果の形状 | 2次元の例での使い方 |
|---|---|---|---|
(2, 3) | スカラー() | (2, 3) | 全要素に同じ値を使う |
(2, 3) | (3,) | (2, 3) | 列ごとに異なる値を使う |
(2, 3) | (1, 3) | (2, 3) | 列ごとに異なる値を使う |
(2, 3) | (2, 1) | (2, 3) | 行ごとに異なる値を使う |
(2, 3) | (2,) | 計算不可 | 右端の3と2が合わない |
この記事では、要素数が0ではない通常の配列を使っています。
計算できない場合:要素数だけでは判断できない

2行3列の配列に、要素数2の1次元配列を足すとエラーになります。
data = np.array([[10, 20, 30], [40, 50, 60]])
offset = np.array([100, 200])
print(data.shape)
print(offset.shape)
# data + offset はValueErrorになるため、ここでは実行しません。
# 実行結果:
# (2, 3)
# (2,)行数が2だから要素数2でよさそうに見えますが、比較は右端から行います。(2, 3)と(2,)の右端は3と2で、同じでも、どちらかが1でもありません。
行ごとに足したいなら、先ほどのようにoffset.reshape(2, 1)へ変えます。配列全体の要素数が同じでも、各軸の条件が合わなければ計算できません。
両方の配列が広がる組み合わせ

片方だけでなく、両方の配列が異なる軸で対応範囲を広げる場合もあります。
column = np.array([10, 20]).reshape(2, 1)
row = np.array([1, 2, 3]).reshape(1, 3)
print(column + row)
print((column + row).shape)
# 実行結果:
# [[11 12 13]
# [21 22 23]]
# (2, 3)columnの10と20それぞれに対して、rowの1・2・3を足しています。
形状は(2, 1)と(1, 3)で、結果は(2, 3)です。両方の入力より要素数の多い結果ができることもわかります。
集計結果と組み合わせる:行ごとの平均を引く

行ごとの平均を引きたい場合も、平均値を1列の形状にします。集計時にkeepdims=Trueを指定すると、その形状を保てます。
data = np.array([[10, 20, 30], [40, 50, 60]])
row_mean = np.mean(data, axis=1, keepdims=True)
print(row_mean)
print(row_mean.shape)
print(data - row_mean)
# 実行結果:
# [[20.]
# [50.]]
# (2, 1)
# [[-10. 0. 10.]
# [-10. 0. 10.]]1行目からは20、2行目からは50が引かれます。
keepdims=Trueを省略した行平均の形状は(2,)で、今回の(2, 3)とは計算できません。集計後にどの配列と計算するかまで考えると、形状を決めやすくなります。
ブロードキャストの注意点
エラーにならなくても、意図した方向とは限らない

2行2列の配列に、要素数2の1次元配列を足す場合はエラーになりません。しかし、値は列ごとに対応します。
data = np.array([[10, 20], [30, 40]])
offset = np.array([100, 200])
print(data + offset)
print(data + offset.reshape(2, 1))
# 実行結果:
# [[110 220]
# [130 240]]
# [[110 120]
# [230 240]]1つ目は1列目に100、2列目に200を足しています。2つ目は1行目に100、2行目に200を足しています。
**計算できたことと、意図した計算になったことは別です。**行と列が同じサイズだと間違いを見落としやすいため、小さな配列で対応関係を確認しましょう。
大きな結果や中間配列にはメモリが必要
入力の値を繰り返し使えるとしても、通常の計算結果や途中の配列には保存領域が必要です。
例えば、(10000, 1)と(1, 10000)の足し算は、結果が1億要素になります。各要素が8バイトのfloat64なら、結果だけで約800 MBが必要です。
大きな配列を計算するときは、入力だけでなく、結果の形状と要素数も確認してください。
NumPyを基礎から学びたい方へ
ブロードキャストは、配列を使った計算・集計・形状変更をつなぐ仕組みです。これらを組み合わせると、列ごとの標準化など、データ分析に役立つ処理を書けるようになります。
体系的に学びたい方向けに、Kindle書籍『機械学習・データ分析のためのNumPy入門 ― 配列操作と数値計算の基本をPythonで学ぶ ―』を執筆しました。
本書では、配列の基本操作から集計やブロードキャストまで、コード例と演習問題を通じて学べます。
「説明を読むだけでなく、形状を自分で考える練習をしたい」という方は、ぜひチェックしてみてください!
まとめ
今回は、NumPyのブロードキャストの仕組みと、形状の確認方法について解説しました。
| 確認すること | ポイント |
|---|---|
| 比較する順番 | 形状の右端から確認する |
| 計算できる条件 | 各軸でサイズが同じ、または一方が1 |
| 次元数が違う場合 | 少ない側の左にサイズ1の軸があると考える |
| 列ごとの計算 | (列数,)または(1, 列数)を使う |
| 行ごとの計算 | (行数, 1)を使う |
| 集計結果の利用 | keepdims=Trueで必要な軸を残せる |
ブロードキャストでは、形状を見ながら、どの値がどの位置で使われるかを考えることが大切です。
まずはreshape()で形状を変えて、同じ値でも計算結果がどう変わるかを確かめてみてください!

