Python の基礎 (NumPy、pandas)

2023年9月10日

はじめに

Python を使う醍醐味は、標準外で提供されている各種パッケージにある。ここでは、その中で NumPy (一部 SciPy)、pandas の基礎について述べる。

配列

NumPy は科学技術計算用のパッケージで、配列や行列などを提供する。パッケージがインストールされていれば、以下のようにインポートする。

>>> import numpy as np

numpy はよく np として使われる。

次のように配列 (ndarray) を定義できる。

>>> a = np.array([1, 2, 3])
>>> a
array([1, 2, 3])

>>> type(a)
<class 'numpy.ndarray'>

これだけだとリストと違いがなさそうに見えるが、すべての要素の型は一致している必要がある。数値計算においては、リストよりも効率的に処理される。

要素の型は、元にしたリストによる。

>>> a = np.array([1, 2, 3])
>>> a.dtype
dtype('int64')

>>> a = np.array([1., 2., 3.])
>>> a.dtype
dtype('float64')

要素の型を指定できる。

>>> a = np.array([1, 2, 3], dtype=float)
>>> a
array([1., 2., 3.])

>>> a.dtype
dtype('float64')

続きの数値なら、np.arange() で作れる。

>>> np.arange(1, 11)
array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10])

>>> np.arange(1, 11, 2)
array([1, 3, 5, 7, 9])

>>> np.arange(10, 0, -1)
array([10,  9,  8,  7,  6,  5,  4,  3,  2, 1])

繰り返しを行う np.tile() というものもある。

>>> np.tile([1, 2], 3)
array([1, 2, 1, 2, 1, 2])

要素のアクセス

各要素にアクセスするには、添字を使う (0 から始まる)。

>>> a[0]
1

"0:2" の形で 1 要素目から 2 要素目までの部分配列を取り出せる。

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

このような書き方をスライス表記という。

添字のリストを与えることもできる。

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

bool のリストを与えることもできる (これをマスクという)。

>>> a[[True, False, True]]
array([1, 3])

要素のデータ型

要素の値を変更する際、データ型に注意すること。

>>> a = np.array([1, 2, 3])
>>> a
array([1, 2, 3])

>>> a[0] = 2.5
>>> a
array([2, 2, 3])

>>> a.dtype
dtype('int64')

上の a の要素の型は int64 になっているので、要素に浮動小数点を入れると、小数点以下が黙って切り捨てられてしまう。

ちなみに、Juila だときっちり怒られる。

julia> a = [1, 2, 3]
3-element Vector{Int64}:
 1
 2
 3

julia> a[1] = 2.5
ERROR: InexactError: Int64(2.5)

R の場合は、数値のデフォルト型が浮動小数点数型なので問題にならない。

> a <- c(1, 2, 3)
> a
[1] 1 2 3

> typeof(a)
[1] "double"

> a[1] <- 2.5
> a
[1] 2.5 2.0 3.0

このあたりの違いに気をつけること。

ベクトルのサイズ

長さを知りたければ len() を使えばよい。

>>> len(a)
3

属性 size で大きさを得ることもできる。

>>> a.size
3

要素の追加や削除

np.append() で末尾に要素を追加できる。ただし、もとの配列は変更されない。

>>> np.append(a, 4)
array([1, 2, 3, 4])

>>> a
array([1, 2, 3])

要素を削除するには np.delete() を使う。

>>> b = np.append(a, 4)
>>> b
array([1, 2, 3, 4])

>>> np.delete(b, 3)
array([1, 2, 3])

np.delete() の第 2 引数は削除したい要素の添字である。

添字指定でも同じことができる。

>>> b[[x for x in range(len(b)) if x != 3]]
array([1, 2, 3])

途中に要素を追加するには np.insert() を用いる。

>>> np.insert(a, 1, 5)
array([1, 5, 2, 3])

配列の連結

末尾に配列を追加する場合は np.append() を用いる。

>>> np.append(a, [5, 6, 4])
array([1, 2, 3, 5, 6, 4])

配列の代入

配列の要素を先頭とそれ以外に分けて変数に入れることができる。

>>> a, *b = np.array([1, 2, 3])
>>> a
1

>>> b
[2, 3]

配列を別の変数に代入する場合には注意が必要である。

>>> a = np.array([1, 2, 3])
>>> b = a
>>> b[0] = 2
>>> b
array([2, 2, 3])

>>> a
array([2, 2, 3])

上の例では、b の要素の値を変更すると、a の要素の値も変わってしまう。代入はオブジェクトを入れる箱に名前を付けるだけのため、b は a と同じオブジェクトを見ているからである。b に a のコピーを割り当てたい場合は、copy() を用いる。

>>> a = np.array([1, 2, 3])
>>> b = a.copy()
>>> b[0] = 2
>>> b
array([2, 2, 3])

>>> a
array([1, 2, 3])

大きさだけ確保する

とりあえずベクトルの大きさだけ確保するには、np.zeros() を使えばよい。

>>> a = np.zeros(3)
>>> a
array([0., 0., 0.])

np.tile() でもよい。

>>> a = np.tile(0., 3)
>>> a
array([0., 0., 0.])

ある値で埋める

全要素を特定の値にしたい場合などは、fill() を使う。

>>> a = np.array([1, 2, 3])
>>> a
array([1, 2, 3])

>>> a.fill(0)
>>> a
array([0, 0, 0])

以下のようにしてもよい。

>>> a[:] = 0

結合する

配列を結合するには、np.hstack() を使う。

>>> a = np.array([1, 2, 3])
>>> b = np.array([4, 5, 6])
>>> np.hstack((a, b))
array([1, 2, 3, 4, 5, 6])

最初と最後を取り出す

配列の最初と最後の要素を取り出すには、次のようにする。

>>> a = np.arange(1, 101)
>>> a[:3]
array([1, 2, 3])

>>> a[-3:]
array([ 98,  99, 100])

":3" は 0、1、2、"-3:" は -3、-2、-1 となる。

要素が含まれるかどうか

要素が含まれるかどうかは、in、not in でわかる。

>>> 1 in np.array([1, 2, 3])
True

>>> 4 in np.array([1, 2, 3])
False

>>> 4 not in np.array([1, 2, 3])
True

要素の並び替え

要素を並び替えるには sort() を用いる。

>>> a = np.array([1, 2, 3, 5, 6, 4])
>>> a.sort()
>>> a
array([1, 2, 3, 4, 5, 6])

もとの配列を変更したくない場合は、sorted() を用いる。

>>> sorted(a)
[1, 2, 3, 4, 5, 6]

>>> a
array([1, 2, 3, 5, 6, 4])

単純に逆にするには、np.flip() を用いる。

>>> np.flip(a)
array([4, 6, 5, 3, 2, 1])

重複要素の削除

重複要素を削除するには np.unique() を使う。

>>> a = np.array([1, 2, 2, 5, 4, 6, 3])
>>> np.unique(a)
array([1, 2, 3, 4, 5, 6])

もとの添字を得ることもできる。

>>> u, idx = np.unique(a, return_index=True)
>>> idx
array([0, 1, 6, 4, 3, 5])

>>> a[idx]
array([1, 2, 3, 4, 5, 6])

最大値と最小値

要素の最大値、最小値を得るには、それぞれ max()、min() を用いる。

>>> rng = np.random.default_rng()
>>> a = rng.random(5)
>>> a
array([0.94425088, 0.36188794, 0.62370355, 0.62279579, 0.29481583])

>>> a.max()
0.9442508774201377

>>> a.min()
0.2948158309981246

np.max()、np.min() でもよい。

>>> np.max(a)
0.9442508774201377

>>> np.min(a)
0.2948158309981246

最大値、最小値のそれぞれの添字がほしい場合は、argmax()、argmin() がある。

>>> a.argmax()
0

>>> a.argmin()
4

np.maximum() で配列同士の大きいほうの要素を取ることができる。これを使ってあるスカラー値で下限を設定することができる。

>>> np.maximum(a, 0.5)
array([0.94425088, 0.5       , 0.62370355, 0.62279579, 0.5       ])

絶対値

np.abs() でそれぞれの要素の絶対値が得られる。

>>> a = np.array([1, -2, 3, -4, 5])
>>> np.abs(a)
array([1, 2, 3, 4, 5])

合計

np.sum()、sum() で合計を取ることができる。

>>> a = np.array([1, 2, 3, 4, 5])
>>> np.sum(a)
15

>> a.sum()
15

>> sum(a)
15

足していく途中がほしい場合は np.cumsum()、cumsum() を使う。

>>> np.cumsum(a)
array([ 1,  3,  6, 10, 15])

>>> a.cumsum()
array([ 1,  3,  6, 10, 15])

積

np.prod()、prod() で各要素の積を取ることができる。

>>> a = np.array([1, 2, 3, 4, 5])
>>> np.prod(a)
120

>>> a.prod()
120

積の途中がほしい場合は np.cumprod()、cumprod() を用いる。

>>> np.cumprod(a)
array([  1,   2,   6,  24, 120])

>>> a.cumprod()
array([  1,   2,   6,  24, 120])

演算の積算

np.ufunc.reduce() を用いると、ある演算子を要素に適用した結果などが得られる。

>>> a = np.array([1, 2, 3, 4, 5])
>>> np.ufunc.reduce(np.add, a)
15

>>> np.ufunc.reduce(np.multiply, a)
120

ここで、np.add や np.multiply は、+ や * の関数である。

初期値を指定できる。

>>> np.ufunc.reduce(np.multiply, a, initial=-1)
-120

積算の途中がほしい場合は、np.ufunc.accumulate() を用いる。

>>> np.ufunc.accumulate(np.add, a)
array([ 1,  3,  6, 10, 15])

階差

np.diff() で階差を作る。

>>> np.diff(np.cumprod([1, 2, 3, 4, 5]))
array([ 1,  4, 18, 96])

配列同士の演算

配列の要素同士を足し合わせるには、+ でよい。

>>> a = np.array([1, 2, 3])
>>> b = np.array([4, 5, 6])
>>> a + b
array([5, 7, 9])

- や * も同様である。片方がスカラーでも機能する。

各要素への関数の適用

配列の各要素へ関数を適用して別の配列を作るには、map() を用いる。

>>> a = np.arange(1, 6)
>>> a
array([1, 2, 3, 4, 5])

>>> np.array(list(map(lambda x: 2*x + 1, a)))
array([ 3,  5,  7,  9, 11])

抽出

filter() で条件に合った要素だけを取り出すことができる。

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

>>> np.array(list(filter(lambda x: x % 2 == 0, a)))
array([ 2,  4,  6,  8, 10])

map() を使って bool 型の添字リストを作る方法もある。

>>> a[list(map(lambda x: x % 2 == 0, a))]
array([ 2,  4,  6,  8, 10])

内包表記でもよい。

>>> np.array([x for x in a if x % 2 == 0])
array([ 2,  4,  6,  8, 10])

もっと直接的な書き方もできる。

>>> a[a % 2 == 0]
array([ 2,  4,  6,  8, 10])

条件に合う場所の添字がほしい場合は、np.argwhere() を用いる。

>>> a = np.arange(2, 11)
>>> a
array([ 2,  3,  4,  5,  6,  7,  8,  9, 10])
>>> np.argwhere(a % 2 == 0)
array([[0],
       [2],
       [4],
       [6],
       [8]])

np.argwhere() は、行列でも使えるため、上のような感じで返ってくるが、1次元配列の場合は次のようにしたほうが便利かもしれない。

>>> np.argwhere(a % 2 == 0).flat[:]
array([0, 2, 4, 6, 8])

flat というのは、たとえば "[[0]]" を添字指定でふつうに取り出すと "[0]" になるが、flat で添字を指定して取り出すと "0" が直接得られる。

条件判定

どれかの要素で条件が成り立つかを調べるには np.any()、any() が使える。また、すべての要素で条件が成り立つかどうかは np.all()、all() でわかる。

>>> a = np.array([1, 3, 5, 8, 9])
>>> np.any(a % 2 == 0)
True

>>> any(a % 2 == 0)
True

>>> np.all(a % 2 == 1)
False

>>> all(a % 2 == 1)
False

>>> a = np.array([1, 3, 5, 7, 9])
>>> np.all(a % 2 == 1)
True

>>> all(a % 2 == 1)
True

条件に合うものが何個あるかを数えるには、np.sum()、sum() を用いればよい。

>>> a = np.array([1, 3, 5, 8, 9, 10])
>>> np.sum(a % 2 == 0)
2

>>> sum(a % 2 == 0)
2

要素の置き換え

要素の置き換えは、以下のようにできる。

>>> a = np.array([1, 2, 3, 4, 5], dtype=float)
>>> a[a % 2 == 0] = np.nan
>>> a
array([ 1., nan,  3., nan,  5.])

ここで、np.nan は NaN (Not a Number) である。np.nan を 0 に置き換えたいとすると、次のようにすればよい。

>>> np.place(a, np.isnan(a), 0)
>>> a
array([1., 0., 3., 0., 5.])

あるいは、この場合は次のようにもできる。

>>> a[np.isnan(a)] = 0
>>> a
array([1., 0., 3., 0., 5.])

masked array というものもある。np.nan を含んだまま合計すると値が np.nan になってしまうが、np.nan を除いて合計を取ることができる。

>>> a.sum()
nan

>>> np.ma.masked_array(a, mask=np.isnan(a)).sum()
9.0

次のようにもできる。

>>> a[np.logical_not(np.isnan(a))].sum()
9.0

行列

次のような形で行列を定義することができる。

>>> a = np.array([[1, 2], [3, 4]])
>>> a
array([[1, 2],
       [3, 4]])

np.matrix() というものもあるが、利用が推奨されていない。

値が 0 の行列がとりあえずほしい場合は、np.zeros() を用いる。

>>> np.zeros((2, 3))
array([[0., 0., 0.],
       [0., 0., 0.]])

要素にアクセスしたければ、カンマ区切りで添字を指定すればよい (0 から始まる)。

>>> a[0, 1]
2

"0:2" のような添字指定で部分行列のを得ることも可能。

>>> a[0:2, 0]
array([1, 3])

範囲による添字指定で先頭と末尾の指定を省略することもできる。

>>> a[1:, 1]
array([4])

>>> a[:-1, 1]
array([2])

ある列全体がほしいといった場合は、":" だけ指定すればよい。

>>> a[:, 1]
array([2, 4])

サイズがほしい場合は、属性 size で得られる。

>>> a.size
4

サイズは全体のサイズである。行列の形がほしい場合は、属性 shape で得られる。

>>> a.shape
(2, 2)

多次元配列

次のようにすると、多次元配列を定義できる。

>>> a = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
>>> a
array([[[1, 2],
        [3, 4]],

       [[5, 6],
        [7, 8]]])

>>> a[0]
array([[1, 2],
       [3, 4]])

>>> a[1]
array([[5, 6],
       [7, 8]])

>>> a[0, :, :]
array([[1, 2],
       [3, 4]])

大きさだけ確保する

とりあえず行列の大きさだけ確保するには、np.zeros() を使えばよい。

>>> a = np.zeros((2, 3))
>>> a
array([[0., 0., 0.],
       [0., 0., 0.]])

np.tile() でもよい。

>>> a = np.tile(0., (2, 3))
>>> a
array([[0., 0., 0.],
       [0., 0., 0.]])

ある値で埋める

全要素を特定の値にしたい場合などは、fill() を使う。

>>> a = np.array([[1, 2], [3, 4]])
>>> a
array([[1, 2],
       [3, 4]])
>>> a.fill(0)
>>> a
array([[0, 0],
       [0, 0]])
array([0, 0, 0])

以下のようにしてもよい。

>>> a[:] = 0

形状を変える

reshape() で形状を変えられる。

>>> a = np.arange(1, 7).reshape(3, 2)
>>> a
array([[1, 2],
       [3, 4],
       [5, 6]])

>>> a = np.arange(1, 7).reshape(3, 2, order="F")
>>> a
array([[1, 4],
       [2, 5],
       [3, 6]])

もとの行列には影響しない。

>>> a.reshape(1, 6)
array([[1, 4, 2, 5, 3, 6]])
>>> a
array([[1, 4],
       [2, 5],
       [3, 6]])

これを使うと、ちょっとした行列を作るのが少し楽になるかもしれない。

>> a = np.array([1, 2, 3, 4]).reshape(2, 2)
>>> a
array([[1, 2],
       [3, 4]])

結合する

配列を結合するには、np.vstack()、np.hstack() を使う。

>>> a = np.array([[1, 2], [3, 4]])
>>> b = np.array([[5, 6], [7, 8]])
>>> np.vstack((a, b))
array([[1, 2],
       [3, 4],
       [5, 6],
       [7, 8]])

>>> np.hstack((a, b))
array([[1, 2, 5, 6],
       [3, 4, 7, 8]])

行列の部分参照を得る

Python の変数間の代入では、オブジェクトの参照渡されるが、NumPy の配列では、配列の部分の参照を渡すことができる。

>>> a = np.array([[1, 2], [3, 4]])
>>> b = a[:, 1].view()
>>> b
array([2, 4])

上の例では、view() で a の部分の参照を b に渡している。実は、スライス自体がこの参照を返すようになっている。つまり、単に以下のようにすればよい。

>>> b = a[:, 1]
>>> b
array([2, 4])

>>> b[:] = 0.
>>> a
array([[1, 0],
       [3, 0]])

b の値を変更すると、a の値が部分的に変更される。

ループ

行ごとのループ

>>> a = np.array([[2, 4], [6, 8]])
>> for x in a:
...     print(x)
... 
[2 4]
[6 8]

列ごとのループ

>>> for x in a.T:
...     print(x)
... 
[2 6]
[4 8]

a.T は a の転置である。あるいは

>>> for x in np.nditer(a, flags=["external_loop"], order="F"):
...     print(x)
... 
[2 6]
[4 8]

要素ごとのループ

>>> for x in np.nditer(a):
...     print(x)
... 
2
4
6
8

>> for x in np.nditer(a, order="F"):
...     print(x)
... 
2
6
4
8

値を変更したい場合は、次のようにする。

>>> with np.nditer(a, op_flags=["readwrite"]) as it:
...     for x in it:
...             x[...] = 2*x
... 
>>> a
array([[ 4,  8],
       [12, 16]])

インデックスがほしい場合は、次のようにする。

>>> it = np.nditer(a, flags=["c_index"])
>>> for x in it:
...     print(it.index, x)
... 
0 2
1 4
2 6
3 8

>>> it = np.nditer(a, flags=["f_index"], order="F")
>>> for x in it:
...     print(it.index, x)
... 
0 2
1 6
2 4
3 8

>>> it = np.nditer(a, flags=["multi_index"])
>>> for x in it:
...     print(it.multi_index, x)
... 
(0, 0) 2
(0, 1) 4
(1, 0) 6
(1, 1) 8

転置

T、transpose()、np.transpose() で行列の転置が得られる。

>>> a = np.array([[1, 2], [3, 4]])
>>> a
array([[1, 2],
       [3, 4]])

>>> a.T
array([[1, 3],
       [2, 4]])

>>> a.transpose()
array([[1, 3],
       [2, 4]])

>>> np.transpose(a)
array([[1, 3],
       [2, 4]])

複素共役行列

conj()、conjugate()、np.conj()、np.conjugate() で複素共役行列が得られる。

>>> a = np.array([[1 + 2j], [3 + 4j]])
>>> a
array([[1.+2.j],
       [3.+4.j]])

>>> a.conj()
array([[1.-2.j],
       [3.-4.j]])

>>> a.conjugate()
array([[1.-2.j],
       [3.-4.j]])

>>> np.conj(a)
array([[1.-2.j],
       [3.-4.j]])

>>> np.conjugate(a)
array([[1.-2.j],
       [3.-4.j]])

行列同士の演算

行列同士の和は + で計算できる。

>>> a = np.array([1, 2, 3, 4]).reshape(2, 2)
>>> b = np.array([5, 6, 7, 8]).reshape(2, 2)
>>> a + b
array([[ 6,  8],
       [10, 12]])

積 * だと、要素同士の積になる。

>>> a * b
array([[ 5, 12],
       [21, 32]])

行列の積を計算したい場合は、@ あるいは np.matmul() を用いる。

>>> a @ b
array([[19, 22],
       [43, 50]])

>>> np.matmul(a, b)
array([[19, 22],
       [43, 50]])

これと似た dot()、np.dot() もある。

>>> a.dot(b)
array([[19, 22],
       [43, 50]])

>>> np.dot(a, b)
array([[19, 22],
       [43, 50]])

累乗も要素ごとの累乗になる。

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

>>> a**2
array([[ 1,  4,  9],
       [16, 25, 36],
       [49, 81, 64]])

行列の累乗を計算したい場合は、np.linalg.matrix_power() を用いる。

>>> np.linalg.matrix_power(a, 2)
array([[ 30,  39,  39],
       [ 66,  87,  90],
       [ 99, 131, 139]])

>>> np.linalg.matrix_power(a, -2)
array([[ 3.74074074, -3.85185185,  1.44444444],
       [-3.25925926,  3.81481481, -1.55555556],
       [ 0.40740741, -0.85185185,  0.44444444]])

単位行列

>>> np.eye(3)
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])

ノルム

>>> a = np.array([1, 2, 3])
>>> np.linalg.norm(a)
3.7416573867739413

正規化

正規化の関数はなさそうなので、自分で定義する。

>>> normalize = lambda a: a/np.linalg.norm(a)
>>> normalize(a)
array([0.26726124, 0.53452248, 0.80178373])

内積

>>> a = np.array([1, 2, 3])
>>> b = np.array([4, 5, 6])
>>> a.dot(b)
32

>>> np.dot(a, b)
32

外積

>>> np.cross(a, b)
array([-3,  6, -3])

トレース

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

>>> np.trace(a)
14

対角成分

>>> np.diag(a)
array([1, 5, 8])

行列式

>>> np.linalg.det(a)
8.999999999999998

逆行列

>>> np.linalg.inv(a)
array([[-1.55555556,  1.22222222, -0.33333333],
       [ 1.11111111, -1.44444444,  0.66666667],
       [ 0.11111111,  0.55555556, -0.33333333]])

固有値

>>> a = np.array([1, 2, 3, 4]).reshape(2, 2)
>>> a
array([[1, 2],
       [3, 4]])

>>> f = np.linalg.eig(a)
>>> f
(array([-0.37228132,  5.37228132]), array([[-0.82456484, -0.41597356],
       [ 0.56576746, -0.90937671]]))

返り値は固有値、固有ベクトルのペアである。固有値だけ得る関数もある。

>>> np.linalg.eigvals(a)
array([-0.37228132,  5.37228132])

分解

LU 分解

LU 分解は NumPy にないので、代わりに SciPy を使う。

>>> from scipy.linalg import lu
>>> a = np.array([1, 2, 3, 4, 5, 6, 7, 9, 8]).reshape(3, 3)  
>>> a
array([[1, 2, 3],
       [4, 5, 6],
       [7, 9, 8]])

>>> P, L, U = lu(a)
>>> P
array([[0., 1., 0.],
       [0., 0., 1.],
       [1., 0., 0.]])

>>> L
array([[ 1.        ,  0.        ,  0.        ],
       [ 0.14285714,  1.        ,  0.        ],
       [ 0.57142857, -0.2       ,  1.        ]])

>>> U
array([[7.        , 9.        , 8.        ],
       [0.        , 0.71428571, 1.85714286],
       [0.        , 0.        , 1.8       ]])

>>> P @ L @ U
array([[1., 2., 3.],
       [4., 5., 6.],
       [7., 9., 8.]])

Cholesky 分解

>>> a = np.array([4, 12, -16, 12, 37, -43, -16, -43, 98]).reshape(3, 3)
>>> a
array([[  4,  12, -16],
       [ 12,  37, -43],
       [-16, -43,  98]])

>>> L = np.linalg.cholesky(a)
>>> L
array([[ 2.,  0.,  0.],
       [ 6.,  1.,  0.],
       [-8.,  5.,  3.]])

>>> L @ L.T
array([[  4.,  12., -16.],
       [ 12.,  37., -43.],
       [-16., -43.,  98.]])

QR 分解

>>> a = np.array([12, -51, 4, 6, 167, -68, -4, 24, -41]).reshape(3, 3)
>>> a
array([[ 12, -51,   4],
       [  6, 167, -68],
       [ -4,  24, -41]])

>>> Q, R = np.linalg.qr(a)
>>> Q
array([[-0.85714286,  0.39428571,  0.33142857],
       [-0.42857143, -0.90285714, -0.03428571],
       [ 0.28571429, -0.17142857,  0.94285714]])

>>> R
array([[ -14.,  -21.,   14.],
       [   0., -175.,   70.],
       [   0.,    0.,  -35.]])

>>> Q @ R
array([[ 12., -51.,   4.],
       [  6., 167., -68.],
       [ -4.,  24., -41.]])

特異値分解

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

>>> U, S, Vh = np.linalg.svd(a)
>>> U
array([[ 0.,  1.,  0.,  0.],
       [ 1.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  1.],
       [ 0.,  0., -1.,  0.]])

>>> S
array([3.        , 2.23606798, 2.        , 0.        ])

>>> Vh
array([[-0.        ,  0.        ,  1.        , -0.        ,  0.        ],
       [ 0.4472136 ,  0.        ,  0.        ,  0.        ,  0.89442719],
       [ 0.        , -1.        ,  0.        ,  0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        ,  1.        ,  0.        ],
       [-0.89442719,  0.        ,  0.        ,  0.        ,  0.4472136 ]])

>>> U @ np.diag(S) @ Vh[:4, :]
array([[1., 0., 0., 0., 2.],
       [0., 0., 3., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 2., 0., 0., 0.]])

連立方程式を解く

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

>>> b = np.array([1, 2, 3]).reshape(3, 1)
>>> b
array([[1],
       [2],
       [3]])

>>> x = np.linalg.solve(a, b)
>>> x
array([[-0.11111111],
       [ 0.22222222],
       [ 0.22222222]])

>>> a @ x
array([[1.],
       [2.],
       [3.]])

データフレーム

データフレームは、データ分析のための便利なデータ構造である。データフレームは、pandas パッケージによって提供されている。パッケージがインストールされていれば、以下のようにインポートする。

>>> import pandas as pd

pandas はよく pd として使われる。

データフレームを手で作る場合は、次のようになる。

>>> df = pd.DataFrame({"name": ["omochi", "annko", "tamago"], "type": ["sabatora", "chatora", "mike"], "age": [3, 4, 5]})
>>> df
     name      type  age
0  omochi  sabatora    3
1   annko   chatora    4
2  tamago      mike    5

空の状態から作っていく方法もある。

>>> df = pd.DataFrame()
>>> df["name"] = ["omochi", "annko", "tamago"]
>>> df["type"] = ["sabatora", "chatora", "mike"]
>>> df["age"] = [3, 4, 5]
>>> df
     name      type  age
0  omochi  sabatora    3
1   annko   chatora    4
2  tamago      mike    5

データの個数

データの個数は len() で得られる。

>>> len(df)
3

要素のアクセス

要素のアクセスは iloc でできる。

>>> df.iloc[0, 1]
'sabatora'

>>> df.iloc[:, 1]
0    sabatora
1     chatora
2        mike
Name: type, dtype: object

ピンポイントの値がほしければ、iat も使える。

>>> df.iat[0, 1]
'sabatora'

values を使う方法もある。

>>> df.values[0, 1]
'sabatora'

>>> df.values[:, 1]
array(['sabatora', 'chatora', 'mike'], dtype=object)

結果を見るとわかるように、この場合は NumPy の配列で返ってくる。

直接スライスを使うと、選択した行が得られる。

>>> df[:2]
     name      type  age
0  omochi  sabatora    3
1   annko   chatora    4

名前による参照

名前による参照も行える。

>>> df.name
0    omochi
1     annko
2    tamago
Name: name, dtype: object

>>> df["name"]
0    omochi
1     annko
2    tamago
Name: name, dtype: object

>>> df[["name", "type"]]
     name      type
0  omochi  sabatora
1   annko   chatora
2  tamago      mike

部分がほしければ次のようにもできる。

>>> df[["name", "type"]][:2]
     name      type
0  omochi  sabatora
1   annko   chatora

loc() を使う方法もある。

>>> df.loc[:1, ["name", "type"]]
     name      type
0  omochi  sabatora
1   annko   chatora

スライスの数値が異なることに注意。

ピンポイントでデータがほしければ、at() が使える。

>>> df.at[1, "name"]
'annko'

列の名前を変更する

>>> df.columns
Index(['name', 'type', 'age'], dtype='object')

columns の値を変えれば、列の名前を変更できる。

>>> df.columns = ["名前", "タイプ", "年齢"]
>>> df
       名前       タイプ  年齢
0  omochi  sabatora   3
1   annko   chatora   4
2  tamago      mike   5

カテゴリカルデータにする

ある種類の中でしか値を取らないカテゴリカルデータというものがある。type をカテゴリカルデータに変換するには、次のようにする。

>>> df["type"] = df["type"].astype("category")
>>> df
     name      type  age
0  omochi  sabatora    3
1   annko   chatora    4
2  tamago      mike    5

>>> df["type"]
0    sabatora
1     chatora
2        mike
Name: type, dtype: category
Categories (3, object): ['chatora', 'mike', 'sabatora']

type に変な値を入れようとすると怒られる。

>>> df["type"][1] = "white"
...
TypeError: Cannot setitem on a Categorical with a new category (white), set the categories first

あとからカテゴリーを追加する場合は、次のようにする。

>>> df["type"] = df["type"].astype("category")
>>> df["type"] = df["type"].cat.add_categories("white")
>>> df["type"][1] = "white"
>>> df
     name      type  age
0  omochi  sabatora    3
1   annko     white    4
2  tamago      mike    5

>>> df.type
0    sabatora
1       white
2        mike
Name: type, dtype: category
Categories (4, object): ['chatora', 'mike', 'sabatora', 'white']

データの追加

行データを追加したければ、最後の行の次に追加すればよい。

>>> df.loc[3, :] = ["momo", "mike", 3]
>>> df
     name      type  age
0  omochi  sabatora  3.0
1   annko   chatora  4.0
2  tamago      mike  5.0
3    momo      mike  3.0

添字を飛ばすと何が起こるかというと、そのまま入るだけである。

>>> df.loc[5, :] = ["kinako", "chatora", 4]
>>> df
     name      type  age
0  omochi  sabatora  3.0
1   annko   chatora  4.0
2  tamago      mike  5.0
3    momo      mike  3.0
5  kinako   chatora  4.0

データは、名前を指定して入れることができる。

>>> df = pd.DataFrame()
>>> df["name"] = ["omochi", "annko", "tamago"]
>>> df["type"] = ["sabatora", "chatora", "mike"]
>>> df["age"] = [3, 4, 5]
>>> df
     name      type  age
0  omochi  sabatora    3
1   annko   chatora    4
2  tamago      mike    5

データの削除

drop() でデータを取り除くことができる。

>>> df.drop(1)
     name      type  age
0  omochi  sabatora    3
2  tamago      mike    5

>>> df.drop("type", axis=1)
     name  age
0  omochi    3
1   annko    4
2  tamago    5

結合

データフレームを結合するには、pd.concat() を用いる。

>>> df = pd.DataFrame()
>>> df["height"] = [147.9, 163.5, 159.8, 155.1, 163.3]
>>> df["weight"] = [41.7, 60.2, 47.0, 53.2, 48.3]

>>> df2 = pd.DataFrame()
>>> df["height"] = [158.7, 172.0, 161.2, 153.9, 161.6]
>>> df["weight"] = [59.7, 58.5, 49.0, 46.7, 44.5]

>>> df3 = pd.concat([df, df2], ignore_index=True)
>>> df3
   height  weight
0   147.9    41.7
1   163.5    60.2
2   159.8    47.0
3   155.1    53.2
4   163.3    48.3
5   158.7    59.7
6   172.0    58.5
7   161.2    49.0
8   153.9    46.7
9   161.6    44.5

"ignore_index=True" を指定しないと、もとのインデックスが残る。

>>> df3 = pd.concat([df, df2])
>>> df3
   height  weight
0   147.9    41.7
1   163.5    60.2
2   159.8    47.0
3   155.1    53.2
4   163.3    48.3
0   158.7    59.7
1   172.0    58.5
2   161.2    49.0
3   153.9    46.7
4   161.6    44.5

最初と最後を取り出す

データフレームの最初と最後を取り出すには、head()、tail() を使う。

>>> df = pd.DataFrame()
>>> df["height"] = [147.9, 163.5, 159.8, 155.1, 163.3, 158.7, 172.0, 161.2, 153.9, 161.6]
>>> df["weight"] = [41.7, 60.2, 47.0, 53.2, 48.3, 59.7, 58.5, 49.0, 46.7, 44.5] 

>>> df.head(3)
   height  weight
0   147.9    41.7
1   163.5    60.2
2   159.8    47.0

>>> df.tail(3)
   height  weight
7   161.2    49.0
8   153.9    46.7
9   161.6    44.5

値が含まれるかどうか

values を使えば、in で値が含まれるか確認できる。

>>> df = pd.DataFrame()  
>>> df["name"] = ["omochi", "annko", "tamago"]  
>>> df["type"] = ["sabatora", "chatora", "mike"] 
>>> df["age"] = [3, 4, 5] 
>>> df
     name      type  age
0  omochi  sabatora    3
1   annko   chatora    4
2  tamago      mike    5

>>> "annko" in df["name"].values
True

isin() というものもある。

>>> df["name"].isin(["annko"])
0    False
1     True
2    False
Name: name, dtype: bool

並び替え

値で並び替えるには、sort_values() を用いる。

>>> df = pd.DataFrame()
>>> df["height"] = [147.9, 163.5, 159.8, 155.1, 163.3, 158.7, 172.0, 161.2, 153.9, 161.6]
>>> df["weight"] = [41.7, 60.2, 47.0, 53.2, 48.3, 59.7, 58.5, 49.0, 46.7, 44.5] 

>>> df.sort_values(by=["height"])
   height  weight
0   147.9    41.7
8   153.9    46.7
3   155.1    53.2
5   158.7    59.7
2   159.8    47.0
7   161.2    49.0
9   161.6    44.5
4   163.3    48.3
1   163.5    60.2
6   172.0    58.5

>>> df.sort_values(by=["height"], ascending=False)
   height  weight
6   172.0    58.5
1   163.5    60.2
4   163.3    48.3
9   161.6    44.5
7   161.2    49.0
2   159.8    47.0
5   158.7    59.7
3   155.1    53.2
8   153.9    46.7
0   147.9    41.7

重複データの削除

重複したデータを削除するには drop_duplicates() を用いる。

>>> df2 = pd.concat([df, df.head(3)], ignore_index=True)
>>> df2
    height  weight
0    147.9    41.7
1    163.5    60.2
2    159.8    47.0
3    155.1    53.2
4    163.3    48.3
5    158.7    59.7
6    172.0    58.5
7    161.2    49.0
8    153.9    46.7
9    161.6    44.5
10   147.9    41.7
11   163.5    60.2
12   159.8    47.0

>>> df2.drop_duplicates()
   height  weight
0   147.9    41.7
1   163.5    60.2
2   159.8    47.0
3   155.1    53.2
4   163.3    48.3
5   158.7    59.7
6   172.0    58.5
7   161.2    49.0
8   153.9    46.7
9   161.6    44.5

デフォルトでは重複データで最初に出てきたものを採用する。最後に出てきたものを採用するなら、次のようにする。

>>> df2.drop_duplicates(keep="last")
    height  weight
3    155.1    53.2
4    163.3    48.3
5    158.7    59.7
6    172.0    58.5
7    161.2    49.0
8    153.9    46.7
9    161.6    44.5
10   147.9    41.7
11   163.5    60.2
12   159.8    47.0

変わる部分は、インデックスである。インデックスを振り直すなら、reset_index() を用いる。

>>> df2.drop_duplicates(keep="last").reset_index(drop=True)
   height  weight
0   155.1    53.2
1   163.3    48.3
2   158.7    59.7
3   172.0    58.5
4   161.2    49.0
5   153.9    46.7
6   161.6    44.5
7   147.9    41.7
8   163.5    60.2
9   159.8    47.0

"drop=True" を指定しない場合、もとのインデックスが残る。

最大値と最小値

要素の最大値、最小値を得るには、それぞれ max()、min() を用いる。

>>> df["height"].max()
172.0

>>> df["height"].min()
147.9

総和と平均

要素の総和や平均値を得るには、それぞれ sum()、mean() を用いる。

>>> df["height"].sum()
1597.0

>>> df["height"].mean()
159.7

要素への関数の適用

列ごとの平均をとるには次のようにできる。

>>> df.apply(lambda x: x.mean())
height    159.70
weight     50.88
dtype: float64

行ごとに計算したい場合は、次のようにできる。

>>> df["BMI"] = df.apply(lambda x: round(x["weight"]/(x["height"]/100.)**2, 1), axis=1)
>>> df
   height  weight   BMI
0   147.9    41.7  19.1
1   163.5    60.2  22.5
2   159.8    47.0  18.4
3   155.1    53.2  22.1
4   163.3    48.3  18.1
5   158.7    59.7  23.7
6   172.0    58.5  19.8
7   161.2    49.0  18.9
8   153.9    46.7  19.7
9   161.6    44.5  17.0

要素ごとに処理を行いたい場合は、次のようにする。

>>> df["height"].map(lambda x: x if x > 150 else 150)
0    150.0
1    163.5
2    159.8
3    155.1
4    163.3
5    158.7
6    172.0
7    161.2
8    153.9
9    161.6
Name: height, dtype: float64

データフレーム全体について要素ごとに処理したい場合は、df.map() を用いる。

>>> df.map(lambda x: round(x))
   height  weight  BMI
0     148      42   19
1     164      60   22
2     160      47   18
3     155      53   22
4     163      48   18
5     159      60   24
6     172      58   20
7     161      49   19
8     154      47   20
9     162      44   17

抽出

「身長が 160 cm 以上」といった条件に合うデータを得るには、次のようにする。

>>> df[df["height"] >= 160]
   height  weight
1   163.5    60.2
4   163.3    48.3
6   172.0    58.5
7   161.2    49.0
9   161.6    44.5

条件判定

どれかの要素で条件が成り立つかを調べるには any() が使える。また、すべての要素で条件が成り立つかどうかは all() でわかる。

>>> any(df["height"].map(lambda x: x > 160))
True

>>> any(df["height"].map(lambda x: x < 150))
True

>>> all(df["height"].map(lambda x: x > 160))
False

要素の置き換え

条件に合った要素を別の値で置き換えるのは map() でできる。

>>> df["height"].map(lambda x: 160 if x < 160 else x)
0    160.0
1    163.5
2    160.0
3    160.0
4    163.3
5    160.0
6    172.0
7    161.2
8    160.0
9    161.6
Name: height, dtype: float64

ファイルの書き出しと読み込み

データフレームを CSV ファイルとして書き出すには、次のようにする。

>>> df = pd.DataFrame({"name": ["omochi", "annko", "tamago"], "type": ["sabatora", "chatora", "mike"], "age": [3, 4, 5]})
>>> df.to_csv("cats.csv", index=False)

cats.csv

name,type,age
omochi,sabatora,3
annko,chatora,4
tamago,mike,5

もし "index=False" がなかったら、以下のような結果になる。

,name,type,age
0,omochi,sabatora,3
1,annko,chatora,4
2,tamago,mike,5

書き出した CSV ファイルを読み込むには、次のようにする。

>>> df2 = pd.read_csv("cats.csv")
>>> df2
     name      type  age
0  omochi  sabatora    3
1   annko   chatora    4
2  tamago      mike    5