<jupyter_start><jupyter_text>NumPy（数学演算／配列操作ライブラリ 入門編）---- ref: https://numpy.org/doc/stable/index.html- blog: https://slash-z.com/- github: https://github.com/KazutoMakino/PythonCourses--- 他のプログラミング言語に対して，Python は比較的容易に書くことができ，頭で思い描いたことを実装しやすい言語です．  しかし，一方でコンパイルを必要としないスクリプト言語であることから，大規模計算において Python 単独では処理が遅いため，Python 単体にて重い計算をさせることは適切ではないと言えます．  そのため，Python は C 言語や fortran などの他の高速な言語を用いて書かれたサードパーティライブラリを容易に用いることができるようになっており，他の言語で作成したシステムとくっつけることができるｊという意味で，グルー（glue, 糊（のり））言語とも言われています．  サードパーティライブラリの存在により，Python は人にやさしいインターフェースを提供するとともに，高速演算だったり多機能な処理が可能となっており，美味しいとこどりの素晴らしい言語です．  さて，この美味しさを実感するためにはサードパーティライブラリを扱える必要があります．  今回は無数にあるサードパーティライブラリの中でも，特に数値計算関連において使用率が 1, 2 を争うと思われる NumPy というライブラリについて，ある程度使えるようになるために一部紹介していきます．  NumPy 概要 NumPy（ナンパイ、ナムパイ）は，python における数学演算やテンソル計算において標準的に用いられるサードパーティライブラリです．  配列については python の標準で備わっている list や tuple によっても演算が可能でしたが，これらよりもさらに便利で高速演算が可能な関数を持っています．  また，list との相互変換も容易で，ほかのサードパーティライブラリにおいても，numpy との相互変換を可能としているものが多く，サードパーティライブラリであるにも関わらず，python 扱う上で必須なライブラリです．numpy で内包している数や扱える処理について大別すると，- 無限大や円周率, ネイピア数など，数学演算で用いる特殊な数，および nan (None)- list と同等のスライスなどの操作- 配列生成／変形- 数学関数- 乱数の数列の生成- ファイル読み込み／書き込み収録されている関数やメソッドがあまりにも豊富なので，ある程度カテゴリー分けして，その中で個人的に良く用いている処理について記載いたします．まずはライブラリのインポートですが，numpy は公式ドキュメントにて np と省略形でインポートしており，この方法が一般的です．<jupyter_code>import numpy as np<jupyter_output><empty_output><jupyter_text>numpy.ndarray の持つ属性 numpy において，配列は numpy.ndarray 型で表現されます．  挙動や扱いについてはスライスが可能であったりと list と似ている部分もあるのですが，数多くの有益なメソッドがあったり，要素ごとに演算しなくても，例えば numpy.ndarray 型変数に単純に 2 をかけてスカラー倍すると全要素がちゃんと 2 倍されるのですが，このように一気に演算できるような仕組みを持っています．  便利なメソッドを試すために，まず numpy.ndarray 型変数を生成する必要があるため，最初にリストやタプルから numpy.ndarray にする方法を示します． ```np.array(数値やリストなど, dtype=None, copy=True)```<jupyter_code>a = np.array([0, 1, 2])
a
type(a)<jupyter_output><empty_output><jupyter_text>戻り値は数値やリストなどのオブジェクトの numpy.ndarray 型が返ってきますが，要素の型は dtype によって変わります．  初期値は None となっていますが，この場合は，元のオブジェクトから型が推定されます．  例えば，次のように指定することもできます．<jupyter_code>a = np.array([0, 1, 2], dtype=float)
a
np.array(((0, 1, 2), (3, 4, 5)), dtype=np.int8)
np.array(0)<jupyter_output><empty_output><jupyter_text>上記のように，リストでもタプルでも数値でも，numpy.ndarray 型となります．  数値を引数に与えた場合は，0 次元の配列になります．  ちなみに，np.asarray でも同様な操作ができますが，こちらは引数の初期値が copy=False となっていてビューを返すので，よっぽど処理がひっ迫してなければ上記の np.array を用います．numpy.ndarray 型変数の属性は以下で取得できます．- `.T`：転置テンソルを返す- `.dtype`：データタイプを返す- `.flat`：1 次元化された配列のイテレータを返す- `.imag`：複素数の虚部を返す- `.real`：複素数の実部を返す- `.size`：配列の要素数を返す- `.ndim`：配列の次元数を返す- `.shape`：配列の各次元の要素数を返す<jupyter_code>a = np.array([[0, 1, 2], [3, 4, 5]])
a
a.T
a.dtype
a.flat
a.imag
a.real
a.size
a.ndim
a.shape<jupyter_output><empty_output><jupyter_text>また，リストと同様にスライスも扱うことができます．<jupyter_code>a[0]
a[0, 1:]<jupyter_output><empty_output><jupyter_text>組み込み関数も思った通りに使うことができます．<jupyter_code>len(a)
list(a)
tuple(a)<jupyter_output><empty_output><jupyter_text>ただし，上記のように多次元配列の numpy.ndarray を，例えば組み込み関数の list を用いてリストにしようとした場合は，外側の型のみリストになります．  全ての次元においてリスト化したい場合は，後述のメソッド tolist を用います． numpy.ndarray に対する定数の加減乗除は，全ての要素に対して同一の操作が適用されます．<jupyter_code>a = np.array([[0, 1, 2], [3, 4, 5]])
a + 10<jupyter_output><empty_output><jupyter_text>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 として設定します．<jupyter_code>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
a.shape<jupyter_output><empty_output><jupyter_text>この 3 階テンソルに対してメソッドを適用していきます．<jupyter_code>a.all(), (a+1).all()
a.any(), np.array([0,0,0]).any()
a.argmax()
a.argmax(axis=0)
a.argmax(axis=1)
a.argmax(axis=2)
a.argmin(axis=0)
a.argmin(axis=1)
a.argmin(axis=2)
a.argsort()
a.argsort(axis=0)
a.argsort(axis=1)
a.argsort(axis=2)
a.astype(np.float16)
a.clip(min=10, max=20)
a.copy()
a.copy() == a
a.copy() is a
a.cumprod()
(a+1).cumprod()
a.cumsum()
b = a.copy()
a.fill(-1)
a
a = b.copy()
a.flatten()
a.max()
a.mean()
a.min()
a.repeat(2)
a.reshape(2, 12)
a.reshape(4, -1)
(a+0.5).round(0)
b = np.array([5,3,-1,4,5,5,10])
b.sort()
b
b = np.array([[[0], [1]]])
b.squeeze()
a.std()
a.sum()
a.shape
a.swapaxes(0, 1).shape
a.tolist()
a.trace()
b = np.array(
    [
        [1, 0, 0],
        [1, 2, 0],
        [1, 2, 3],
    ]
)
b.trace()
a.transpose()
a.var()
a.std()**2 == a.var()<jupyter_output><empty_output><jupyter_text>numpy が持つ定数および関数 冒頭で記載いたしましたが，大別すると以下で，それぞれ見ていきます．- 無限大や円周率, ネイピア数など，数学演算で用いる特殊な数，および nan (None)- list と同等のスライスなどの操作- 配列生成／変形- 数学関数- 乱数の数列の生成- （ファイル読み込み／書き込み）・・・・大抵 pandas というライブラリを使うことが多いため省略  数学で意味のある定数 数学演算で用いられる定数についても numpy はサポートしています．- `numpy.e`：ネイピア数（自然対数の底）- `numpy.euler_gamma`：オイラーの定数（調和級数が自然数の逆数の和）- `numpy.inf`：無限大- `numpy.nan`：nan- `numpy.pi`：円周率<jupyter_code>np.e**(np.pi * 1j) + 1<jupyter_output><empty_output><jupyter_text>上記は数学的に， $$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)`：コピーを生成<jupyter_code>np.arange(0, 1, 0.1)
np.linspace(start=0, stop=1, num=10)
np.linspace(start=0, stop=1, num=10, endpoint=False)
np.zeros((2, 5))
b = np.arange(0,1,0.1).reshape((2,5))
b
np.zeros_like(b)
np.full(shape=(2, 5), fill_value=-1)<jupyter_output><empty_output><jupyter_text>配列の変形や結合など 基本的には numpy.ndarray におけるメソッドとほとんど同じで，第一引数に配列オブジェクトをとるくらいで少し書き方が違うだけです．  例えば，reshape については，<jupyter_code>a = np.arange(0, 1, 0.1)
a.reshape((2, 5))
np.reshape(a, (2,5))<jupyter_output><empty_output><jupyter_text>しかし，メソッドに無い操作もあり，代表格は以下です．- `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)`：配列を回転させる<jupyter_code>a = np.array([[0, 1], [2, 3]])
b = np.array([[4, 5]])
np.concatenate((a,b))
np.concatenate((a, b.T), axis=1)
np.tile([0,1], 2)
a = np.array([10, 1, 1, 10, 100, -1])
np.unique(a)
np.unique(a, return_index=True)
a = np.arange(9).reshape((3,3))
a
np.flip(a)
np.flip(a, axis=0)
np.flip(a, axis=1)
np.roll(a, shift=1)
np.roll(a, shift=1, axis=0)
np.roll(a, shift=1, axis=1)
np.rot90(a)
np.rot90(a, k=2)<jupyter_output><empty_output><jupyter_text>数学関数 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 は引数が多いので，以下に例を示します．<jupyter_code>x = np.arange(0, 10, 1)
y = x**2
x
y
np.gradient(y, axis=0)
x = np.arange(0, 2+1, 1)
y = x
np.trapz(y, x=x)<jupyter_output><empty_output><jupyter_text>$$    2 \int_{-1}^{1} \sqrt{1 - x^2} dx = \pi$$ これについて台形公式で計算してみると，<jupyter_code>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
np.pi*1**2 - circle_area<jupyter_output><empty_output><jupyter_text>台形積分の精度としてはこんなもんでしょう．    乱数生成 v1.17.0 までは np.random.rand のようにメソッドで乱数を取得する書き方ですが，v1.17.0 以降は処理高速化のために，一旦，乱数を返すジェネレータを生成してから配列を返すという使い方が一般的な書き方となりました（実は僕も古い手法しか知りませんでした）．  まずは，以下のようにランダムジェネレータを生成します．<jupyter_code>rng = np.random.default_rng(seed=0)
rng<jupyter_output><empty_output><jupyter_text>ここで，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乗分布に沿ってランダムに値を取得するメソッドなどもありますが，割愛します．<jupyter_code>rng.integers(100, size=10)
rng.integers(low=-2, high=0, size=(2, 3), endpoint=True)
rng.random(size=(2,5))
rng.choice([1, 12, 123], size=(2,3))
rng.choice([1, 12, 123], size=(2,3), p=[0.3, 0.0, 0.7])
rng.permutation(3)
rng.permutation([0, 0, 0, 0, 1])
rng.permutation(["red", "red", "red", "red", "white"])
rng.normal(size=5)
rng.uniform(size=5)<jupyter_output><empty_output>
StarcoderdataJupyterStructuredCleanDedup