NumPy のユニバーサル関数 (ufunc) は配列の個々の要素に対する計算の繰り返しを効率的に行うことができる関数です.Python のリスト形式よりも高速に実行されます.
まずは,NumPy 配列の平方根を np.sqrt()
で求めてみよう.
import numpy as np
x = np.array([1, 2, 3, 4, 5])
print(np.sqrt(x))
[1. 1.41421356 1.73205081 2. 2.236067
ユニバーサル関数が高速に実行できることを確認してみよう.まず,1千万までの平方根であってもすぐに実行できるはずです.
x = np.arange(10000000)
y = np.sqrt(x)
y[4084441]
2021.0
マジックコマンドを使って実行時間を計測してみよう.Juypter Notebook ではセルの先頭に %%time
を入力すると,そのセルの実行時間を計測できます.その他にもある一行の時間を計測する %time
や,同じ処理を何度か繰り返して時間を計測する %timeit
,%%timeit
などがあります.
%%time
x = np.arange(10000000)
y = np.sqrt(x)
y[4084441]
CPU times: user 20.9 ms, sys: 14.1 ms, total: 34.9 ms Wall time: 36.3 ms
同じ処理を Python のリストで記述して実行してみよう.math ライブラリの math.sqrt()
を使う場合は,繰り返しの処理が必要になります.C言語などでは次のような書き方は標準的ですが,Python リストでは(NumPyと比較して)処理に時間を要するはずです.
import math
%%time
n = 10000000
y = [0] * n
for i in range(0, n):
y[i] = math.sqrt(i)
print(y[4084441])
2021.0 CPU times: user 1.02 s, sys: 58 ms, total: 1.08 s Wall time: 1.08 s
さらに同じ処理を Python の内包表記で記述して実行してみよう.リストの繰り返しに比べて高速に処理できますが,NumPy には遠く及びません.
%%time
n = 10000000
results = [math.sqrt(i) for i in range(0, n)] # 内包表記
print(results[4084441])
2021.0 CPU times: user 639 ms, sys: 39.8 ms, total: 679 ms Wall time: 683 ms
絶対値は np.absolute()
です.
x = np.array([1, -2, 3, -4, 5])
print(np.absolute(x))
[1 2 3 4 5]
np.absolute()
の別名として np.abs()
も利用可能です.
print(np.abs(x))
[1 2 3 4 5]
指数関数は np.exp()
があるが,これ以外にも \(2^x\) を計算する np.exp2()
や,\(x^y\) を計算する np.power(x, y)
などもあります.
x = np.array([1, 2, 3, 4, 5])
print(np.exp(x))
print(np.exp2(x))
print(np.power(3, x))
[ 2.71828183 7.3890561 20.08553692 54.59815003 148.4131591 ] [ 2. 4. 8. 16. 32.] [ 3 9 27 81 243]
自然対数は np.log()
,底が2と10の対数はそれぞれnp.log2()
,とnp.log10()
です.
x = np.array([1, 2, 4, 10, 100])
print(np.log(x))
print(np.log2(x))
print(np.log10(x))
[0. 0.69314718 1.38629436 2.30258509 4.60517019] [0. 1. 2. 3.32192809 6.64385619] [0. 0.30103 0.60205999 1. 2. ]
三角関数は np.sin()
,np.cos()
,np.tan()
などが利用できます.
# 0°, 30°, 45°, 60°, 90°
x = np.array([0, np.pi/3, np.pi/2, 2*np.pi/3, np.pi])
print(x)
print(np.sin(x))
print(np.cos(x))
print(np.tan(x))
[0. 1.04719755 1.57079633 2.0943951 3.14159265] [0.00000000e+00 8.66025404e-01 1.00000000e+00 8.66025404e-01 1.22464680e-16] [ 1.000000e+00 5.000000e-01 6.123234e-17 -5.000000e-01 -1.000000e+00] [ 0.00000000e+00 1.73205081e+00 1.63312394e+16 -1.73205081e+00 -1.22464680e-16]
最大公約数 (Greatest Common Divisor) や最小公倍数 (Lowest Common Multiple) 返す関数 np.gcd()
や np.lcm()
もあります.
198と252の最大公約数
np.gcd(198, 252)
18
198と252の最小公倍数
np.lcm(198, 252)
2772
合計はデータの値 \(x_i\) をすべて足し合わせたもので,これは np.sum()
で簡単に求めることができます.
x = np.array([1, 2, 3, 4, 5])
np.sum(x)
15
これは数学的には次のように書くことができます. \begin{eqnarray} \sum_{i=1}^{n}x_i = x_1 + x_2 + \cdots + x_n \end{eqnarray}
やはりここでも NumPy が高速に実行できることを試しておこう.1千万回の繰り返し処理は次のように効率的に実行できるはずです.
%%time
x = np.arange(1, 10000000)
print(np.sum(x))
49999995000000 CPU times: user 15.8 ms, sys: 8.14 ms, total: 24 ms Wall time: 23.3 ms
同じ計算を Python のリストで記述すると次のようになりますが,実行に少々時間を要するはずで,NumPy が数十倍高速です.
%%time
s = 0
for x in range(0, 10000000):
s += x
print(s)
49999995000000 CPU times: user 653 ms, sys: 9.73 ms, total: 663 ms Wall time: 664 ms
行列に対して合計を求めることもできるが,その動作には注意が必要かもしれません.オプショナル引数を指定しなければ,すべての要素の合計を取得します.
X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(X)
print(np.sum(X))
[[1 2 3] [4 5 6] [7 8 9]] 45
オプショナル引数で axis=0
を指定すると,縦方向の合計(列ごとの合計)を取得できます.
print(np.sum(X, axis=0))
[12 15 18]
逆に axis=1
を指定すると,横方向の合計(行ごとの合計)を取得できます.
print(np.sum(X, axis=1))
[ 6 15 24]
合計と同様に np.prod()
で積を求めることも可能です.例えば全ての要素の積を求めます.
x = np.array([1, 2, 3, 4, 5])
np.prod(x)
120
これは数学的には次のように書くことができます. \begin{eqnarray} \prod_{i=1}^{n}x_i = x_1 \times x_2 \times \cdots \times x_n \end{eqnarray}
Numpy には累積和を求める関数 np.cumsum()
や 累積積を求める np.cumprod()
もあります.どちらもそれぞれの要素までの累積和や累積積を求めていることに注意してください.
x = np.array([1, 2, 3, 4, 5])
print(np.cumsum(x))
[ 1 3 6 10 15]
x = np.array([1, 2, 3, 4, 5])
print(np.cumprod(x))
[ 1 2 6 24 120]
平均は np.mean()
や np.average()
です.
x = np.array([1, 2, 3, 4, 5])
np.mean(x)
3.0
x = np.array([1, 2, 3, 4, 5])
np.average(x)
3.0
もちろん,平均の数学的な定義は次のとおりです. \begin{eqnarray} \bar{x} = \frac{1}{n}\sum_{i=1}^{n}x_i \end{eqnarray}
合計のときと同様に,行列に対しても平均を求めることもできます.
X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
np.mean(X)
5.0
X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
np.average(X)
5.0
オプショナル引数で axis=0
を指定すると,縦方向の平均(列ごとの平均)を取得できます.
np.mean(X, axis=0)
array([4., 5., 6.])
np.average(X, axis=0)
array([4., 5., 6.])
オプショナル引数で axis=1
を指定すると,横方向の平均(行ごとの平均)を取得できます.
np.mean(X, axis=1)
array([2., 5., 8.])
np.average(X, axis=1)
array([2., 5., 8.])
なお, np.mean()
と np.average()
の違いのひとつは,np.average()
が重み付け平均を求められることです.
x = np.array([1, 2, 3, 4, 5])
w = np.array([0.1, 0.4, 0.3, 0.1, 0.1])
np.average(x, weights = w)
2.6999999999999997
要素の最大や最小を求める関数もあります.
x = np.array([2, 3, 1, 5, 4])
print(np.max(x))
print(np.min(x))
5 1
さらに,最大値や最小値のインデックスを返す関数もあります.
x = np.array([2, 3, 1, 5, 4])
print(np.argmax(x))
print(np.argmin(x))
3 2
ただし,最大値や最小値が複数あっても見つかった先頭のインデックスだけが返ってくることに注意して下さい.
x = np.array([2, 3, 1, 5, 4, 5, 1])
print(np.argmax(x))
print(np.argmin(x))
3 2
なお,データ \begin{eqnarray} x_1,~x_2,\cdots,~x_n \end{eqnarray} を小さい順に並び替えて \begin{eqnarray} x_{(1)} \leq x_{(2)} \leq \cdots \leq x_{(n)} \end{eqnarray} のように書くとき,\(x_{(i)}\) を \(i\) 番目の順序統計量といいます.つまり,最小値は \(x_{(1)}\) であり,最大値は \(x_{(n)}\) です.さらに \(x_{(1)} \leq x_{(2)} \leq \cdots \leq x_{(n)}\) の中央をメディアン(中央値)といいます.データ数 \(n\) が奇数のときは次のようになります.
x = np.array([2, 3, 1, 5, 4])
np.median(x)
3.0
一方でデータ数 \(n\) が偶数のときは,中央の2つの値の平均になります.
x = np.array([2, 3, 1, 5, 4, 1])
np.median(x)
2.5
分散は np.var()
を使って求めることができます.このとき,特に引数を指定しなければ,標本分散を求めることに注意してください.つまり,データ数を \( n \) とすると,\( n \) で割った結果になります(これは Pandas の std( ) と異なります).メソッド np.var()
には ddof
引数を指定することができます.ddof
は Delta Degrees of Freedom であり,\( n - ddof \) で割ることを意味します.引数省略時は ddof = 0
であり,ddof = 1
を指定すると,不偏分散(\( n - 1\) で割った結果)になります.
x = np.array([1, 2, 3, 4, 5])
print("標本分散 : ", np.var(x))
print("標本分散 : ", np.var(x, ddof=0))
print("不偏分散 : ", np.var(x, ddof=1))
標本分散 : 2.0 標本分散 : 2.0 不偏分散 : 2.5
なお,分散(標本分散)の定義は \begin{eqnarray} S^2 = \frac{1}{n}\sum_{i=1}^{n}\left(x_i - \bar{x}\right)^2 \end{eqnarray} で,不偏分散の定義は \begin{eqnarray} U^2 = \frac{1}{n-1}\sum_{i=1}^{n}\left(x_i - \bar{x}\right)^2 \end{eqnarray} です.
標準偏差は np.std()
で求めることができます.分散のときと同様に ddof
の指定には注意が必要です.
x = np.array([1, 2, 3, 4, 5])
print("標本の標準偏差 : ", np.std(x))
print("標本の標準偏差 : ", np.std(x, ddof=0))
print("母集団の標準偏差 : ", np.std(x, ddof=1))
標本の標準偏差 : 1.4142135623730951 標本の標準偏差 : 1.4142135623730951 母集団の標準偏差 : 1.5811388300841898
2次元の5組のデータを定義して標準偏差を求めるには次のようにすると良いでしょう.
X = np.array([[1,8],[2,22],[3,28],[4,35],[5,36]])
print(X)
print(np.std(X, axis=0))
[[ 1 8] [ 2 22] [ 3 28] [ 4 35] [ 5 36]] [ 1.41421356 10.24499878]
相関係数を求めるには np.corrcoef()
を用いると良いでしょう.このとき,データ行列を T
メソッドや transpose()
メソッドで転置して渡す必要があります.
X = np.array([[1,8],[2,22],[3,28],[4,35],[5,36]])
print(X)
print(np.corrcoef(X.T))
[[ 1 8] [ 2 22] [ 3 28] [ 4 35] [ 5 36]] [[1. 0.95247191] [0.95247191 1. ]]
print(np.corrcoef(np.transpose(X)))
[[1. 0.95247191] [0.95247191 1. ]]
あるいは,データ行列を転置せずに,rowvar=False
によって指定することもできます(デフォルトは rowvar=True
).
print(np.corrcoef(X, rowvar=False))
[[1. 0.95247191] [0.95247191 1. ]]
共分散は np.cov()
で計算できます.次のようにすると,分散共分散行列を一気に計算できます.このとき,分散の np.var()
関数とは異なり,ddof = 1
がデフォルトであることに注意が必要です.
母集団の分散・共分散
X = np.array([[1,8],[2,22],[3,28],[4,35],[5,36]])
print(np.cov(X.T))
[[ 2.5 17.25] [ 17.25 131.2 ]]
母集団の分散・共分散
print(np.cov(X.T, ddof = 1))
[[ 2.5 17.25] [ 17.25 131.2 ]]
標本分散・共分散
print(np.cov(X.T, ddof = 0))
[[ 2. 13.8 ] [ 13.8 104.96]]
欠損値(つまり観測できない値)は np.nan
で表現します.なお,np.nan
による初期化はここを参照してください.
x = np.array([np.nan, np.nan, np.nan, 4, 5])
print(x)
[nan nan nan 4. 5.]
通常の集約関数である np.sum()
や np.mean()
, np.average()
に欠損値を含む NumPy 配列を与えると結果は nan
になります.
print(x)
print(np.sum(x))
print(np.mean(x))
print(np.average(x))
[nan nan nan 4. 5.] nan nan nan
欠損値を含む NumPy 配列の合計を求めるには np.nansum()
を使います.
x = np.array([np.nan, np.nan, np.nan, 4, 5])
print(x)
print(np.nansum(x))
[nan nan nan 4. 5.] 9.0
行列の場合も同様です.
X = np.array([[1, 2, 3], [4, 5, np.nan], [7, 8, np.nan]])
print(X)
print("すべての合計 : ", np.nansum(X))
print("列ごとの合計 : ", np.nansum(X, axis=0))
print("行ごとの合計 : ", np.nansum(X, axis=1))
[[ 1. 2. 3.] [ 4. 5. nan] [ 7. 8. nan]] すべての合計 : 30.0 列ごとの合計 : [12. 15. 3.] 行ごとの合計 : [ 6. 9. 15.]
欠損値を含む NumPy 配列の平均を求めるには np.nanmean()
を使います.
x = np.array([np.nan, np.nan, np.nan, 4, 5])
print(x)
print(np.nanmean(x))
[nan nan nan 4. 5.] 4.5
X = np.array([[1, 2, 3], [4, 5, np.nan], [7, 8, np.nan]])
print(X)
print("すべての平均 : ", np.nanmean(X))
print("列ごとの平均 : ", np.nanmean(X, axis=0))
print("行ごとの平均 : ", np.nanmean(X, axis=1))
[[ 1. 2. 3.] [ 4. 5. nan] [ 7. 8. nan]] すべての平均 : 4.285714285714286 列ごとの平均 : [4. 5. 3.] 行ごとの平均 : [2. 4.5 7.5]
当然ながら欠損値を 0 で表現してしまうと異なる結果になることに注意してください.
x = np.array([0, 0, 0, 4, 5])
print(x)
print(np.nanmean(x))
[0 0 0 4 5] 1.8
X = np.array([[1, 2, 3], [4, 5, 0], [7, 8, 0]])
print(X)
print("すべての平均 : ", np.nanmean(X))
print("列ごとの平均 : ", np.nanmean(X, axis=0))
print("行ごとの平均 : ", np.nanmean(X, axis=1))
[[1 2 3] [4 5 0] [7 8 0]] すべての平均 : 3.3333333333333335 列ごとの平均 : [4. 5. 1.] 行ごとの平均 : [2. 3. 5.]
欠損値であるかどうかは np.isnan()
で判断できます.
x = np.array([np.nan, np.nan, np.nan, 4, 5])
print(x)
print(np.isnan(x))
[nan nan nan 4. 5.] [ True True True False False]
X = np.array([[1, 2, 3], [4, 5, np.nan], [7, 8, np.nan]])
print(X)
print(np.isnan(X))
[[ 1. 2. 3.] [ 4. 5. nan] [ 7. 8. nan]] [[False False False] [False False True] [False False True]]
欠損値の個数をカウントするには np.count_nonzero()
と np.isnan()
を組み合わせればよいでしょう.
x = np.array([np.nan, np.nan, np.nan, 4, 5])
print(x)
print(np.count_nonzero(np.isnan(x)))
[nan nan nan 4. 5.] 3
X = np.array([[1, 2, 3], [4, 5, np.nan], [7, 8, np.nan]])
print(X)
print("欠損値の個数 : ", np.count_nonzero(np.isnan(X)))
print("列ごとの欠損値の個数 : ", np.count_nonzero(np.isnan(X), axis=0))
print("行ごとの欠損値の個数 : ", np.count_nonzero(np.isnan(X), axis=1))
[[ 1. 2. 3.] [ 4. 5. nan] [ 7. 8. nan]] 欠損値の個数 : 2 列ごとの欠損値の個数 : [0 0 2] 行ごとの欠損値の個数 : [0 1 1]
欠損値でない有効なデータ数は次のようにして求めることができます.なお,~
は否定を意味します.
x = np.array([np.nan, np.nan, np.nan, 4, 5])
print(x)
print(np.count_nonzero(~np.isnan(x)))
[nan nan nan 4. 5.] 2
X = np.array([[1, 2, 3], [4, 5, np.nan], [7, 8, np.nan]])
print(X)
print("データ数 : ", np.count_nonzero(~np.isnan(X)))
print("列ごとのデータ数 : ", np.count_nonzero(~np.isnan(X), axis=0))
print("行ごとのデータ数数 : ", np.count_nonzero(~np.isnan(X), axis=1))
[[ 1. 2. 3.] [ 4. 5. nan] [ 7. 8. nan]] データ数 : 7 列ごとのデータ数 : [3 3 1] 行ごとのデータ数数 : [3 2 2]