『Python数値計算ノート』ではアフィリエイトプログラムを利用して商品を紹介しています。

【NumPy】ユニバーサル関数

ユニバーサル関数

配列(ndarray)のすべての要素を操作して配列を返す関数をユニバーサル関数(universal function)とよびます。たとえば、numpy.sin() は受け取った配列のすべての要素について正弦値を計算します。

# NUMPY_UNIVERSAL

# In[1]

import numpy as np

# 円周率
pi = np.pi

# 配列の定義
x = [pi/3, pi/2, pi]

print(np.sin(x))
# [8.66025404e-01 1.00000000e+00 1.22464680e-16]

すべてのユニバーサル関数 (ufunc) は out 引数で出力を指定することができます。

# In[2]

# x = [0 1 2 3 4 5 6 7 8 9]
x = np.arange(10)

# 初期化されていない配列を用意
y = np.empty(10)

# xの各要素の平方根を計算してyに格納
np.sqrt(x, out=y)

print(y)
# [0.         1.         1.41421356 1.73205081 2.
#  2.44948974 2.64575131 2.82842712 3.        ]

NumPyには以下のようなユニバーサル関数が用意されています。
 
絶対値 numpy.absolute()
平方根 numpy.sqrt()
指数関数 numpy.exp()
対数関数 numpy.log(), numpy.log10(), numpy.log2()
三角関数 numpy.sin(), numpy.cos(), numpy.tan()
 
上記のリストにあるように、代表的な数学関数についてはユニバーサル関数が用意されていますが、すべて網羅されているわけではないので、Python 本体、あるいは拡張ライブラリに定義されている関数をユニバーサル関数に変換することもあります。

numpy.frompyfunc()

numpy.frompyfunc() に Python の関数を渡すと、ユニバーサル関数に変換されます

numpy.frompyfunc(func, nin, nout)

func には任意の関数オブジェクトを渡します。
nin は元の関数の引数の個数、nout は返り値の個数です。
 
次のサンプルコードでは bin() をユニバーサル関数に変換します。
bin() は数値を 2 進数表記の文字列に変換する組み込み関数です。

# NUMPY_FROMPYFUNC

# In[1]

import numpy as np

# binをユニバーサル関数に変換
ubin = np.frompyfunc(bin, 1, 1)

# 配列を用意
num = np.array([1, 2, 3, 4, 5])

# numの要素を2進数表記に変換
x = ubin(num)

print(x)
# ['0b1' '0b10' '0b11' '0b100' '0b101']

numpy.vectorize()

numpy.vectorize() は第 1 引数に受け取った関数をユニバーサル関数に変換します。

np.vectorize(pyfunc, otypes=None, doc=None,
excluded=None, cache=False, signature=None)

オプション引数 otypes で出力値のデータ型を指定できます。
たとえば、$x$ の値によって場合分けされた関数
 \[y=\begin{cases}x & (x\geq 0)\\[6pt]0 & (x\lt 0)\end{cases}\]
を定義して、ユニバーサル関数に変換してみます。

# NUMPY_VECTORIZE

# In[1]

import numpy as np

def func(x):
    if x >= 0:
        f = x
    else:
        f = 0
    return f

# funcをユニバーサル関数に変換
# 出力値は浮動小数点数に設定
ufunc = np.vectorize(func, otypes=[float])

ufunc() に配列を渡してみます。

# In[2]

x = np.array([-2, -1, 0, 1.5, 2.5])
print(ufunc(x))
# [0.  0.  0.  1.5 2.5]

関数をユニバーサル化すると、Matplotlib でグラフを描画する処理が速くなります。以下のコードは ufunc() に配列を渡してデータをまとめて作成してグラフにプロットします。

# In[3]

import matplotlib.pyplot as plt

# 区間[-6,6]を64分割
x = np.linspace(-6, 6, 65)

# 配列xの各要素に対するyの値を計算
y = ufunc(x)

# FigureとAxesの設定
fig = plt.figure(figsize=(5, 5))
ax = fig.add_subplot(111)
ax.grid()
ax.set_xlabel("x", fontsize=15)
ax.set_ylabel("y", fontsize=15)
ax.set_xlim(-6, 6)
ax.set_ylim(0, 6)

# Axesにグラフをプロット
ax.plot(x, y, color="blue")

numpy.vectorize

numpy.ufunc.reduce()

numpy.ufunc.reduce() は、配列の指定した軸に沿った要素について、同じ演算を連続的に作用させて1つの値に集約させるメソッドです。

ufunc.reduce(a, axis=0, dtype=None, out=None,
keepdims=False, initial)

このメソッドは numpy.add() や numpy.maximum() などの二項演算関数オブジェクトに対して使用することができます。

# NUMPY_UFUNC_REDUCE

# In[1]

import numpy as np

# a = [[1 2 3],
#      [4 5 6],
#      [7 8 9]]
a = np.arange(1, 10).reshape(3, 3)

# 配列aの要素を0軸に沿って加算集約
b = np.add.reduce(a)

# 配列aの要素を1軸に沿って減算集約
c = np.subtract.reduce(a, axis=1)

# 配列aの要素を1軸に沿って乗算集約(次元は保持)
d = np.multiply.reduce(a, axis=1, keepdims=True)

print("b:{}\n".format(b))
print("c:{}\n".format(c))
print("d:{}\n".format(d))

# b:
# [12 15 18]

# c:
# [ -4  -7 -10]

# d:
# [[  6]
#  [120]
   [504]]

numpy.ufunc.accumulate()

numpy.ufunc.accumulate() は、ufunc() による累積演算結果を返すメソッドです。

ufunc.accumulate(array, axis=0, dtype=None, out=None)

array が 1 次元配列 [p0 p1 p2]、ufunc が numpy.add であった場合、戻り値は

[p0 p0 + p1 p0 + p1 + p2]

となります。

# NUMPY_UFUNC_ACCUMULATE

# In[1]

import numpy as np

# 1次元配列を定義
a = np.array([1, 2, 3, 4, 5])

# 加算による累積配列
b = np.add.accumulate(a)

print(b)
# [1  3  6 10 15]

第 1 引数 array が多次元配列である場合、引数 axis で累積方向を指定することができます。

# In[2]

# a = [[1 2 3],
#      [4 5 6],
#      [7 8 9]]
a = np.arange(1, 10).reshape(3, 3)

# 配列aの要素を0軸に沿って加算累積
b = np.add.accumulate(a)

# 配列aの要素を1軸に沿って減算累積
c = np.subtract.accumulate(a, axis=1)

print("配列b\n{}\n".format(b))
print("配列c\n{}".format(c))

# 配列b
# [[ 1  2  3]
#  [ 5  7  9]
#  [12 15 18]]

配列c
# [[  1  -1  -4]
#  [  4  -1  -7]
#  [  7  -1 -10]]

numpy.ufunc.at()

numpy.ufunc.at() は特定のインデックスの要素に関数を作用させるメソッドです。

ufunc.at(a, indices, b=None)

たとえば、ufunc が add である場合、配列 a のうち、第 2 引数 indices で指定した要素に配列 (またはスカラー) b を加算します。

# PYTHON_UFUNC_AT

# In[1]

import numpy as np

# 1次元配列を定義
a = np.array([1, 2, 3, 4, 5])

# 配列aの2番目と4番目の要素に1を加える
np.add.at(a, [1, 3], 1)

print(a)
# [1 3 3 5 5]

at()メソッドは破壊的操作(元の配列を書き換える操作)であることに注意してください。
 
同じインデックスを複数回指定することもできます。

# In[2]

# 1次元配列を定義
a = np.array([1, 2, 3, 4, 5])

# 配列aの最初の要素に1を3回加えて、5番目の要素に1を1回加える
np.add.at(a, [0, 0, 0, 4], 1)

print(a)
# [4 2 3 4 6]

numpy.ufunc.outer()

numpy.ufunc.outer() は要素間の演算表を作成します。
たとえば、numpy.multiply.outer() は乗算表を返します。

# NUMPY_UFUNC_OUTER

# In[1]

import numpy as np

# x = [1 2 3 4 5 6 7 8 9]
x = np.arange(1, 10)

# 九九の表を作成
y = np.multiply.outer(x, x)

print(y)
# [[ 1  2  3  4  5  6  7  8  9]
#  [ 2  4  6  8 10 12 14 16 18]
#  [ 3  6  9 12 15 18 21 24 27]
#  [ 4  8 12 16 20 24 28 32 36]
#  [ 5 10 15 20 25 30 35 40 45]
#  [ 6 12 18 24 30 36 42 48 54]
#  [ 7 14 21 28 35 42 49 56 63]
#  [ 8 16 24 32 40 48 56 64 72]
#  [ 9 18 27 36 45 54 63 72 81]]

コメント