NumPy#

in English or the language of your choice.

# 警告メッセージを非表示
import warnings
warnings.filterwarnings("ignore")

array#

このパッケージは,数値計算をする上で重要な役割を果たし,特に,行列計算に威力を発揮する。NumPyは「ナンパイ」と読む。

慣例としてnpとして読み込む。

import numpy as np

基本となる関数がnp.array()であり,次のコードでは1次元配列を作る。

arr = np.array([10, 20, 30, 40, 50])
arr
array([10, 20, 30, 40, 50])
type(arr)
numpy.ndarray

次に2次元配列、即ち、行列を作る。

mat = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]])
mat
array([[ 1,  2,  3,  4],
       [ 5,  6,  7,  8],
       [ 9, 10, 11, 12],
       [13, 14, 15, 16]])
print(type(arr))
print(type(mat))
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>

arrmatNumPyndarrayn次元array)というデータ型(クラス)である。[]に囲まれた数字が並んでいるがリストとではない。従って,要素を抽出して直接リストとして扱うことはできない。そのためにはリストへの変換が必要になるが,その方法については後述する。

1次元配列:要素の抽出#

arrの要素を抽出するには要素のインデックスを使う。

arr[1]
20

複数の要素を抽出するにはインデックスをリストとして使う。

arr[[0,1,3]]
array([10, 20, 40])

要素を連続で抽出するスライシング(slicing)も使うことができる。

arr[1:4]
array([20, 30, 40])

2次元配列:要素の抽出#

\(i\)行目の第\(j\)列目要素にアクセスするためにはmat[i,j]と書く。[,],を挟んで左が行を表し,右が列を示す。

[行のインデックス,列のインデックス]

コードはキーストロークが少なく,簡単なものが良いと考えられている。一方で,Pythonは初心者に易しい言語だと言われるが,それでも関数・メソッドの数は膨大で,そのオプションとの組み合わせを考えるとまさしく「無数」にあると言っても過言ではない。そのため初心者にとって関数の使い方やオプションの書き方を間違う可能性は小さくない。さらに,自分が書いたコードを数週間・数ヶ月後に読み直すと,何をしようとしているのか分からないという状況が生じることもある。従って,初心者にとっては以下の点が非常に重要になる。

  • 間違いにくいコードの書き方を覚える。

  • 高い可読性を意識したコードの書き方を覚える。

このスタンスに基づいて以下のルールに従って説明する。

  1. [,]内の,を省略可能な場合であっても省略しない。

  2. [,]内の,の左または右を省略できる場合であっても省略しない。

行または列を連続選択する(slicing)場合を考えよう。以下で説明するように:を使うが

start:end

となる。ここでstartとは選択する要素の最初インデックスであり,endは選択する最後の要素の次のインデックスである(リストの場合と同じ)。上のルールに従うと,

  • ,の左または右が:のみの場合,「全て」という意味になる。

  • :の左を省略すると「最初から」という意味になる。

  • :の右を省略すると「最後まで」という意味になる。

これを読むだけでは分かりにくいと思うので,以下の例に目を通してもう一度この箇所の説明を読んむことを推奨する。

mat[0,1]
2

\(i\)行の抽出はmat[i,:]で可能である。:は「列の全て」という意味になる。

r0 = mat[0,:]
r0
array([1, 2, 3, 4])

抽出した行は上で説明した1次元配列なのでインデックスやスライシングを使って要素にアクセスすることができる。

複数の行の抽出は次の方法で可能である。

mat[1:3,:]
array([[ 5,  6,  7,  8],
       [ 9, 10, 11, 12]])

第3行目は含まれないことに注意しよう。リストの要素の取り出し方と同じように:の右のインデックスの行は含まれない。

(注意)

,:を省略してmat[1:3]と書いてもエラーは発生せず同じ結果が返されるが,,:があることにより,行を抽出しており列は全て選択されていることが明示的になる。

\(i\) 列目を抽出したい場合は次のコードになる。

mat[:, 1]
array([ 2,  6, 10, 14])

複数列の抽出は以下のようにする。

mat[:, 1:3]
array([[ 2,  3],
       [ 6,  7],
       [10, 11],
       [14, 15]])

:の役割を考えると以下はmat自体である。

mat[:,:]
array([[ 1,  2,  3,  4],
       [ 5,  6,  7,  8],
       [ 9, 10, 11, 12],
       [13, 14, 15, 16]])

行列計算#

まず2つの行列(2次元配列array)を作成する。

m1 = np.array([[1, 1], [1, 1]])
m1
array([[1, 1],
       [1, 1]])
m2 = np.array([[2, 2], [2, 2]])
m2
array([[2, 2],
       [2, 2]])

行列の和

要素ごとの和となる。

m2 + m1
array([[3, 3],
       [3, 3]])

行列の差

要素ごとの差となる。

m2 - m1
array([[1, 1],
       [1, 1]])

行列のスカラー積

与えられた定数とそれぞれの要素の積となる。

10 * m1
array([[10, 10],
       [10, 10]])
m1/2  # 1/2をかけるのと同じ
array([[0.5, 0.5],
       [0.5, 0.5]])

行列の積:バージョン1

*を使うと要素どうしの積となる。数学で学んだ行列の式とは異なるので注意すること。

m1*m2
array([[2, 2],
       [2, 2]])

行列の積:バージョン2

@を使うと数学で学ぶ行列の積となる。

m1@m2
array([[4, 4],
       [4, 4]])

numpyの関数dot()@と同じとなる。

np.dot(m1,m2)
array([[4, 4],
       [4, 4]])

転置行列

数学で学ぶ転置行列と同じ。m3を使って説明する。

m3 = np.array([[1,2,3],[4,5,6]])
m3
array([[1, 2, 3],
       [4, 5, 6]])

m3のメソッドtranspose()を使う。

m3.transpose()
array([[1, 4],
       [2, 5],
       [3, 6]])

.transpose()の省略形として.Tを使うこともできる。

m3.T
array([[1, 4],
       [2, 5],
       [3, 6]])

逆行列

数学で学ぶ逆行列である。m4を使い説明する。逆行列を計算するためにnumpylinalg(linear algebra, 線形代数)というサブパッケージの中にあるinvという関数を読み込む。

from numpy.linalg import inv

m4 = np.array([[1,2],[3,4]])
inv(m4)
array([[-2. ,  1. ],
       [ 1.5, -0.5]])

NumPy使用時によく使う属性とメソッド#

以前も説明したが、メソッドとはオブジェクト特有の関数であり、オブジェクトの属性の1つである。もう1つの属性の種類にデータ属性(例えば,行数)がある。あるオブジェクトにどのような属性があるかは関数dir()を使うことにより確認できる。

dir(m3)
['T',
 '__abs__',
 '__add__',
 '__and__',
 '__array__',
 '__array_finalize__',
 '__array_function__',
 '__array_interface__',
 '__array_prepare__',
 '__array_priority__',
 '__array_struct__',
 '__array_ufunc__',
 '__array_wrap__',
 '__bool__',
 '__buffer__',
 '__class__',
 '__class_getitem__',
 '__complex__',
 '__contains__',
 '__copy__',
 '__deepcopy__',
 '__delattr__',
 '__delitem__',
 '__dir__',
 '__divmod__',
 '__dlpack__',
 '__dlpack_device__',
 '__doc__',
 '__eq__',
 '__float__',
 '__floordiv__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getitem__',
 '__getstate__',
 '__gt__',
 '__hash__',
 '__iadd__',
 '__iand__',
 '__ifloordiv__',
 '__ilshift__',
 '__imatmul__',
 '__imod__',
 '__imul__',
 '__index__',
 '__init__',
 '__init_subclass__',
 '__int__',
 '__invert__',
 '__ior__',
 '__ipow__',
 '__irshift__',
 '__isub__',
 '__iter__',
 '__itruediv__',
 '__ixor__',
 '__le__',
 '__len__',
 '__lshift__',
 '__lt__',
 '__matmul__',
 '__mod__',
 '__mul__',
 '__ne__',
 '__neg__',
 '__new__',
 '__or__',
 '__pos__',
 '__pow__',
 '__radd__',
 '__rand__',
 '__rdivmod__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__rfloordiv__',
 '__rlshift__',
 '__rmatmul__',
 '__rmod__',
 '__rmul__',
 '__ror__',
 '__rpow__',
 '__rrshift__',
 '__rshift__',
 '__rsub__',
 '__rtruediv__',
 '__rxor__',
 '__setattr__',
 '__setitem__',
 '__setstate__',
 '__sizeof__',
 '__str__',
 '__sub__',
 '__subclasshook__',
 '__truediv__',
 '__xor__',
 'all',
 'any',
 'argmax',
 'argmin',
 'argpartition',
 'argsort',
 'astype',
 'base',
 'byteswap',
 'choose',
 'clip',
 'compress',
 'conj',
 'conjugate',
 'copy',
 'ctypes',
 'cumprod',
 'cumsum',
 'data',
 'diagonal',
 'dot',
 'dtype',
 'dump',
 'dumps',
 'fill',
 'flags',
 'flat',
 'flatten',
 'getfield',
 'imag',
 'item',
 'itemset',
 'itemsize',
 'max',
 'mean',
 'min',
 'nbytes',
 'ndim',
 'newbyteorder',
 'nonzero',
 'partition',
 'prod',
 'ptp',
 'put',
 'ravel',
 'real',
 'repeat',
 'reshape',
 'resize',
 'round',
 'searchsorted',
 'setfield',
 'setflags',
 'shape',
 'size',
 'sort',
 'squeeze',
 'std',
 'strides',
 'sum',
 'swapaxes',
 'take',
 'tobytes',
 'tofile',
 'tolist',
 'tostring',
 'trace',
 'transpose',
 'var',
 'view']

このリストの中にtranspose()Tがあるのが確認できる。この中でよく使うのがshapeであり,行と列の数を確認する場合に有用なデータ属性である。

m3.shape
(2, 3)

データ属性には()がない。一方,メソッド属性は()が必要となる。言い換えると,メソッドの()は「実行する」ということを意味する。

次に,抽出した行または列をリストに変換するメソッドについて説明する。

r0 = mat[0,:]
type(r0)
numpy.ndarray

r0のデータ型はNumPyndarrayである。これをリストに変換するためにtolist()というメソッドを使う。

r0_list = r0.tolist()

r0_list
[1, 2, 3, 4]
type(r0_list)
list

よく使うNumPy関数#

ルート \(\left(\sqrt x\right)\)

np.sqrt(4)
2.0

sqrtは square root の略。

底が\(e\)の指数関数(\(e^x\)

np.exp(10)
22026.465794806718

expは exponentiation の略

自然対数(\(\log_ex\)または\(\ln x\)

np.log(10)
2.302585092994046

0\(N\)個のarrayを作る。

np.zeros(N)
np.zeros(10)
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])

1(float)が\(N\)個のarrayを作る。

np.ones(N)
np.ones(10)
array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])

\(a\)から\(b\)までの区間を等間隔に割った\(N\)個の数字を返す。

np.linspace(a,b,N)
np.linspace(0,1,5)
array([0.  , 0.25, 0.5 , 0.75, 1.  ])

\(a\)から\(b\)までの区間で\(m\)ステップずつ増加し等間隔に割った数字を返す(\(b\)は含まない)。

np.arange(a,b,m)

m = 1の場合,組み込み関数のrange(a,b)と同じ数字を生成するが,返り値がarrayであることが異なる。

np.arange(5,10,0.5)
array([5. , 5.5, 6. , 6.5, 7. , 7.5, 8. , 8.5, 9. , 9.5])

標本平均

xが数字のarrayやリストの場合

np.mean(x)\(=\bar{x}=\frac{1}{n}\sum_{i=1}^{n}x_i\)

xx = [1,2,3,4,5,6]
np.mean(xx)
3.5

標本中央値

xが数字のarrayやリストの場合

np.median(x)

np.median(xx)
3.5

標本分散

xが数字のarrayやリストの場合

np.var(x, ddof=0)\(=s_x^2=\dfrac{1}{n-\text{ddof}}\sum_{i=1}^n\left(x_i-\bar{x}\right)^2\)ddof=0がデフォルト)

(注意)計量経済学で習う分散の不偏推定量はddof=1が必要!

np.var(xx,ddof=1)
3.5

標本標準偏差

xが数字のarrayやリストの場合

np.std(x, ddof=0)\(=s_x=\sqrt{s_x^2}\)ddof=0がデフォルト)

(注意)標本標準偏差の場合,必ずしもddof=1により不偏推定量とはならないが,通常ddof=1を用いる。

np.std(xx,ddof=1)
1.8708286933869707

標本共分散

2次元以上のarrayやリストの場合

np.cov(xy, ddof=0)\(=c_{xy}=\dfrac{1}{n-\text{ddof}}\sum_{i=1}^n(x_i-\bar{x})(y_i-\bar{y})\)ddof=0がデフォルト)

(注意1)計量経済学で習う分散の不偏推定量はddof=1が必要!

下の計算結果

  • \(c_{xy}=-0.6\)

  • \(s_x^2=3.5\) ([1,2,3,4,5,6]の分散)

  • \(s_y^2=4.4\) ([1,6,2,5,3,1]の分散)

x = [1,2,3,4,5,6]
y = [1,6,2,5,3,1]

cov_xy = np.cov([x,y],ddof=1)
cov_xy
array([[ 3.5, -0.6],
       [-0.6,  4.4]])

ここでそれぞれの数字は次を表している。

  • -0.6xyの共分散であり,次のように値を抽出できる。

    `cov_xy[0,1]` もしくは `cov_xy[1,0]`
    
  • 3.5xの分散であり,次のように値を抽出できる。

    `cov_xy[0,0]`
    
  • 4.4yの分散であり,次のように値を抽出できる。

    `cov_xy[1,1]`
    

標本相関係数

2次元以上のarrayやリストの場合

np.corrcoef(xy)\(=r_{xy}=\dfrac{c_{xy}}{s_x\cdot s_y}\)

(注意)ddofの影響はない。

下の計算結果

  • \(r_{xy}=-0.152...\)

  • \(r_{xx}=r_{yy}=1\)

corr_xy = np.corrcoef([x,y])
corr_xy
array([[ 1.        , -0.15289416],
       [-0.15289416,  1.        ]])

ここでそれぞれの数字は次を表している。

  • -0.15289416xyの相関係数であり,次のように値を抽出できる。

    `corr_xy[0,1]` もしくは `corr_xy[1,0]`
    
  • 1.0xxの相関関係であり,次のように値を抽出できる。

    `corr_xy[0,0]`
    
  • 1.0yyの相関係数であり,次のように値を抽出できる。

    `corr_xy[1,1]`
    

array vs list#

ここではlistNumPyarrayの重要な違いについて説明する。 次のリストのそれぞれの要素に10を足したいとしよう。

list0 = [1.0, 2.0, 3.0, 4.0, 5.0]

forループを使うと次のようになる。

list1 = []
for i in list0:
    list1.append(i + 10)

list1
[11.0, 12.0, 13.0, 14.0, 15.0]

もう1つの方法として内包標記(list comprehension)がある。

list2 = [i + 10 for i in list0]
list2
[11.0, 12.0, 13.0, 14.0, 15.0]

どちらの方法を使ったとしても複雑さが残る。また次のコードでは10を最後に追加するだけである。

list0 + [10]
[1.0, 2.0, 3.0, 4.0, 5.0, 10]

より簡単なコードで実行できれば良いが,以下のコードではエラーが発生する。

list0 + 10
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[55], line 1
----> 1 list0 + 10

TypeError: can only concatenate list (not "int") to list

これを実現するのがNumPyarrayである。

まずarrayを作成する。

arr0 = np.array(list0)
arr0
array([1., 2., 3., 4., 5.])
arr0 + 10
array([11., 12., 13., 14., 15.])

この機能はベクトル演算(Vectorization)と呼ばれ、ループを使わずに個々の要素に直接働きかけ計算している。上記のコードは次の計算を行っている。

arr0 + np.array([10]*5)
array([11., 12., 13., 14., 15.])

裏でarr0の長さに合わせて10を「拡張」し計算している。この機能により、より高速な計算が可能となるばかりか、より短いコードでそれを実現できる。+, -, *, ** や他の関数にも同様に使うことができる。以下で例を挙げる。

arr0 - 5
array([-4., -3., -2., -1.,  0.])
arr0 * 10  
array([10., 20., 30., 40., 50.])
arr0 ** 2
array([ 1.,  4.,  9., 16., 25.])
np.sqrt(arr0)
array([1.        , 1.41421356, 1.73205081, 2.        , 2.23606798])
np.log(arr0)
array([0.        , 0.69314718, 1.09861229, 1.38629436, 1.60943791])

次の計算も可能である。

y = arr0 * 2 + np.sqrt(arr0) + 10
y
array([13.        , 15.41421356, 17.73205081, 20.        , 22.23606798])

この機能はNumPyの行列でも有効である。

mat0 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
mat0
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])
mat0 * 10
array([[10, 20, 30],
       [40, 50, 60],
       [70, 80, 90]])
np.sqrt(mat0)
array([[1.        , 1.41421356, 1.73205081],
       [2.        , 2.23606798, 2.44948974],
       [2.64575131, 2.82842712, 3.        ]])
np.log(mat0)
array([[0.        , 0.69314718, 1.09861229],
       [1.38629436, 1.60943791, 1.79175947],
       [1.94591015, 2.07944154, 2.19722458]])