numpy – apply_along_axis, apply_over_axes, vectorize の使い方

目次

概要

NumPy 配列の各要素に関数を適用する apply_along_axis、apply_over_axes、vectorize について解説します。

numpy.apply_along_axis

指定した軸に沿って1次元配列を引数にとる関数を適用します。

numpy.apply_along_axis(func1d, axis, arr, *args, **kwargs)
引数
名前 デフォルト値
func1d function (M,) -> (Nj…)
この関数は1次元配列の引数を取らなければなりません。指定された軸に沿って arr をスライスしてできた部分配列が関数に渡されます。
axis integer
arr がスライスされる軸。
arr ndarray (Ni…, M, Nk…)
入力配列。
args any
func1d への追加の引数。
kwargs any
func1d への追加の名前付き引数。
返り値
名前 説明
out 出力配列。

out の形状

入力配列の形状を arr.shapefunc1d の返り値の形状が f である場合、出力配列 out の形状は arr.shape[:axis] + f + arr.shape[axis + 1:] で計算されます。例えば、func1d がスカラーを返す場合は次元が1つ減ります。

  • 例1: スカラーを返す場合
    • arr.shape: (d1, d2, d3)
    • axis: 1
    • func1d: (d2,) -> ()
    • out.shape: (d1, d3)
  • 例2: 配列を返す場合
    • arr.shape: (d1, d2, d3)
    • axis: 1
    • func1d: (d2,) -> (e1, e2)
    • out.shape: (d1, e1, e2, d3)

使い道として、2次元配列の行ごと、列ごとに関数を適用したい場合に使用します。

In [1]:
import numpy as np

np.random.seed(0)

a = np.random.rand(10, 10)
normalize = lambda x: x / np.linalg.norm(x)

# axis=0 に沿ってノルムが1になるように正規化する。
b = np.apply_along_axis(normalize, 0, a)
print(b.shape)
print(np.linalg.norm(b, axis=0))  # ノルムが1になっていることを確認

# axis=1 に沿ってノルムが1になるように正規化する。
b = np.apply_along_axis(normalize, 1, a)
print(np.linalg.norm(b, axis=1))  # ノルムが1になっていることを確認
(10, 10)
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]

numpy.apply_over_axes

指定した複数の軸 axes に沿って関数を繰り返し適用します。

funcres = func(a, axis) のように呼ばれます。関数の返り値 res の形状は、a と同じ形状か、1次元少ない形状でなければなりません。resa より1次元少ない場合、axis の前にサイズ1の次元が挿入されます。その後、func への呼び出しは、axes の各軸に対して、res を第1引数として繰り返します。詳しくは以下のサンプルコードを見てください。

numpy.apply_over_axes(func, a, axes)
引数
名前 デフォルト値
func function
この関数は func(a, axis) の2つの引数を取らなければなりません。
a array_like
入力配列。
axes array_like
func が適用される軸。要素は整数でなければなりません。
返り値
名前 説明
apply_over_axis 出力配列。次元の数は a と同じですが、形状は異なることがあります。これは、func が入力に対して出力の形を変えるかどうかに依存します。
In [2]:
a = np.arange(24).reshape(2, 3, 4)
print(a)

b = np.apply_over_axes(np.sum, a, (0, 2))
print(b)
[[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]

 [[12 13 14 15]
  [16 17 18 19]
  [20 21 22 23]]]
[[[ 60]
  [ 92]
  [124]]]

これは以下のように動作しています。

1 res = np.sum(a, axis=0)

a.shape: (10, 10, 10)
[[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]
 [[12 13 14 15]
  [16 17 18 19]
  [20 21 22 23]]]
  ↓
res.shape: (10, 10)
[[12 14 16 18]
 [20 22 24 26]
 [28 30 32 34]]
  ↓ axis=0 の前にサイズ1の次元が挿入される
res.shape: (1, 10, 10)

2 res = np.sum(res, axis=2)

res.shape: (1, 10, 10)
[[12 14 16 18]
 [20 22 24 26]
 [28 30 32 34]]
  ↓
res.shape: (1, 10)
[[ 60  92 124]]
  ↓ axis=2 の前にサイズ1の次元が挿入される
res.shape: (1, 10, 1)

numpy.vectorize

指定した関数から配列を引数に取ることができる関数を作成します。

class numpy.vectorize(pyfunc, otypes=None, doc=None, excluded=None, cache=False, signature=None)
引数
名前 デフォルト値
pyfunc callable
python の関数またはメソッド。
otypes str list of dtypes None
出力データ型。型を表す文字の文字列またはデータ型のリストのいずれかで指定する必要があります。各出力に対して、1つのデータ型指定子を指定する必要があります。
doc str None
関数の docstring。None の場合、docstring は pyfunc.__doc__ となります。
excluded set None
関数の位置引数やキーワード引数を表す文字列や整数の一覧。これらは、変更されずに直接 pyfunc に渡されます。
cache bool False
True の場合、otypes が提供されていない場合、出力数を決定する最初の関数呼び出しをキャッシュします。
signature string None
関数の入出力を表す表記。デフォルトでは、pyfunc はスカラを入出力として受け取ることを前提としています。
返り値
名前 説明
vectorized ベクトル化された関数。

標準関数 bin() を配列の各要素に適用することを考えます。 そのまま配列を渡しても、関数が配列を受け取るように対応していないのでエラーとなります。

In [3]:
bin([1, 2, 3, 4, 5])
TypeErrorTraceback (most recent call last) <ipython-input-6-ce48f42cd845> in <module> —-> 1 bin([1, 2, 3, 4, 5]) TypeError: ‘list’ object cannot be interpreted as an integer

このような場合に、numpy.vectorize() を使うことでスカラーしか受け取れない関数から配列を受け取って処理できる関数を作成することができます。

otypes の指定

関数の返り値に対応する numpy の型の一覧を otypes に指定します。

  • 例: 返り値が str 型の1つの場合: otypes=[np.object] または otypes="O"
  • 例: 返り値が str 型の返り値2つの場合: otypes=[np.object, np.object] または otypes="OO"
In [4]:
func = np.vectorize(bin, otypes=[np.object])
print(func([1, 2, 3, 4, 5]))
['0b1' '0b10' '0b11' '0b100' '0b101']

返り値が複数の場合はそれぞれに対応する numpy の型を otypes に指定します。

In [5]:
myfunc = lambda x: (hex(x), oct(x), bin(x))

func = np.vectorize(myfunc, otypes="OOO")
print(func([1, 2]))
(array(['0x1', '0x2'], dtype=object), array(['0o1', '0o2'], dtype=object), array(['0b1', '0b10'], dtype=object))

excluded の指定

指定した関数に直接渡す位置引数やキーワード引数を excluded に指定します。 以下の例では、excluded=[1] を指定して2個目の位置引数 a を指定して、ベクトル化した関数を呼び出すときに直接渡しています。

In [6]:
myfunc = lambda x, a: x + a

func = np.vectorize(myfunc, otypes=[float], excluded=[1])
print(func([1, 2, 3, 4, 5], 5))
[ 6.  7.  8.  9. 10.]

キーワード引数の場合も同様です。

In [7]:
myfunc = lambda x, a, b: a * x + b

func = np.vectorize(myfunc, otypes=[float], excluded=["a", "b"])
print(func([1, 2, 3, 4, 5], a=3, b=2))
[ 5.  8. 11. 14. 17.]

signature の指定

Details of Signature に記載がある表記法で関数の入出力を規定します。なにも指定しなかった場合、スカラーを受け取り、スカラーを返す関数を仮定します。

指定方法の例

名前 説明
加算 (),()->()
総和 (i)->()
内積 (i),(i)->()
行列積 (m,n),(n,p)->(m,p)
ベクトル行列積 (n),(n,p)->(p)
行列ベクトル積 (m,n),(n)->(m)
上記4つのいずれか (m?,n),(n,p?)->(m?,p?)
外積 (i,t),(j,t)->(i,j)
クロス積 (3),(3)->(3)
In [8]:
myfunc = lambda x: x * np.arange(5)

func = np.vectorize(myfunc, signature="()->(n)")
print(func([1, 2, 3, 4, 5]))
[[ 0  1  2  3  4]
 [ 0  2  4  6  8]
 [ 0  3  6  9 12]
 [ 0  4  8 12 16]
 [ 0  5 10 15 20]]

numpy.frompyfunc

指定した関数から配列を引数に取ることができる NumPy ufunc を作成します。numpy.vectorize() でスカラーを引数にとり、スカラーを返す関数を指定した場合と同様のことができますが、この場合はこちらの関数のほうが高速に動作します。

numpy.frompyfunc(func, nin, nout)
引数
名前 デフォルト値
func Python function object
任意の関数。
nin int
入力引数の数。
nout int
func によって返される出力の数。
返り値
名前 説明
out NumPy ufunc オブジェクト
In [9]:
func = np.frompyfunc(hex, 1, 1)
print(func([10, 11, 12, 13, 14]))
['0xa' '0xb' '0xc' '0xd' '0xe']

コメント

コメントする

目次