【自前python講座】NumPy(数学演算/配列操作ライブラリ 入門編)

numpy-firststep python
numpy-firststep



他のプログラミング言語に対して,Python は比較的容易に書くことができ,頭で思い描いたことを実装しやすい言語です.
しかし,一方でコンパイルを必要としないスクリプト言語であることから,大規模計算において Python 単独では処理が遅いため,Python 単体にて重い計算をさせることは適切ではないと言えます.
そのため,Python は C 言語や fortran などの他の高速な言語を用いて書かれたサードパーティライブラリを容易に用いることができるようになっており,他の言語で作成したシステムとくっつけることができるという意味で,グルー(glue, 糊(のり))言語とも言われています.
サードパーティライブラリの存在により,Python は人にやさしいインターフェースを提供するとともに,高速演算だったり多機能な処理が可能となっており,美味しいとこどりの素晴らしい言語です.
さて,この美味しさを実感するためにはサードパーティライブラリを扱える必要があります.
今回は無数にあるサードパーティライブラリの中でも,特に数値計算関連において使用率が 1, 2 を争うと思われる NumPy というライブラリについて,ある程度使えるようになるために一部紹介していきます.

今回のコードも以下の github に載せています.

https://github.com/KazutoMakino/PythonCourse/blob/main/008_numpy_firststep/008_numpy_firststep.ipynb

概要

NumPy(ナンパイ、ナムパイ)は,python における数学演算やテンソル計算において標準的に用いられるサードパーティライブラリです.
配列については python の標準で備わっている list や tuple によっても演算が可能でしたが,これらよりもさらに便利で高速演算が可能な関数を持っています.
また,list との相互変換も容易で,ほかのサードパーティライブラリにおいても,numpy との相互変換を可能としているものが多く,サードパーティライブラリであるにも関わらず,python 扱う上で必須なライブラリです.

numpy で内包している数や扱える処理について大別すると,

  • 無限大や円周率, ネイピア数など,数学演算で用いる特殊な数,および nan (None)
  • list と同等のスライスなどの操作
  • 配列生成/変形
  • 数学関数
  • 乱数の数列の生成
  • ファイル読み込み/書き込み

収録されている関数やメソッドがあまりにも豊富なので,ある程度カテゴリー分けして,その中で個人的に良く用いている処理について記載いたします.

まずはライブラリのインポートですが,numpy は公式ドキュメントにて np と省略形でインポートしており,この方法が一般的です.

import numpy as np

numpy.ndarray の持つ属性

numpy において,配列は numpy.ndarray 型で表現されます.
挙動や扱いについてはスライスが可能であったりと list と似ている部分もあるのですが,数多くの有益なメソッドがあったり,要素ごとに演算しなくても,例えば numpy.ndarray 型変数に単純に 2 をかけてスカラー倍すると全要素がちゃんと 2 倍されるのですが,このように一気に演算できるような仕組みを持っています.
便利なメソッドを試すために,まず numpy.ndarray 型変数を生成する必要があるため,最初にリストやタプルから numpy.ndarray にする方法を示します.

np.array(数値やリストなど, dtype=None, copy=True)
a = np.array([0, 1, 2])
a
# array([0, 1, 2])

type(a)
# numpy.ndarray

戻り値は数値やリストなどのオブジェクトの numpy.ndarray 型が返ってきますが,要素の型は dtype によって変わります.
初期値は None となっていますが,この場合は,元のオブジェクトから型が推定されます.
例えば,次のように指定することもできます.

a = np.array([0, 1, 2], dtype=float)
a
# array([0., 1., 2.])

np.array(((0, 1, 2), (3, 4, 5)), dtype=np.int8)
# array([[0, 1, 2],
#        [3, 4, 5]], dtype=int8)

np.array(0)
# array(0)

上記のように,リストでもタプルでも数値でも,numpy.ndarray 型となります.
数値を引数に与えた場合は,0 次元の配列になります.
ちなみに,np.asarray でも同様な操作ができますが,こちらは引数の初期値が copy=False となっていてビューを返すので,よっぽど処理がひっ迫してなければ上記の np.array を用います.

numpy.ndarray 型変数の属性は以下で取得できます.

  • .T:転置テンソルを返す
  • .dtype:データタイプを返す
  • .flat:1 次元化された配列のイテレータを返す
  • .imag:複素数の虚部を返す
  • .real:複素数の実部を返す
  • .size:配列の要素数を返す
  • .ndim:配列の次元数を返す
  • .shape:配列の各次元の要素数を返す
a = np.array([[0, 1, 2], [3, 4, 5]])
a
# array([[0, 1, 2],
#        [3, 4, 5]])

a.T
# array([[0, 3],
#        [1, 4],
#        [2, 5]])

a.dtype
# dtype('int32')

a.flat
# <numpy.flatiter at 0x2b8997ab520>

a.imag
# array([[0, 0, 0],
#        [0, 0, 0]])

a.real
# array([[0, 1, 2],
#        [3, 4, 5]])

a.size
# 6

a.ndim
# 2

a.shape
# (2, 3)

また,リストと同様にスライスも扱うことができます.

a[0]
# array([0, 1, 2])

a[0, 1:]
# array([1, 2])

組み込み関数も思った通りに使うことができます.

len(a)
# 2

list(a)
# [array([0, 1, 2]), array([3, 4, 5])]

tuple(a)
# (array([0, 1, 2]), array([3, 4, 5]))

ただし,上記のように多次元配列の numpy.ndarray を,例えば組み込み関数の list を用いてリストにしようとした場合は,外側の型のみリストになります.
全ての次元においてリスト化したい場合は,後述のメソッド tolist を用います.

numpy.ndarray に対する定数の加減乗除は,全ての要素に対して同一の操作が適用されます.

a = np.array([[0, 1, 2], [3, 4, 5]])
a + 10

# array([[10, 11, 12],
#        [13, 14, 15]])

numpy.ndarray のメソッド

numpy.ndarray 型変数に対して良く使うメソッドは以下です.

  • .all(axis=None):すべての要素を評価したとき,すべての要素が True であれば True を返し,そうでなければ False を返す(axis は評価する対象の次元で,例えば arr.shape が (3, 4, 5) だとすると,axis=0 は 1 つ目の要素数 3 の次元が評価対象となる)
  • .any(axis=None):すべての要素を評価したとき,どれか一つの要素が True であれば True を返し,そうでなければ False を返す
  • .argmax(axis=None):要素が最大値を取る要素番号を返す
  • .argmin(axis=None):要素が最小値を取る要素番号を返す
  • .argsort(axis=-1):配列の値の大小関係の順番を返す
  • .astype(dtype):要素の型をキャストする
  • .clip(min=None, max=None):指定した最小値よりも小さい要素は指定した最小値で,指定した最大値よりも大きい場合は最大値で,指定した最小値と最大値の間にある要素はそのままの値の配列で返す
  • .copy():コピーを返す
  • .cumprod(axis=None, dtype=None):要素の累積積を返す
  • .cumsum(axis=None, dtype=None):要素の累積和を返す
  • .fill(value):与えられた配列の全要素をスカラーで埋める(値を返さず元配列を上書きすることに注意)
  • .flatten():1 次元の配列にコピーして返す
  • .max(axis=None):要素の最大値を返す
  • .mean(axis=None, dtype=None):要素の平均を返す
  • .min(axis=None):要素の最小値を返す
  • .repeat(repeats, axis=None):repeats 分配列を繰り返す
  • .reshape(shape):shape で指定した次元の配列を返す
  • .round(decimals):decimals で指定した桁数で丸めた要素の配列を返す
  • .sort(axis=None):配列をソートする(戻り値はなく元配列が書き換わることに注意)
  • .squeeze(axis=None):要素が 0 しかない次元を消す
  • .std(axis=None, dtype=None, ddof=):標準偏差を返す
  • .sum(axis=None, dtype=None):総和を返す
  • .swapaxes(axis1, axis2):axis1 と axis2 を入れ替える
  • .tolist():全ての次元を list でキャストして返す
  • .trace():線形代数でいう縮約(トレース)を返す
  • .transpose():転置を返す
  • .var(axis=None, dtype=None, ddof=):統計学の分散を返す

まずは,対象の numpy.ndarray 型変数を a として設定します.

a = np.array(
    [
        [
            [0, 1, 2, 3],
            [4, 5, 6, 7],
            [8, 9, 10, 11],
        ],
        [
            [12, 13, 14, 15],
            [16, 17, 18, 19],
            [20, 21, 22, 23],
        ],
    ]
)
a
# array([[[ 0,  1,  2,  3],
#         [ 4,  5,  6,  7],
#         [ 8,  9, 10, 11]],
# 
#        [[12, 13, 14, 15],
#         [16, 17, 18, 19],
#         [20, 21, 22, 23]]])

a.shape
# (2, 3, 4)

この 3 階テンソルに対してメソッドを適用していきます.

a.all(), (a+1).all()
# (False, True)

a.any(), np.array([0,0,0]).any()
# (True, False)

a.argmax()
# 23

a.argmax(axis=0)
# array([[1, 1, 1, 1],
#        [1, 1, 1, 1],
#        [1, 1, 1, 1]], dtype=int64)

a.argmax(axis=1)
# array([[2, 2, 2, 2],
#        [2, 2, 2, 2]], dtype=int64)

a.argmax(axis=2)
# array([[3, 3, 3],
#        [3, 3, 3]], dtype=int64)

a.argmin(axis=0)
# array([[0, 0, 0, 0],
#        [0, 0, 0, 0],
#        [0, 0, 0, 0]], dtype=int64)

a.argmin(axis=1)
# array([[0, 0, 0, 0],
#        [0, 0, 0, 0]], dtype=int64)

a.argmin(axis=2)
# array([[0, 0, 0],
#        [0, 0, 0]], dtype=int64)

a.argsort()
# array([[[0, 1, 2, 3],
#         [0, 1, 2, 3],
#         [0, 1, 2, 3]],
# 
#        [[0, 1, 2, 3],
#         [0, 1, 2, 3],
#         [0, 1, 2, 3]]], dtype=int64)

a.argsort(axis=0)
# array([[[0, 0, 0, 0],
#         [0, 0, 0, 0],
#         [0, 0, 0, 0]],
# 
#        [[1, 1, 1, 1],
#         [1, 1, 1, 1],
#         [1, 1, 1, 1]]], dtype=int64)

a.argsort(axis=1)
# array([[[0, 0, 0, 0],
#         [1, 1, 1, 1],
#         [2, 2, 2, 2]],
# 
#        [[0, 0, 0, 0],
#         [1, 1, 1, 1],
#         [2, 2, 2, 2]]], dtype=int64)

a.argsort(axis=2)
# array([[[0, 1, 2, 3],
#         [0, 1, 2, 3],
#         [0, 1, 2, 3]],
# 
#        [[0, 1, 2, 3],
#         [0, 1, 2, 3],
#         [0, 1, 2, 3]]], dtype=int64)

a.astype(np.float16)
# array([[[ 0.,  1.,  2.,  3.],
#         [ 4.,  5.,  6.,  7.],
#         [ 8.,  9., 10., 11.]],
# 
#        [[12., 13., 14., 15.],
#         [16., 17., 18., 19.],
#         [20., 21., 22., 23.]]], dtype=float16)

a.clip(min=10, max=20)
# array([[[10, 10, 10, 10],
#         [10, 10, 10, 10],
#         [10, 10, 10, 11]],
# 
#        [[12, 13, 14, 15],
#         [16, 17, 18, 19],
#         [20, 20, 20, 20]]])

a.copy()
# array([[[ 0,  1,  2,  3],
#         [ 4,  5,  6,  7],
#         [ 8,  9, 10, 11]],
# 
#        [[12, 13, 14, 15],
#         [16, 17, 18, 19],
#         [20, 21, 22, 23]]])

a.copy() == a
# array([[[ True,  True,  True,  True],
#         [ True,  True,  True,  True],
#         [ True,  True,  True,  True]],
# 
#        [[ True,  True,  True,  True],
#         [ True,  True,  True,  True],
#         [ True,  True,  True,  True]]])

a.copy() is a
# False

a.cumprod()
# array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
#        0, 0], dtype=int32)

(a+1).cumprod()
# array([          1,           2,           6,          24,         120,
#                720,        5040,       40320,      362880,     3628800,
#           39916800,   479001600,  1932053504,  1278945280,  2004310016,
#         2004189184,  -288522240,  -898433024,   109641728, -2102132736,
#        -1195114496,  -522715136,   862453760,  -775946240], dtype=int32)

a.cumsum()
# array([  0,   1,   3,   6,  10,  15,  21,  28,  36,  45,  55,  66,  78,
#         91, 105, 120, 136, 153, 171, 190, 210, 231, 253, 276], dtype=int32)

b = a.copy()
a.fill(-1)
a
# array([[[-1, -1, -1, -1],
#         [-1, -1, -1, -1],
#         [-1, -1, -1, -1]],
# 
#        [[-1, -1, -1, -1],
#         [-1, -1, -1, -1],
#         [-1, -1, -1, -1]]])

a = b.copy()

a.flatten()
# array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
#        17, 18, 19, 20, 21, 22, 23])

a.max()
# 23

a.mean()
# 11.5

a.min()
# 0

a.repeat(2)
# array([ 0,  0,  1,  1,  2,  2,  3,  3,  4,  4,  5,  5,  6,  6,  7,  7,  8,
#         8,  9,  9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16,
#        17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23])

a.reshape(2, 12)
# array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11],
#        [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]])

a.reshape(4, -1)
# array([[ 0,  1,  2,  3,  4,  5],
#        [ 6,  7,  8,  9, 10, 11],
#        [12, 13, 14, 15, 16, 17],
#        [18, 19, 20, 21, 22, 23]])

(a+0.5).round(0)
# array([[[ 0.,  2.,  2.,  4.],
#         [ 4.,  6.,  6.,  8.],
#         [ 8., 10., 10., 12.]],
# 
#        [[12., 14., 14., 16.],
#         [16., 18., 18., 20.],
#         [20., 22., 22., 24.]]])

b = np.array([5,3,-1,4,5,5,10])
b.sort()
b
# array([-1,  3,  4,  5,  5,  5, 10])

b = np.array([[[0], [1]]])
b.squeeze()
# array([0, 1])

a.std()
# 6.922186552431729

a.sum()
# 276

a.shape
# (2, 3, 4)

a.swapaxes(0, 1).shape
# (3, 2, 4)

a.tolist()
# [[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]],
#  [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]]

a.trace()
# array([16, 18, 20, 22])

b = np.array(
    [
        [1, 0, 0],
        [1, 2, 0],
        [1, 2, 3],
    ]
)
b.trace()
# 6

a.transpose()
# array([[[ 0, 12],
#         [ 4, 16],
#         [ 8, 20]],
# 
#        [[ 1, 13],
#         [ 5, 17],
#         [ 9, 21]],
# 
#        [[ 2, 14],
#         [ 6, 18],
#         [10, 22]],
# 
#        [[ 3, 15],
#         [ 7, 19],
#         [11, 23]]])

a.var()
# 47.916666666666664

a.std()**2 == a.var()
# True

numpy が持つ定数および関数

冒頭で記載いたしましたが,大別すると以下で,それぞれ見ていきます.

  • 無限大や円周率, ネイピア数など,数学演算で用いる特殊な数,および nan (None)
  • list と同等のスライスなどの操作
  • 配列生成/変形
  • 数学関数
  • 乱数の数列の生成
  • (ファイル読み込み/書き込み)・・・・大抵 pandas というライブラリを使うことが多いため省略

数学で意味のある定数

数学演算で用いられる定数についても numpy はサポートしています.

  • numpy.e:ネイピア数(自然対数の底)
  • numpy.euler_gamma:オイラーの定数(調和級数が自然数の逆数の和)
  • numpy.inf:無限大
  • numpy.nan:nan
  • numpy.pi:円周率
np.e**(np.pi * 1j) + 1
# 1.2246467991473532e-16j

上記は数学的に,

$$
e^{\pi i} + 1 = \cos(\pi) + i \sin(\pi) + 1 = 0
$$

ですが,数値計算上でもほとんど 0 になっていますね.

配列の生成

配列の生成や変形などを行う代表的なメソッドについて挙げていきます.
全てのサンプルコードを書くと膨大な量になるので,一部に絞ります.

  • numpy.arange(start, stop, step, dtype=None):list(range()) と似ているが小数点を用いることが可能
  • numpy.linspace(start, stop, num, endpoint):初期値と終わり値と個数から等間隔のデータを生成
  • numpy.zeros(shape, dtype=None), numpy.zeros_like(a, dtype=None):全要素が 0 の配列を生成
  • numpy.ones(shape, dtype=None), numpy.ones_like(a, dtype=None):全要素が 1 の配列を生成
  • numpy.full(shape, fill_value, dtype=None), numpy.full_like(a, fill_value, dtype=None):全要素が指定した定数の値である配列を生成
  • numpy.copy(a):コピーを生成
np.arange(0, 1, 0.1)
# array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])

np.linspace(start=0, stop=1, num=10)
# array([0.        , 0.11111111, 0.22222222, 0.33333333, 0.44444444,
#        0.55555556, 0.66666667, 0.77777778, 0.88888889, 1.        ])

np.linspace(start=0, stop=1, num=10, endpoint=False)
# array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])

np.zeros((2, 5))
# array([[0., 0., 0., 0., 0.],
#        [0., 0., 0., 0., 0.]])

b = np.arange(0,1,0.1).reshape((2,5))
b
# array([[0. , 0.1, 0.2, 0.3, 0.4],
#        [0.5, 0.6, 0.7, 0.8, 0.9]])

np.zeros_like(b)
# array([[0., 0., 0., 0., 0.],
#        [0., 0., 0., 0., 0.]])

np.full(shape=(2, 5), fill_value=-1)
# array([[-1, -1, -1, -1, -1],
#        [-1, -1, -1, -1, -1]])

配列の変形や結合など

基本的には numpy.ndarray におけるメソッドとほとんど同じで,第一引数に配列オブジェクトをとるくらいで少し書き方が違うだけです.
例えば,reshape については,

a = np.arange(0, 1, 0.1)
a.reshape((2, 5))
# array([[0. , 0.1, 0.2, 0.3, 0.4],
#        [0.5, 0.6, 0.7, 0.8, 0.9]])

np.reshape(a, (2,5))
# array([[0. , 0.1, 0.2, 0.3, 0.4],
#        [0.5, 0.6, 0.7, 0.8, 0.9]])

しかし,メソッドに無い操作もあり,代表格は以下です.

  • numpy.concatenate(a, axis=0, dtype=None):配列を結合する
  • numpy.tile(a, reps):reps 分複製した配列を結合させる
  • numpy.append(a, values):リストの append と同じ(numpy のほうが遅いのであまり使わない)
  • numpy.unique(a, return_index=False, return_inverse=False):重複する要素が消された配列を返す
  • numpy.flip(a, axis=None):配列を反転させる
  • numpy.roll(a, shift, axis=None):配列の形状をそのままで要素をずらす
  • numpy.rot90(a, k=1):配列を回転させる
a = np.array([[0, 1], [2, 3]])
b = np.array([[4, 5]])
np.concatenate((a,b))
# array([[0, 1],
#        [2, 3],
#        [4, 5]])

np.concatenate((a, b.T), axis=1)
# array([[0, 1, 4],
#        [2, 3, 5]])

np.tile([0,1], 2)
# array([0, 1, 0, 1])

a = np.array([10, 1, 1, 10, 100, -1])
np.unique(a)
# array([ -1,   1,  10, 100])

np.unique(a, return_index=True)
# (array([ -1,   1,  10, 100]), array([5, 1, 0, 4], dtype=int64))

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

np.flip(a)
# array([[8, 7, 6],
#        [5, 4, 3],
#        [2, 1, 0]])

np.flip(a, axis=0)
# array([[6, 7, 8],
#        [3, 4, 5],
#        [0, 1, 2]])

np.flip(a, axis=1)
# array([[2, 1, 0],
#        [5, 4, 3],
#        [8, 7, 6]])

np.roll(a, shift=1)
# array([[8, 0, 1],
#        [2, 3, 4],
#        [5, 6, 7]])

np.roll(a, shift=1, axis=0)
# array([[6, 7, 8],
#        [0, 1, 2],
#        [3, 4, 5]])

np.roll(a, shift=1, axis=1)
# array([[2, 0, 1],
#        [5, 3, 4],
#        [8, 6, 7]])

np.rot90(a)
# array([[2, 5, 8],
#        [1, 4, 7],
#        [0, 3, 6]])

np.rot90(a, k=2)
# array([[8, 7, 6],
#        [5, 4, 3],
#        [2, 1, 0]])

数学関数

numpy の数学関数は豊富で,かつ,引数に配列を取ることができ,全ての要素に適用されるため強力です.
以下を用いることができます(メソッドで使用可のものは除いています).

  • numpy.sin(x):sin x
  • numpy.cos(x):cos x
  • numpy.tan(x):tan x
  • numpy.arcsin(x):arcsin x
  • numpy.arccos(x):arccos x
  • numpy.arctan(x):arctan x
  • numpy.deg2rad(x):degree から radian に変換
  • numpy.rad2deg(x):radian から degree に変換
  • numpy.sinh(x):sinh x
  • numpy.conh(x):cosh x
  • numpy.tanh(x):tanh x
  • numpy.arcsinh(x):arcsinh x
  • numpy.arccosh(x):arccosh x
  • numpy.arctanh(x):arctanh x
  • numpy.floor(x):床関数
  • numpy.ceil(x):天井関数
  • numpy.trunc(x):小数点以下切り捨てた配列を返す
  • numpy.exp(x):e の x 乗
  • numpy.log(x):自然対数
  • numpy.log10(x):常用対数
  • numpy.log2(x):log2 x
  • numpy.abs(x):絶対値を返す
  • numpy.gradient(f, axis=None, edge_order=1):中心差分を用いて配列の傾きを返す
  • numpy.dot(a):行列の積を返す
  • numpy.cross(a):行列の外積を返す
  • numpy.trapz(y, x=None, dx=1.0, axis=-1):台形積分値を返す

見たまんまの関数が多いですが,gradient と trapz は引数が多いので,以下に例を示します.

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

y
# array([ 0,  1,  4,  9, 16, 25, 36, 49, 64, 81], dtype=int32)
np.gradient(y, axis=0)
# array([ 1.,  2.,  4.,  6.,  8., 10., 12., 14., 16., 17.])

x = np.arange(0, 2+1, 1)
y = x
np.trapz(y, x=x)
# 2.0

x = np.linspace(-1, 1, num=10000000, endpoint=True)
y = 2 * (1-x**2)**(1/2)
circle_area = np.trapz(y, x=x)
circle_area
# 3.1415926534846044

np.pi*1**2 - circle_area
# 1.0518874660192523e-10

乱数生成

v1.17.0 までは np.random.rand のようにメソッドで乱数を取得する書き方ですが,v1.17.0 以降は処理高速化のために,一旦,乱数を返すジェネレータを生成してから配列を返すという使い方が一般的な書き方となりました(実は僕も古い手法しか知りませんでした).
まずは,以下のようにランダムジェネレータを生成します.

rng = np.random.default_rng(seed=0)
rng
# Generator(PCG64) at 0x2A21D73E660

ここで,seed はランダムシードで,これを設定しておくことで乱数生成の再現性が担保されます.
また,乱数発生器はデフォルトで PCG-64 というものを用いられています.
もし変更されたい場合は,公式ドキュメント (https://numpy.org/doc/stable/reference/random/bit_generators/index.html) を参照下さい.

さて,上記で作成したジェネレータに対して(抜粋しましたが)以下のメソッドを適用することで,乱数の配列が得られます.

  • .integers(low, high=None, size=None, dtype=np.int64, endpoint=False):指定した範囲の整数の乱数の size で指定した形状の配列を生成
  • .random(size=None, dtype=np.float64):0 以上 1 未満の乱数の配列を生成
  • .choice(a, size=None, replace=True, p=None, axis=0, shuffle=True):a で指定した配列の中から,size の形状になるように,要素を確率 p を用いて選出した配列を生成
  • .permutation(x, axis=0):x が配列の場合は,この配列の順序がランダムに入れ替えられた配列を返す(x が int の場合は,list(range(x)))
  • .normal(loc=0.0, scale=1.0, size=None):正規分布から乱数の配列を生成(loc は分布の平均値,scale は標準偏差に相当)
  • .uniform(low=0.0, high=1.0, size=None):指定した範囲の一様正規分布から乱数の配列を生成

他にも,カイ2乗分布に沿ってランダムに値を取得するメソッドなどもありますが,割愛します.

rng.integers(100, size=10)
# array([63, 54, 55, 93, 27, 81, 67,  0, 39, 85], dtype=int64)

rng.integers(low=-2, high=0, size=(2, 3), endpoint=True)
# array([[-1, -2, -2],
#        [-1,  0, -1]], dtype=int64)

rng.random(size=(2,5))
# array([[0.23936944, 0.87648423, 0.05856803, 0.33611706, 0.15027947],
#        [0.45033937, 0.79632427, 0.23064221, 0.0520213 , 0.40455184]])

rng.choice([1, 12, 123], size=(2,3))
# array([[123, 123,   1],
#        [123,   1,  12]])

rng.choice([1, 12, 123], size=(2,3), p=[0.3, 0.0, 0.7])
# array([[123, 123,   1],
#        [123, 123, 123]])

rng.permutation(3)
# array([0, 1, 2])

rng.permutation([0, 0, 0, 0, 1])
# array([0, 0, 1, 0, 0])

rng.permutation(["red", "red", "red", "red", "white"])
# array(['red', 'red', 'red', 'white', 'red'], dtype='<U5')

rng.normal(size=5)
# array([1.22868372, 0.33962001, 0.42377135, 0.37122742, 0.38275716])

rng.uniform(size=5)
# array([0.82270628, 0.41538404, 0.82980399, 0.00995456, 0.36504616])

実は numpy でもテンソル代数学的計算だったり FFT (Fast Fourier Transformation) などもできるのですが,計算処理速度の観点から scipy というライブラリの方が良く用いるため,単純な数学演算や配列生成には numpy,複雑な数学演算については scipy という風に使い分けることをお勧めします.
SciPy については今回は割愛します.

演習問題

以前解いた内包表記の演習問題について,numpy を使って計算してみましょう.
簡単になってると思います.

  • Q.1: numpy を用いた1つの処理式で,1 から 1000 までの 5 の倍数の総和を求めてください.
  • Q.2: numpy を用いた1つの処理式で,1 から 100 まで連続した 100 個の整数に対する標準偏差を求めてください.ここで,データの個数を \(n\), i番目のデータを \(x_i\), データの平均を \(\bar{x}\) とすると,標準偏差 \(\sigma\) は,

$$
\sigma = \sqrt{\frac{1}{n} \sum_{i=1}^n (x_i – \bar{x})^2}
$$

で計算されます.

  • Q.3: y = sin(x) について,0 から 2\(\pi\) まで台形公式で積分してみましょう.この時,x 方向の微小分割幅は 1e-6 とし,出力は小数点第 6 位までになるよう偶数丸めしてください.
  • Q.4: サイコロを 1 度に 2 個振って,目が 2 個とも 1 である確率は 1 / 36 ですが,実際に 1,000,000 回サイコロを振って(も良いけど,乱数発生器を使って)試してみましょう.

演習問題の解答

import numpy as np

# *** Q1 ***
np.arange(5, 1001, 5).sum()
# 100500

# *** Q2 ***
np.std(range(1, 101))
# 28.86607004772212

# *** Q3 ***
dx = 1e-6
x = np.arange(0, 2*np.pi + dx, dx)
y = np.sin(x)
round(np.trapz(y, x=x), 6)
# 0.0

# *** Q4 ***
%time
n = 1000000
rng = np.random.default_rng(seed=0)
a,b = rng.integers(low=1, high=6, size=(2,n), endpoint=True)
​
sum([1 if v1==v2==1 else 0 for v1,v2 in zip(a,b)]) / n
# Wall time: 0 ns
# 0.027927

1 / 36
# 0.027777777777777776

Python のおすすめの学習方法

プログラミングを最短で習得する,少なくても自分の意志で使えるようになる方法について,いくつかプログラミング言語を触ってきた筆者としては何の言語においても,以下2点が重要だと思います.

  • 元々自分が他の言語で作っていた処理を違う言語で書き直す・・・・英語を勉強するときも,脳を生まれたばかりのまっさらな状態から勉強するわけではなく,日本語を通したり対比して,学習済みの言語野を用いて勉強するのと似ています
  • 言語自体を網羅的に勉強するのではなく,やりたい事を先に考え,それを達成するために色々と調べながら実装する・・・・例えば,留学で語学力が上達するのは,その国の言葉を使ってコミュニケーションを取ることが強制されるためであり,使うことに対するモチベーションが一番大事です

独学で行うには,やはり2点目の「やりたい事ドリブン学習」が効果的で,例えば次の書籍は,Python を流行らせている AI/データ分析/機械学習/深層学習について実装することに主眼を置き説明されているので,実際に手を動かしながら学んでいける本だと思います(筆者も最初にこちらの書籍で遊びながら学びました).

コメント

タイトルとURLをコピーしました