6.12. 選択と並べ替え

リダクションは配列をスカラーまたはより低いランクの結果へと縮約しました。選択は、どの要素が残り、どこに配置されるかを決める操作を扱います。すなわち、条件付きの選択、クリッピング、ソート、インデックスの検索、軸に沿った並べ替えです。

6.12.1. 条件付きの選択

where() は、条件が真の位置では x から、それ以外の位置では y から要素を取り出した配列を返します。3つのオペランドは一緒にブロードキャストされます:

a = np.array([1, 2, 3, 4, 5], dtype=np.float)
np.where(a < 3, a, 0.0)
# array([1.0, 2.0, 0.0, 0.0, 0.0])

これは Python ループを書かずに「要素ごとの if/else」を行うのに適したツールです。

clip()maximum(lo, minimum(a, hi)) の省略形であり、値を範囲内に飽和させます:

np.clip(a, 2.0, 4.0)
# array([2.0, 2.0, 3.0, 4.0, 4.0])

maximum()minimum() は2つのオペランドを取り、要素ごとに大きい方/小さい方を返します:

np.maximum(a, 3.0)
np.minimum(a, np.array([5, 4, 3, 2, 1]))

6.12.2. インデックスの検索

nonzero() は、すべての非ゼロ要素の座標を、次元ごとに1つのインデックス配列に分割して返します。2次元入力の場合、結果は2つの配列のタプルになります。1つ目は行インデックスを保持し、2つ目は列インデックスを保持します。これらを列ごとに対応させると、各非ゼロ位置の (row, col) が得られます:

m = np.array([[0, 2, 0],
              [3, 0, 0]], dtype=np.float)
np.nonzero(m)
# (array([0, 1], dtype=uint16), array([1, 0], dtype=uint16))

m 内の非ゼロ要素は m[0, 1] = 2m[1, 0] = 3 です。返された1つ目の配列 [0, 1] はそれらの行インデックスを、2つ目の [1, 0] は列インデックスを与えます。この2つの配列を並べて読むと、位置 (0, 1)(1, 0) が復元されます。

2つのリダクションもインデックスを生成します:

  • argmin() / argmax() -- 最小要素/最大要素のインデックス。

  • argsort() -- 指定した軸(デフォルトは最後の軸)に沿って入力をソートするための整数配列:

    a = np.array([40, 10, 30, 20], dtype=np.uint8)
    idx = np.argsort(a)             # array([1, 3, 2, 0], dtype=uint16)
    a[idx]                          # array([10, 20, 30, 40])
    

    argsort は常に uint16 を返します。したがってソート対象の配列は、ソートされる軸上の要素数が65,535を超えてはいけません。

bincount() は、1次元の uint8 / uint16 入力内の各非負整数の出現回数をカウントします:

histogram = np.bincount(np.array([0, 1, 1, 2, 2, 2], dtype=np.uint8))
# array([1, 2, 3], dtype=uint16)

Python ループを書かずに、小さい整数値のピクセル値のヒストグラムを構築するのに便利です。

6.12.3. ソートと並べ替え

sort() は、指定した軸(デフォルトは最後の軸)に沿ってソートした配列のコピーを返します。インプレース版を使うには、配列に対して直接 sort() を呼び出してください:

np.sort(np.array([3, 1, 2], dtype=np.float))
# array([1.0, 2.0, 3.0])

flip() は、指定した軸(axis を渡さない場合はすべての軸)に沿って順序を反転します:

np.flip(np.array([1, 2, 3, 4]))
# array([4, 3, 2, 1])

roll() は、指定した数だけ要素を循環的にシフトします。リングバッファ方式のシフトレジスタを実装するのに便利です:

np.roll(np.array([1, 2, 3, 4]), 1)
# array([4, 1, 2, 3])

take() は、ファンシーインデックスの明示的な形式であり、任意のインデックスにある要素を取り出します:

a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
np.take(a, [0, 2, 4])
# array([10, 30, 50], dtype=uint8)

6.12.4. フィルタリングと構造の編集

compress() は、ブールインデックスの明示的な形式であり、ブール条件で選択された a のスライスを返します:

a = np.array([10, 20, 30, 40], dtype=np.uint8)
np.compress(a > 15, a)
# array([20, 30, 40], dtype=uint8)

delete() は、指定したインデックスの要素を削除したコピーを返します:

a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
np.delete(a, [1, 3])
# array([10, 30, 50], dtype=uint8)

diff() は、軸に沿った配列のn階差分(前進差分)を返します。隣接するサンプル間の一次変化を計算するのに使います:

samples = np.array([1, 3, 6, 10, 15], dtype=np.float)
np.diff(samples)
# array([2.0, 3.0, 4.0, 5.0])

6.12.5. 各操作のコスト

このページのほぼすべての関数は、新たに割り当てられた配列を返します。例外は2つあります:

  • sort() はインプレースでソートします。一方、フリー関数の sort() はソートしたコピーを返します。

  • take() は、既存のバッファに書き込むための out= キーワードを受け付けます。

1秒間に何度も実行されるループでは、インプレースの sort() を優先し、それ以外の箇所でも事前に割り当てたバッファを再利用してください。ブールマスク自体は比較が実行されるたびに割り当てられます。マスクを一度作成し、毎回の繰り返しの中で作り直すのではなく、複数の操作にまたがって再利用してください。