NumPy
Contents
NumPy#
NumPyは数値演算に特化したPythonパッケージです。Pythonを利用しているほぼすべてのデータサイエンティストがNumPyを直接的(数値演算、行列演算など)・間接的(pandasやMatplotlibなどのライブラリ)に活用しています。従って、Pythonを利用して統計を学ぶうえで、NumPyの使い方やデータ構造を知ることは有益です。本節ではNumPyで最も重要なデータ型 ndarray
の基本となる操作方法や演算方法を解説します。
ndarray型#
ndarray
型は多次元の配列を扱うためのデータ型です。Python組込みのリスト型に似ていますが、次のような特徴があります。
配列内の要素はすべて同じデータ型である
C言語やFORTRAN言語で実装されており、高速に演算できる
多次元の要素にアクセスするためのインデックスやメソッドが用意されている
ブロードキャスト演算が行える
関数が適用できる
はじめにNumPyをインポートして ndarray
オブジェクトを作成してみましょう。 ndarray
オブジェクトを作成するには array
クラスにリストやタプルなどを渡します。
import numpy as np
int_arr = np.array([1, 2])
オブジェクト内の要素はすべて同じデータ型であり、データ型を指定しない場合は自動的に判定されます。 ndarray
オブジェクトの dtype
属性を参照するとデータ型が確認できます。
int_arr.dtype
dtype('int64')
ndarray
オブジェクトの生成時にデータ型を明示する場合には、引数 dtype
にデータ型を渡します。データ型の詳細は 公式ドキュメント を参照してください。
float_arr = np.array([1, 2], dtype=np.float32)
float_arr.dtype
dtype('float32')
型変換を行うには astype
メソッドの引数にデータ型を渡します。
float_arr.astype(np.int32)
array([1, 2], dtype=int32)
astype
メソッドの引数にはクラス名の文字列も渡せます。
float_arr.astype("int32")
array([1, 2], dtype=int32)
Note
ndarray
オブジェクトを「配列」と呼ぶ場合があります。
多次元配列#
ndarray
型は多次元の要素を格納できます。次のコードでは2次元の配列( arr_2d
)、3次元の配列( arr_3d
)を生成しています。
arr_2d = np.array([[1, 10], [2, 20], [3, 30]])
arr_2d
array([[ 1, 10],
[ 2, 20],
[ 3, 30]])
arr_3d = np.array(
[[[1, 10], [2, 20], [3, 30]], [[100, 1000], [200, 2000], [300, 3000]]]
)
arr_3d
array([[[ 1, 10],
[ 2, 20],
[ 3, 30]],
[[ 100, 1000],
[ 200, 2000],
[ 300, 3000]]])
配列の次元数は ndim
属性にアクセスして確認できます。
arr_2d.ndim
2
arr_3d.ndim
3
配列の形状(次元ごとの要素数)は shape
属性にアクセスして確認できます。
arr_2d.shape
(3, 2)
arr_3d.shape
(2, 3, 2)
転置#
配列を転置(軸の入れ替え)する場合は transpose メソッドを実行するか、 T
属性にアクセスします。
arr_2d.transpose()
array([[ 1, 2, 3],
[10, 20, 30]])
arr_2d.T
array([[ 1, 2, 3],
[10, 20, 30]])
arr_3d.T
array([[[ 1, 100],
[ 2, 200],
[ 3, 300]],
[[ 10, 1000],
[ 20, 2000],
[ 30, 3000]]])
ndarrayオブジェクトの生成#
NumPyでは形状(各次元の要素数)を指定したり、特定の値や乱数で埋めた ndarray
オブジェクトを生成できます。本項では ndarray
オブジェクトのさまざまな生成方法を紹介します。
arange
関数は指定した範囲の配列を生成します。基本的な使い方はPython組込みの range
関数と同様です。引数を1つ渡した場合には渡した値が要素数となり、0から自然数が連番で割り当てられます。
np.arange(3)
array([0, 1, 2])
第1引数に開始位置、第2引数に終了位置(第2引数の値は含まれない)を指定すると、指定した範囲で配列を生成します。また、第3引数に増分値を指定できます。 range
関数とは異なり、 arange
関数は小数値も扱えます。
np.arange(0, 2.5, 0.5)
array([0. , 0.5, 1. , 1.5, 2. ])
zeros
関数では指定した要素分の配列を0で埋めて生成します。
np.zeros(3)
array([0., 0., 0.])
ones
関数では1で埋めた配列を生成します。使い方は zeros
関数と同様です。要素数をリストやタプルで渡すと、多次元の配列を生成できます。次のコードでは2行2列の2次元配列を生成しています。
np.ones([2, 2])
array([[1., 1.],
[1., 1.]])
random
モジュールにはさまざまな乱数を生成する関数が用意されています。次のコードでは連続一様分布で生成した乱数から3行3列の配列を生成しています。
rng = np.random.default_rng(1)
rng.random(size=(3, 3))
array([[0.51182162, 0.9504637 , 0.14415961],
[0.94864945, 0.31183145, 0.42332645],
[0.82770259, 0.40919914, 0.54959369]])
要素へのアクセス#
ndarray
オブジェクトはリスト型と同様に要素の参照や代入が行えます。インデックスが多次元のデータに対応しており、組込みのリスト型などと比較してより柔軟に扱えます。本項では次の2つの配列にアクセスします。
arr1 = np.arange(1, 4)
arr1
array([1, 2, 3])
ndarray
オブジェクトの reshape
メソッドでは配列の形状を変更できます。次のコードでは12個の要素を持つ1次元配列を4行3列の2次元配列に変換しています。
arr2 = np.arange(1, 13).reshape(4, 3)
arr2
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12]])
要素を参照するには添字に位置を指定します。
arr1[0]
1
添字にはスライス記法が利用できます。次のコードでは0から数えて1番目から2番目の位置の要素を参照しています。
arr1[1:3]
array([2, 3])
2次元の配列の場合、1次元の配列の各要素が1次元の配列をもつ構造になります。2次元の配列の位置を添字で指定すると、指定した位置の行が参照されます。
arr2[0]
array([1, 2, 3])
添字には各次元の位置をカンマで区切って指定できます。次のコードでは2行3列目の要素を参照できます。
arr2[1, 2]
6
特定の次元の要素をスライス記法で参照もできます。次のコードでは0行目1-2列目の要素を参照しています。
arr2[0, 1:3]
array([2, 3])
リスト型と同様に添字に指定した位置に値を代入できます。
arr1[1] = 10
arr1
array([ 1, 10, 3])
arr2[1, :2] = 100
arr2
array([[ 1, 2, 3],
[100, 100, 6],
[ 7, 8, 9],
[ 10, 11, 12]])
where 関数は条件に合致した要素のインデックスを返します。次のコードでは arr3
の要素が2に該当するインデックスを取得しています。
arr3 = np.array([1, 2, 1, 2])
np.where(arr3 == 2)
(array([1, 3]),)
次のコードでは arr3
の要素が2より小さい場合に該当するインデックスを取得しています。
np.where(arr3 < 2)
(array([0, 2]),)
where
関数の実行結果を配列の添字に渡せます。次のコードでは arr3
から要素の値が2であるデータを抽出しています。
arr3[np.where(arr3 == 2)]
array([2, 2])
値を代入することで、指定した条件の要素を変更できます。
arr3[np.where(arr3 == 2)] = 22
arr3
array([ 1, 22, 1, 22])
ブロードキャスト#
複数の要素を数値演算するケースを考えてみます。さまざまな方法がありますが、例としてPythonの組み込みのリストを次のコードのようにfor文で繰り返して処理する方法が挙げられます。
li = [1, 2, 3]
[x + 1 for x in li]
[2, 3, 4]
このように、組込み型で数値演算を行うには冗長となり、処理速度にも課題があります。 ndarray
オブジェクトではブロードキャスト演算が行えます。次のコードのように配列に対して演算子を利用した演算ができ、高速に処理できます。
arr1 = np.array([1, 2, 3])
arr1 + 1
array([2, 3, 4])
次のコードのように配列同士の演算も演算子が利用できます。
arr2 = np.arange(1, 13).reshape(4, 3)
arr2
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12]])
arr1 + arr2
array([[ 2, 4, 6],
[ 5, 7, 9],
[ 8, 10, 12],
[11, 13, 15]])
関数の適用#
ndarray
オブジェクトの全ての要素に対して関数を適用する仕組みがあります。このような関数をユニバーサル関数または略してufuncなどと表記されます。
次のコードでは power
関数を利用して配列のべき乗を算出しています。
np.power(arr1, arr2)
array([[ 1, 4, 27],
[ 1, 32, 729],
[ 1, 256, 19683],
[ 1, 2048, 531441]])
ndarray
オブジェクトにはNumPyの関数のほか、組込み関数やユーザが作成した関数を適用できます。次のコードでは組込みの abs
関数を利用して絶対値を算出しています。この abs
関数のように単一の値(スカラ型)を引数とする関数を適用した場合には、それぞれの要素に対して関数が適用されます。
abs(np.array([1, -2, 3]))
array([1, 2, 3])
次の組込みの sum
関数のように複数の値(イテラブル)を引数とする関数を適用した場合には、すべてのデータに対して関数が適用されます。
sum(np.array([1, 2, 3]))
6
次のコードでは my_func
という関数を作成し、配列に適用しています。
def my_func(x):
return x ** 2 + 1
my_func(arr1)
array([ 2, 5, 10])
メソッドの適用#
ndarray
型には配列を操作するさまざまなメソッドが用意されています。代表的なメソッドを次に挙げます。
記述統計
sum
,mean
,std
形状の操作
reshape
,flatten
ブール演算
all
,any
次のコードでは mean
メソッドを実行して配列の算術平均値を算出しています。
arr1
array([1, 2, 3])
arr1.mean()
2.0
次のコードでは sum
メソッドを実行して配列の合計値を算出しています。
arr2
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12]])
arr2.sum()
78
次のコードではブール演算をしています。
all
メソッドはすべての要素が True
の場合は True
を返し、それ以外は False
を返します。
any
メソッドはいずれかの要素が True
の場合は True
を返し、それ以外は False
を返します。
arr_bool1 = np.array([True, True, True])
arr_bool2 = np.array([True, False, True])
arr_bool3 = np.array([False, False, False])
arr_bool1.all()
True
arr_bool2.all()
False
arr_bool3.all()
False
arr_bool1.any()
True
arr_bool2.any()
True
arr_bool3.any()
False
多次元配列の場合はメソッドの引数 axis
に指定した軸に対して演算します。
arr2
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12]])
arr2.sum(axis=0)
array([22, 26, 30])
arr2.sum(axis=1)
array([ 6, 15, 24, 33])
軸(axis)の概念は少々複雑であるため、補足します。まずは0番目の次元( axis=0
)に該当する要素を取り出してみます。
arr2[0, :]
array([1, 2, 3])
arr2[1, :]
array([4, 5, 6])
arr2[2, :]
array([7, 8, 9])
arr2[3, :]
array([10, 11, 12])
取り出された要素を、順番に演算していきます。
この場合では0番目の要素となる 1, 4, 7, 10
が演算され、次に1番目の要素が 2, 5, 8, 11
が演算されます。
arr2.sum(axis=0)[0] # == sum([1, 4, 7, 10])
22
arr2.sum(axis=0)[1] # == sum([2, 5, 8, 11])
26
同様に1番目の次元( axis=1
)に該当する要素を取り出してみます。
arr2[3, :]
array([10, 11, 12])
arr2[:, 0]
array([ 1, 4, 7, 10])
arr2[:, 1]
array([ 2, 5, 8, 11])
arr2[:, 2]
array([ 3, 6, 9, 12])
1番目の次元も取り出された要素を、順番に演算していきます。
この場合では0番目の要素となる 1, 2, 3
が演算され、次に1番目の要素が 4, 5, 6
が演算されます。
arr2.sum(axis=1)[0] # == sum([1, 2, 3])
6
arr2.sum(axis=1)[1] # == sum([4, 5, 6])
15
乱数の生成#
NumPyでは PCG64 という擬似乱数生成器を利用して乱数の値や配列を生成できます。
乱数を生成するには default_rng 関数を実行し、 Generator インスタンスを生成します。
Warning
以前のNumPyでは メルセンヌ・ツイスタ(MT19937) という擬似乱数列生成器が利用されていました。
np.random.random()
のように記述されたコードは legacy generators と呼ばれ、非推奨となっています。
from numpy.random import default_rng
rng = default_rng(1)
type(rng)
numpy.random._generator.Generator
random メソッドは0から1までのランダムな少数値を返します。
rng.random()
0.5118216247002567
配列を生成する場合は引数 size
に要素数を渡します。
rng.random(size=3)
array([0.9504637 , 0.14415961, 0.94864945])
多次元の配列を生成するには次元ごとの要素数を格納したリストやタプルなどを渡します。次のコードでは3行2列(0次元目が3、1次元目が2)の乱数をもつ配列生成しています。
rng.random(size=(3, 2))
array([[0.31183145, 0.42332645],
[0.82770259, 0.40919914],
[0.54959369, 0.02755911]])
normal メソッドは標準正規分布に従う乱数を生成します。デフォルトでは平均0、標準偏差1の乱数を生成します。
rng.normal(size=10)
array([ 0.02842224, 0.54671299, -0.73645409, -0.16290995, -0.48211931,
0.59884621, 0.03972211, -0.29245675, -0.78190846, -0.25719224])
平均値は引数 loc
、標準偏差は引数 scale
で指定できます。
rng.normal(loc=5, scale=100, size=(5, 5))
array([[ 5.81421805, -22.56029053, 134.40638144, 105.67243153,
-266.1162479 ],
[-183.9013246 , -12.47720921, -37.21904116, 26.36429975,
26.7321931 ],
[ 216.78387551, -106.20207627, -32.76050071, 209.27716075,
69.67029962],
[ 71.30633724, -46.40063717, -159.80751709, 21.74647442,
15.90140878],
[-117.73520542, -63.32266618, -2.20436797, -89.47516231,
-4.82699679]])
次のコードでは normal
メソッドによって生成されたデータをPloly Expressを利用して、ヒストグラムに可視化しています。Ploly Expressについては別の資料で解説します。
import plotly.express as px
px.histogram(rng.normal(size=1000))