Numpy個人的tips

numpyもデータ分析や数値計算には欠かせないツールの一つです。機械学習などを実装していると必ず必要とされるライブラリです。個人的な備忘録としてメモを残しておきます。詳細は以下の公式ページを参照してください。

目次

github

  • githubのjupyter notebook形式のファイルはこちら

筆者の環境

筆者の環境とimportの方法は以下の通りです。

!sw_vers
ProductName:	Mac OS X
ProductVersion:	10.14.6
BuildVersion:	18G95
!python -V
Python 3.5.5 :: Anaconda, Inc.
import numpy as np

np.__version__
'1.18.1'

スカラー、ベクトル、行列、テンソル

  • スカラー : 0階のテンソル
  • ベクトル : 1階のテンソル
  • 行列 : 2階のテンソル

情報の取得

ndarray型の情報を以下の様な属性値や組み込み関数を指定することで取得する事が出来ます。

  • len()
    • 最初の要素の次元の長さを取得
  • shape
    • 各次元の大きさ(サイズ)
  • ndim
    • 次元
  • size
    • 全要素数
  • itemsize
    • 要素のメモリ容量
  • nbytes
    • バイト数
  • dtype
  • data
    • メモリアドレス
  • flags
    • メモリ情報

使用例は以下の通りです。

a = np.array([i for i in range(2)])
b = np.array([i for i in range(4)]).reshape(-1,2)
c = np.array([i for i in range(12)]).reshape(-1,2,2)

print('a            : ', a)
print('len(a)       : ', len(a))
print('a.shape      : ', a.shape)
print('a.ndim       : ', a.ndim)
print('a.size       : ', a.size)
print('a.itemsize   : ', a.itemsize)
print('a.nbytes     : ', a.nbytes)
print('a.dtype      : ', a.dtype)
print('a.data       : ', a.data)
print('a.flgas      : \n{}'.format(a.flags))
print()
print('b            : \n{}'.format(b))
print('len(b)       : ', len(b))
print('b.shape      : ', b.shape)
print('b.ndim       : ', b.ndim)
print('b.size       : ', b.size)
print('b.itemsize   : ', b.itemsize)
print('b.nbytes     : ', b.nbytes)
print('b.dtype      : ', b.dtype)
print('b.data       : ', b.data)
print('b.flgas      : \n{}'.format(b.flags))
print()
print('c            : \n{}'.format(c))
print('len(c)       : ', len(c))
print('c.shape      : ', c.shape)
print('c.ndim       : ', c.ndim)
print('c.size       : ', c.size)
print('c.itemsize   : ', c.itemsize)
print('c.nbytes     : ', c.nbytes)
print('c.dtype      : ', c.dtype)
print('c.data       : ', c.data)
print('c.flgas      : \n{}'.format(c.flags))
a            :  [0 1]
len(a)       :  2
a.shape      :  (2,)
a.ndim       :  1
a.size       :  2
a.itemsize   :  8
a.nbytes     :  16
a.dtype      :  int64
a.data       :  <memory at 0x10a6c7d08>
a.flgas      :
  C_CONTIGUOUS : True
  F_CONTIGUOUS : True
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False


b            :
[[0 1]
 [2 3]]
len(b)       :  2
b.shape      :  (2, 2)
b.ndim       :  2
b.size       :  4
b.itemsize   :  8
b.nbytes     :  32
b.dtype      :  int64
b.data       :  <memory at 0x10a6f73a8>
b.flgas      :
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False


c            :
[[[ 0  1]
  [ 2  3]]

 [[ 4  5]
  [ 6  7]]

 [[ 8  9]
  [10 11]]]
len(c)       :  3
c.shape      :  (3, 2, 2)
c.ndim       :  3
c.size       :  12
c.itemsize   :  8
c.nbytes     :  96
c.dtype      :  int64
c.data       :  <memory at 0x109a18138>
c.flgas      :
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False

flagsについて

flagsは様々な情報を返してくれます。ここでは変数のメモリの格納方法について説明します。

リンクの公式ページを見ればわかりますが、配列のメモリへの割り当ての方法は2種類あります。一つは、C_CONTIGUOUSで、も一つが、F_CONTIGUOUSです。 C_というのはC言語方式という意味で、F_というのはFORTRAN形式である事意味しています。C言語方式では

$$ \left( \begin{array}{cc} a & b \\ c & d \end{array} \right) $$

という変数をメモリ以上に

$$ a,c,b,d $$

という順番で格納します。FORTRAN方式では、

$$ a,b,c,d $$

とう順番で格納します。普段はあまり意識することはありませんが、備忘録として記載しておきます。

numpyのデータ型

numpyの実際の数値計算部分はC言語で実装されています。よってデータを定義するときにデータの型を指定することが出来ます。この情報によりメモリ上に確保する量を最適化することが出来ます。大規模な数値計算に慣れなるほど、重要なプロパティになります。

本家のサイトにはたくさんのデータタイプが定義されているますが、実際に使うのはそれほど多くありません。

表記1表記2表記3データ型説明
np.bool-?bool真偽値
np.int8int8i1int88ビット符号付き整数
np.int16int16i2int1616ビット符号付き整数
np.int32int32i4int3232ビット符号付き整数
np.int64int64i8int6464ビット符号付き整数
np.uint8uint8u1uint88ビット符号なし整数
np.uint16uint16u2uint1616ビット符号なし整数
np.uint32uint32u4uint3232ビット符号なし整数
np.uint64uint64u8uint6464ビット符号なし整数
np.float16float16f2float16半精度浮動小数点型
np.float32float32f4float32単精度浮動小数点型
np.float64float64f8float64倍精度浮動小数点型
np.float128float128f16float1284倍精度浮動小数点型

表記1、表記2、表記3は定義の方法としては同じです。

a = np.array([i for i in range(5)  ], dtype=np.int8)
b = np.array([i for i in range(5)  ], dtype='int8')
c = np.array([i for i in range(5)  ], dtype='i1')

print(a.dtype)
print(b.dtype)
print(c.dtype)

d = np.array(True, dtype='?')
e = np.array(True, dtype=np.bool)

print(d.dtype)
print(e.dtype)
int8
int8
int8
bool
bool

axis

numpyは高階のテンソルを利用する事ができ、平均や合計値などの統計情報を計算する際、どの方向に計算するか指定することが出来ます。その方向を指定する際、axisをいうオプションを利用します。

axisの方向

言葉で説明するより実際に計算をさせてみた方が早いと思います。

a = np.arange(10)

print('\n####### ベクトルの場合 #######')
print('\na : ')
print(a)
print('\nnp.mean(a) : ')
print(np.mean(a))
print('\nnp.mean(a, axis=0) : ')
print(np.mean(a, axis=0))

print('\n####### 行列の場合 #######')
a = np.arange(10).reshape(2,5)
print('\na : ')
print(a)
print('\nnp.mean(a) : ')
print(np.mean(a))
print('\nnp.mean(a, axis=0) : ')
print(np.mean(a, axis=0))
print('\nnp.mean(a, axis=1) : ')
print(np.mean(a, axis=1))

print('\n####### 3階のテンソルの場合 #######')
a = np.arange(24).reshape(2,3,4)
print('\na : ')
print(a)
print('\nnp.mean(a) : ')
print(np.mean(a))
print('\nnp.mean(a, axis=0) : ')
print(np.mean(a, axis=0))
print('\nnp.mean(a, axis=1) : ')
print(np.mean(a, axis=1))
print('\nnp.mean(a, axis=2) : ')
print(np.mean(a, axis=2))
####### ベクトルの場合 #######

a :
[0 1 2 3 4 5 6 7 8 9]

np.mean(a) :
4.5

np.mean(a, axis=0) :
4.5

####### 行列の場合 #######

a :
[[0 1 2 3 4]
 [5 6 7 8 9]]

np.mean(a) :
4.5

np.mean(a, axis=0) :
[2.5 3.5 4.5 5.5 6.5]

np.mean(a, axis=1) :
[2. 7.]

####### 3階のテンソルの場合 #######

a :
[[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]

 [[12 13 14 15]
  [16 17 18 19]
  [20 21 22 23]]]

np.mean(a) :
11.5

np.mean(a, axis=0) :
[[ 6.  7.  8.  9.]
 [10. 11. 12. 13.]
 [14. 15. 16. 17.]]

np.mean(a, axis=1) :
[[ 4.  5.  6.  7.]
 [16. 17. 18. 19.]]

np.mean(a, axis=2) :
[[ 1.5  5.5  9.5]
 [13.5 17.5 21.5]]

ブロードキャスト

numpyは行列やベクトルとスカラー量の演算がされたとき、行列やベクトルのすべての要素に対してスカラー量の演算が実行されます。最初慣れないと勘違いしてしまうので、押さえておきましょう。スカラー量である$a$がベクトル$b$の全成分に対して演算されていることがわかります。

a = 10
b = np.array([1, 2])

print('a     : ',a)
print('b     : ',b)
print('a + b : ',a + b)
print('a * b : ',a * b)
print('b / a : ',b / a)
a     :  10
b     :  [1 2]
a + b :  [11 12]
a * b :  [10 20]
b / a :  [0.1 0.2]

スライシング

スライシングはndarray形式で定義された変数から、特定の数値をスライスして取り出すための手法です。とても便利なので、ぜひとも覚えておきたいです。

a = np.arange(12).reshape(-1,3)

print('a : \n{}'.format(a))
print()
print('a.shape : ',a.shape)
print()
print('a[0,1]    : ', a[0,1], '## row=1, col=1の要素')
print()
print('a[2,2]    : ', a[2,2], '## row=2, col=2の要素')
print()
print('a[1]      : ', a[1], '## row=1の要素')
print()
print('a[-1]     : ', a[-1], '## 最後の行の要素')
print()
print('2行目から3行目、1列目から2列目までの要素')
print('a[1:3,0:2]  : \n{}'.format(a[1:3,0:2]))
print()
print('すべての列、1列目から1列おきのすべての要素')
print('a[:,::2]  : \n{}'.format(a[:,::2]))
print()
print('1行目から1行おきのすべての要素')
print('a[::2]    : \n{}'.format(a[::2]))
print()
print('2行目から1行おきのすべての要素')
print('a[1::2]   : \n{}'.format(a[1::2]))
print()
a :
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]

a.shape :  (4, 3)

a[0,1]    :  1 ## row=1, col=1の要素

a[2,2]    :  8 ## row=2, col=2の要素

a[1]      :  [3 4 5] ## row=1の要素

a[-1]     :  [ 9 10 11] ## 最後の行の要素

2行目から3行目、1列目から2列目までの要素
a[1:3,0:2]  :
[[3 4]
 [6 7]]

すべての列、1列目から1列おきのすべての要素
a[:,::2]  :
[[ 0  2]
 [ 3  5]
 [ 6  8]
 [ 9 11]]

1行目から1行おきのすべての要素
a[::2]    :
[[0 1 2]
 [6 7 8]]

2行目から1行おきのすべての要素
a[1::2]   :
[[ 3  4  5]
 [ 9 10 11]]

all, any, where

  • all:要素のすべてがtrueならtrueを返す
  • any:要素の少なくても一つがtrueならtrueを返す
a = np.array([[0,1],[1,1]])

print(a.all())
print(a.any())
False
True

whereで条件を満たす要素のインデックスを返します。

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

print(np.where(a>1)) ## 1より大きい2のインデックスである(0,1)を返す
(array([0]), array([1]))

(0,1)がwhere条件に当てはまるインデックスとなります。

whereの三項演算子

whereを利用すると三項演算子の利用に利用できます。最初の条件が満たされていれば、第二引数を、満たされていなければ、第三引数の要素を取ります。この形のwhereは頻繁に利用します。

a = np.array([2 *i +1 for i in range(6)]).reshape(2,3)
print('a : ', a)
print('6より大きい要素はそのままで、小さければ0とする')
np.where(a>6,a,0)
a :  [[ 1  3  5]
 [ 7  9 11]]
6より大きい要素はそのままで、小さければ0とする





array([[ 0,  0,  0],
       [ 7,  9, 11]])
a = np.array([2 *i +1 for i in range(6)]).reshape(2,3)
b = np.zeros((2,3))

print(a)
print(b)
print('aの要素が3で割り切れれば、該当するbの値を、そうでなければaの値を返す')
np.where(a%3==0, b, a)
[[ 1  3  5]
 [ 7  9 11]]
[[0. 0. 0.]
 [0. 0. 0.]]
aの要素が3で割り切れれば、該当するbの値を、そうでなければaの値を返す





array([[ 1.,  0.,  5.],
       [ 7.,  0., 11.]])

基本定数

自然対数の底

np.e
2.718281828459045

円周率

np.pi
3.141592653589793

基本的な四則演算

np.add(x,y)

要素ごとの足し算です。一般的なベクトルの加法です。

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

np.reciprocal(x)

要素ごとの逆数になります。

b = np.array([4.,3.])
np.reciprocal(b)
array([0.25      , 0.33333333])

この関数について面白い事に気づきました。python3系では、整数型の割り算であっても小数点以下まで計算してくれます。python2系では、整数部分だけ表示されます。しかし、逆数を計算するこの関数で整数型の逆数を計算すると、整数部分しか表示してくれません。データ型を浮動小数型である事を明示するとちゃんと小数点以下まで計算してくれます。

# print(1/8) # => 0.125が返る@python3系
# print(1/8) # => 0が返る@python2系
print(np.reciprocal(8))
print(np.reciprocal(8, dtype='float16'))
print(np.reciprocal(8.))
0
0.125
0.125

np.multiply(x,y)

要素ごとのかけ算です。アダマール積といわれています。ベクトルの内積とは異なります。

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

np.divide(x,y)

要素ごとの割り算の商を求めます。

a = np.array([1.,2.])
b = np.array([4.,3.])
np.divide(b,a)
array([4. , 1.5])

np.mod(x,y)

要素ごとの割り算のあまりを求めます。

a = np.array([3.,2.])
b = np.array([11.,3.])
print(np.mod(b,a))
[2. 1.]

np.divmod(x,y)

要素ごとの割り算の商とあまりを同時に求めます。

a = np.array([3.,2.])
b = np.array([11.,3.])
print(np.divmod(b,a))
(array([3., 1.]), array([2., 1.]))

np.power(x,y)

累乗の計算です。ベクトルを指定するとベクトル同士の指数の計算になります。

$2^3=8$

np.power(2,3)
8

$4^1$ と $3^2$

a = np.array([1.,2.])
b = np.array([4.,3.])
np.power(b,a)
array([4., 9.])

np.subtract(x,y)

要素ごとの引き算です。

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