トップページに戻る

Numpyの基礎(1)導入

本HPでは、Numpyの記事をいくつかに分類しています。

Numpyとは?

Numpyは、標準の演算が遅いというPythonの欠点を補うために開発された高速なベクトル演算をサポートするライブラリです。
Pythonで科学技術計算をする上では必須と言っても良いライブラリです。
(本稿の大部分は、「Pythonによるデータ分析入門(O'REILLY、ピンク本!)」の第4章を参考にして書かれています。
この本は翻訳もわかりやすく、とても読みやすい本なのでおすすめです。)

なぜNumpyが必要?

C言語やFortranのような静的型付け言語(コンパイル時に型を決定する言語)では、配列や変数を宣言する際、型を明示的にコンパイラに伝えます。
このようにコンパイルの段階で型が決まっていると、マシンとしては余計な動作をする必要が無いために効率よくメモリを使用できる上に高速に動作します。
一方、Pythonのような動的型付け言語(実行時に型を決める言語)では、そのような高速な動作を失う代わりに、型を気にせずにリストに入れたり、宣言の手間が省けたりと、柔軟で短いコードが書けるようになります。
しかし、科学技術計算においては動作の遅さは致命的になります。
もしPythonで科学技術計算しようとするならば、高速な動作が必要になるところでは型を指定して高速な計算ができるようにする必要があります。
そこで生まれたのがNumpyです(Numerical Pythonの略)。
Numpyでは、C言語の配列をPythonで扱いやすいオブジェクト(ndarray)として使うことで、高速でありながら柔軟なプログラミングが可能です。
また、CやFortranと同様に大規模配列を扱うことにも長けているため、使いこなせればかなり強力なツールです。
Numpyは非常に高機能であり、全ての機能を1ページで説明することは難しいです。
ここでは、いくつかの記事にわけ、ざっくりと概観していくことにします。

Numpyの特徴とは?

ざっとNumpyの特徴を上げてみると、

  • ベクトル演算をベースにした省メモリで高速な多次元配列(ndarrayオブジェクト)
  • 行列演算(逆行列など)や乱数生成、フーリエ変換を簡単に呼び出せる
  • PythonとC, C++, Fortranとの連携を可能にする低級インターフェイスの提供
  • バイナリやテキストでのファイル入出力を簡単にできる

のようなものがあります。
行列演算などの実装はCやFortranで使われているライブラリを使用しており、同等の速度を発揮します。

ndarrayオブジェクト

Numpyで配列と言うとき、たいていndarray(N-dimensional array)オブジェクトのことを指します。
ndarrayはCの配列に加え、その形状や型、関数などを情報として持っています。
本稿でも、特に断らない限り、配列はndarrayのことを意味することにします。

Numpyを使う際の注意点

Numpyも万能ではなく、得意ではない処理もあります。それは、配列要素ごとに回すforループです。
要素ごとにforループを回すと、Pythonの遅さが如実に出てしまい、Numpyの利点が活かせません。
Numpyが本当に力を発揮するのはベクトル演算であり、それは並列化によって実現されています。
もし、要素ごとの計算をしたい場合は、

  • CやFortranでその部分だけを書き、それをPythonで呼び出す
  • NumbaやCythonといった他のライブラリを使用しコンパイルを行う

と言った方法があります。
まずはなるべくベクトル化を試みて、それが難しい場合は上記のような手段を考えてみると良いでしょう。

実際に使ってみる

インポートする

In [1]:
import numpy as np    #慣習でnpと名付けることが多い
import matplotlib.pyplot as plt # 可視化用ライブラリ

インポートに失敗する場合、numpyがそもそもインストールされていない可能性があります。その場合以下のようなコマンドを実行してください。

  • Anacondaの場合「conda install numpy」
  • pipの場合「pip install numpy」

condaはコンパイル済みのものをインストールするためにインストールエラーが起きにくいです。
pipはインストール時にコンパイルするため、コンパイルエラーが起きる場合があります(特にWindows)。
なるべく、Anacondaで一気に導入すると良いでしょう。

ndarrayの構造

さっそく、一次元配列を作ってみましょう。np.array関数にリストやタプルを渡すことで作ることが出来ます。

In [2]:
x = np.array([1., 0.01, 0.7])
x
Out[2]:
array([1.  , 0.01, 0.7 ])

ndarrayの重要な属性として、shapedtypeがあります。
type()はPythonの組み込み関数で、引数が何のオブジェクトかを教えてくれます(dtypeとは違うものなので注意)。
いくつか例を挙げましょう。

In [3]:
# xが何のオブジェクトか表示する
type(x)
Out[3]:
numpy.ndarray

shapeは、まさに配列の形状で、多次元配列であれば、軸に沿った要素の個数をそれぞれの軸について並べたタプルです。
一次元であれば、(n,)のようなタプルになります。
軸が一つしか含まれない場合、(n)とすると単なるnになってしまうので、(n,)のようにカンマが付きます。

In [4]:
# xの形状
x.shape
Out[4]:
(3,)

dtypeは、配列の型を表すものです(floatやint、complexなど、詳細は別記事参照)。

In [5]:
# xの型(この場合8バイト浮動小数点型)
x.dtype
Out[5]:
dtype('float64')

ndimは、配列の次元を表します。

In [6]:
# xの次元
x.ndim
Out[6]:
1

多次元配列はこのように定義できます。

In [7]:
# 2×3の2次元配列を定義
y = np.array([[1., 0.1, 3.],
              [3., 20., 0.07]])

# 一行でも書いてよいですが、見やすい方法を選ぶといいでしょう。
y = np.array([[1., 0.1, 3.], [3., 20., 0.07]])
y
Out[7]:
array([[ 1.  ,  0.1 ,  3.  ],
       [ 3.  , 20.  ,  0.07]])
In [8]:
# 形状
y.shape
Out[8]:
(2, 3)
In [9]:
# 型は1次元と同様
y.dtype
Out[9]:
dtype('float64')
In [10]:
# 次元(つまりlen(y.shape)と等しい)
y.ndim
Out[10]:
2

整数を入れると、intとして配列を作ります。

In [11]:
# 整数を入れた場合
x = np.array([1, 2, 3])
x
Out[11]:
array([1, 2, 3])
In [12]:
# 自動的に4バイトintと型推論している
x.dtype
Out[12]:
dtype('int32')

もちろん複素数も扱えます。

In [13]:
# 複素数を入れた場合
x = np.array([1.j, 3 + 6j, 9])
x
Out[13]:
array([0.+1.j, 3.+6.j, 9.+0.j])
In [14]:
# 実部、虚部それぞれ8バイトの16バイト複素数
x.dtype
Out[14]:
dtype('complex128')

要素へのアクセス

また、各要素にアクセスするためには、リストやタプルのようにインデックスを指定します。
多次元配列でのアクセスは、リストなどと違う記法なので注意してください。

In [15]:
x = np.array([1., 2., 3.,])
x[0]
Out[15]:
1.0
In [16]:
# タプルと同じようにスライスを使える
x[1:]
Out[16]:
array([2., 3.])
In [17]:
# 多次元配列の場合
x = np.arange(20).reshape(4,5)
x
Out[17]:
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19]])
In [18]:
# 行0, 列1の値
x[0, 1]
Out[18]:
1
In [19]:
# 行3
x[3]
Out[19]:
array([15, 16, 17, 18, 19])
In [20]:
# 列4
x[:, 4]
Out[20]:
array([ 4,  9, 14, 19])
In [21]:
# 列3, 4を抜き出す
x[:, 3:]
Out[21]:
array([[ 3,  4],
       [ 8,  9],
       [13, 14],
       [18, 19]])

簡単な演算

スカラーとの四則演算は普通のベクトルのような書き方ができます(Fortranと一緒)。
このような演算はリストやタプルでは出来ないので、そのためにndarrayに変換することもよくあります。

In [22]:
x = np.array([1., 0.01, 0.7])
x + 1 # ==> [ 2.  ,  1.01,  1.7 ]
2 * x # ==> [ 2.  ,  0.02,  1.4 ]
x / 1e5 # ==> [  1.00000000e-05,   1.00000000e-07,   7.00000000e-06]
Out[22]:
array([1.e-05, 1.e-07, 7.e-06])

同じ形状の配列同士の四則演算は、要素ごとに演算した結果になります。
(異なる形状の配列であっても、場合によっては演算できることがあり、それをブロードキャスト(別記事)と言います)

In [23]:
x = np.array([8., 0.3, 90])
y = np.array([4., 0.2, 150])
x + y # ==> [  12. ,    0.5,  240. ]
x - y # ==> [  4. ,   0.1, -60. ]
x * y # ==> [  3.20000000e+01,   6.00000000e-02,   1.35000000e+04]
x / y # ==> [ 2. ,  1.5,  0.6]
Out[23]:
array([2. , 1.5, 0.6])

多次元配列でも同じように要素ごとの演算になります。
行列積を計算したい場合は、np.dot関数を用いるか、Python3.5以降であれば@マークを使えます。

In [24]:
# 3×3行列を作成
A = np.arange(9).reshape(3,3)
A
Out[24]:
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])
In [25]:
# 3×3の単位行列の2倍
B = np.eye(3) * 2
B
Out[25]:
array([[2., 0., 0.],
       [0., 2., 0.],
       [0., 0., 2.]])
In [26]:
# 要素ごとの積になる(行列積ではない!)
A * B
Out[26]:
array([[ 0.,  0.,  0.],
       [ 0.,  8.,  0.],
       [ 0.,  0., 16.]])
In [27]:
# 行列積
A.dot(B)
Out[27]:
array([[ 0.,  2.,  4.],
       [ 6.,  8., 10.],
       [12., 14., 16.]])
In [28]:
# Python3.5以降
A @ B
Out[28]:
array([[ 0.,  2.,  4.],
       [ 6.,  8., 10.],
       [12., 14., 16.]])

誤字やおかしい点などがあったら @zawawahoge (Twitter) にお気軽にご連絡ください。

トップページに戻る