Pythonの数値計算ライブラリ NumPy入門

  • 146
    Like
  • 1
    Comment
More than 1 year has passed since last update.

この投稿は自分のブログ記事をQiita用に整形したものです。追記事項があればブログの方に書いていきます。
「Pythonの数値計算ライブラリ NumPy入門」 http://rest-term.com/archives/2999/

Pythonの数値計算ライブラリ NumPy入門

numpy-sm.png Scientific Computing Tools For Python — Numpy

NumPy は Pythonプログラミング言語の拡張モジュールであり、大規模な多次元配列や行列のサポート、これらを操作するための大規模な高水準の数学関数ライブラリを提供する。(via Wikipedia)

これまで知識があいまいだったNumPyについて、もう一度おさらいしたいと思います。NumPyはSciPyと併せて科学技術計算でよく利用されています。また、高速に行列演算ができるのでOpenCV(コンピュータビジョンライブラリ)でもNumPyを利用したPythonインタフェースが提供されるようになりました。

OpenCVのPythonバインディングについては2011年にブログでも取り上げていますので参考までに。
* さくらVPSにOpenCVをインストールしてPythonから使う

環境

CentOS 6.4 (x86_64)
Python 2.7.5 (ELF 64-bit LSB executable)
NumPy 1.7.1

  • NumPy配列(numpy.ndarray)とは
  • numpy.ndarrayの属性(attributes)
  • データ型について
  • 配列の生成
  • 配列形状の変更
  • Indexing
  • Fancy Indexing
  • インデックスの検索
  • 配列に対する操作/演算
  • 配列の結合/分割、軸操作
  • 配列のソート
  • 配列要素に対する演算
  • Broadcasting
  • 配列の走査
  • 統計関数
  • ファイル入出力

NumPy配列(numpy.ndarray)とは

An ndarray is a (usually fixed-size) multidimensional container of items of the same type and size.

numpy.ndarray は多次元配列を扱うクラスです。基本的に以下の制限があります。

  • 配列内要素の型は全て同じ
  • 配列長は固定 (固定長配列)
  • 配列の各次元の要素数は同じ

(※ ただし、制限付きで配列の形状変更は可能。説明は後述)
C言語の配列とよく似ています。制限がある分、Pythonのリスト(list)型と比較して大規模な配列を扱う際の処理効率が良くなっています。また、これ以降 "配列" と表記したときは基本的に numpy.ndarray を指すこととします。

import
## import時に np というエイリアスを付けるのが慣例のようです。
import numpy as np

numpy.ndarrayの属性(attributes)

numpy.ndarray の主な属性については以下の通り。効率良く処理するためにndarrayのデータはメモリの連続領域上に保持されていますが()、これらの属性を参照するとデータがメモリ上にどうレイアウトされているかを調べることができます。

An instance of class ndarray consists of a contiguous one-dimensional segment of computer memory

ndarray.flags 配列データのメモリレイアウト情報 (numpy.flagsobj)
ndarray.ndim 配列の次元数
ndarray.size 配列の要素数
ndarray.shape 各次元の要素数
ndarray.itemsize 1要素のバイト数
ndarray.strides 各次元で次の要素に移動する際に必要なバイト数
ndarray.nbytes 配列全体のバイト数
ndarray.dtype 配列要素のデータ型 (numpy.dtype)
numpy.ndarray
## 二次元配列 (行列)
>>> a = np.array([[1,2,3],[4,5,6],[7,8,9],[10,11,12]])
>>> a
array([[ 1,  2,  3],
       [ 4,  5,  6],
       [ 7,  8,  9],
       [10, 11, 12]])
>>> a.flags
  C_CONTIGUOUS : True   ## データがメモリ上に連続しているか(C配列型)
  F_CONTIGUOUS : False  ## 同上(Fortran配列型)
  OWNDATA : True        ## 自分のデータかどうか、ビュー(後述)の場合はFalse
  WRITEABLE : True      ## データ変更可能か
  ALIGNED : True        ## データ型がアラインされているか
  UPDATEIFCOPY : False  ## Trueには変更できないので特に気にしなくて良い
>>> a.ndim              ## 次元数
2
>>> a.size              ## 要素数
12
>>> a.shape             ## 各次元の要素数 (行数, 列数)
(4, 3)
>>> a.itemsize          ## 1要素のバイト数
8
>>> a.strides           ## 24バイトで次の行、8バイトで次の列
(24, 8)
>>> a.nbytes            ## 配列全体のバイト数 (size*itemsize)
96
>>> a.dtype             ## 要素のデータ型(後述)
dtype('int64')

stridesは、メモリ上でa[0,0]とa[0,1]は8バイト離れており、a[0,0]とa[1,0]は24バイト離れていることを示します。データの並びがFortran型の場合は、strides は (8, 32) となります。一次元の場合はC型もFortran型も関係ありません。

データ型について

配列要素のデータ型はnumpy.dtypeというオブジェクトで扱われています。指定できる型については大きく分けて、論理型、符号付き整数型、符号無し整数型、浮動小数点型、複素数型の5つ。あとはデータ型のビット数別にそれぞれ指定できます。詳細は公式サイトを参照してください。

datatypes
## numpy.array() でdtypeオプションを指定しなければ自動的に設定される
## ここでは64ビット環境なので64ビットの型で生成される
>>> a = np.array([1,2,3,4,5])
>>> a.dtype
dtype('int64')    ## 64ビット符号付き整数
>>> a.itemsize
8
>>> a = np.array([1.0,2.0,3.0,4.0,5.0])
>>> a.dtype
dtype('float64')  ## 64ビット浮動小数点数
>>> a.itemsize
8
## numpy.array() でdtypeオプションを指定
## 32ビット符号付き整数
>>> a = np.array([1,2,3], dtype=np.int32)
>>> a.dtype
dtype('int32')
>>> a.itemsize
4
## 8ビット符号無し整数 (画像処理ではよく使われる)
>>> a = np.array([1,2,3], dtype=np.uint8)
>>> a.dtype
dtype('uint8')
>>> a.itemsize
1

データ型のキャストも可能です。ndarray.astype() を使います。

ndarray.astype()
>>> a = np.array([1, 2, 3, 4, 5]))
>>> a.dtype
dtype('int64')
## int64型からint32型にキャスト
>>> a.astype(np.int32)
array([1, 2, 3, 4, 5], dtype=int32)

>>> a = np.array([1.2, 2.4, 3.6, 4.8, 5.0])
>>> a.dtype
dtype('float64')
## float64からint64型にキャスト、小数点以下は切り捨て
>>> a.astype(np.int64)
array([1, 2, 3, 4, 5])

ダウンキャストでも例外は発生しません。また、ndarray.astype() は新しい配列を生成して返すので注意してください。

配列の生成

まずは配列の生成方法から。生成方法はたくさんありますが、ここでは基本的でよく使われる方法を中心に紹介します。

>>> import numpy as np
## numpy.array() で生成、引数にはリスト(またはタプル)を渡す
>>> np.array([1,4,3,2,5])
array([1, 4, 3, 2, 5])
## 二次元配列もOK
>>> np.array([[3,1,2], [6,4,5]])
array([[3, 1, 2],
       [6, 4, 5]])

## numpy.zeros() で生成、全要素の値は0
>>> np.zeros(5)
array([ 0.,  0.,  0.,  0.,  0.])
>>> np.zeros([2,3])
array([[ 0.,  0.,  0.],
       [ 0.,  0.,  0.]])

## numpy.ones() で生成、全要素の値は1
>>> np.ones(5)
array([ 1.,  1.,  1.,  1.,  1.])
>>> np.ones([2,3])
array([[ 1.,  1.,  1.],
       [ 1.,  1.,  1.]])

## numpy.identity() で生成、単位行列 (正方行列なので引数は1つ)
>>> np.identity(3)
array([[ 1.,  0.,  0.],
       [ 0.,  1.,  0.],
       [ 0.,  0.,  1.]])

## numpy.eye() で生成、identity() と似ているが列数指定ができる
>>> np.eye(3, 2)
array([[ 1.,  0.],
       [ 0.,  1.],
       [ 0.,  0.]])

## numpy.arange() で生成、組み込みの range() と同じ要領
>>> np.arange(10)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
## (始点,終点,増分)を指定
>>> np.arange(1.0, 2.0, 0.2)
array([ 1. ,  1.2,  1.4,  1.6,  1.8])

## numpy.linspace() で生成、arange() と似ているが要素数を指定できる
>>> np.linspace(1, 4, 6)
array([ 1. ,  1.6,  2.2,  2.8,  3.4,  4. ])

## numpy.logspace() で生成、値は対数スケール(常用対数)で並べられる
>>> np.logspace(2, 3, 4)
array([  100.        ,   215.443469  ,   464.15888336,  1000.        ])
## 底(base)を2に指定
>>> np.logspace(2, 4 ,4, base=2)
array([  4.        ,   6.34960421,  10.0793684 ,  16.        ])

## numpy.tile() で生成、要素を繰り返した配列を返す
>>> np.tile([0,1,2,3,4], 2)
array([0, 1, 2, 3, 4, 0, 1, 2, 3, 4])

## numpy.meshgrid() で生成、縦横に等間隔な格子状配列
>>> a, b = np.meshgrid([1,2,3], [4,5,6,7])
>>> a
array([[1, 2, 3],
       [1, 2, 3],
       [1, 2, 3],
       [1, 2, 3]])
>>> b
array([[4, 4, 4],
       [5, 5, 5],
       [6, 6, 6],
       [7, 7, 7]])

## numpy.tri() で生成、三角行列
>>> np.tri(3)
array([[ 1.,  0.,  0.],
       [ 1.,  1.,  0.],
       [ 1.,  1.,  1.]])

## numpy.diag() で生成、入力配列から対角要素を抜き出した配列を返す
>>> a = np.array([[0,1,2], [3,4,5], [6,7,8]])
>>> a
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])
>>> np.diag(a)
array([0, 4, 8])

## numpy.empty() で生成、領域の確保のみで初期化はされない
>>> np.empty(5)
array([  1.06452759e-312,   1.06452759e-312,   1.00000000e+000,
         1.00000000e+000,   2.37151510e-322])

## ndarray.copy() で配列のディープコピー
>>> a = np.array([1,2,3])
>>> b = a.copy()

## numpy.random モジュールの利用
## numpy.random.randint() で整数乱数値を要素とした配列を生成
## 生成する乱数の範囲(最小値、最大値)、要素数を指定
>>> np.random.randint(0,100,10)
array([54, 68, 19, 57, 23, 27, 36, 99, 53, 70])

配列形状の変更

ndarrayオブジェクトは制限付きで配列形状の変更が可能です。
※ ビュー(View:参照)
NumPyの一部の操作で返されるオブジェクトはビュー(View)と呼ばれ、元データの参照となります。なので、ビュー内の値を変更すると元データも変更されるので注意してください。

## 一次元配列
>>> a = np.arange(10)
>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

## ndarray.reshape() で配列形状の変更
## 二次元配列に変更、ビューを返す
>>> b = a.reshape((2,5))
>>> b
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])
## ビューの値を変更
>>> b[0] = 100
>>> b
array([[100, 100, 100, 100, 100],
       [  5,   6,   7,   8,   9]])
## 元データも変更される
>>> a
array([100, 100, 100, 100, 100,   5,   6,   7,   8,   9])
## 要素数が異なるとエラーとなる
>>> a.reshape((2,3))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: total size of new array must be unchanged

## ndarray.resize() で配列形状の変更(in-place)
>>> a = np.arange(10)
## 要素数はそのままで二次元配列に変更、ビューは返さない
>>> a.resize((2,5))
## 元データの形状が変更される
>>> a
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])
## 生成後、一度でも変数を参照していると要素数(配列長)の変更は不可となる
>>> a.resize(11)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: cannot resize an array references or is referenced
by another array in this way.  Use the resize function
## ただし refcheck オプションを False にすると参照チェックは行われない
>>> a.resize(11, refcheck=False)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0])

## ndarray.flatten() で一次元配列に変更、ビューではなくコピーを返す
>>> a = np.arange(10).reshape((2,5))
>>> a
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])
>>> b = a.flatten()
>>> b
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

返される配列がビューかコピーになるかはきちんと把握しておいた方がいいです。それと ndarray.reshape() はよく使うのでこれも覚えておくといいかと思います。

Indexing

配列要素の参照/代入、配列のスライスなどについて紹介します。基本的にPythonのリスト(list)とよく似た操作方法ですが、一部拡張構文があるので確認しておきます。

## 二次元配列 (行列)
>>> a = np.array([[0,1,2], [3,4,5], [6,7,8]])
>>> a
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

## 要素の参照/代入 (リストと同じ)
>>> a[2]
array([7, 8, 9])
>>> a[1][2]
6
>>> a[1][2] = 1
>>> a[1][2]
1
## 範囲外を指定するとIndexError
>>> a[3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: index out of bounds

## カンマ区切りで参照/代入 (拡張構文)
## a[行,列]
>>> a[1,2]  
6
## インデックスに負数を指定すると末尾から数える
>>> a[-1,-2]
8
>>> a[1,2] = 10
>>> a[1,2]
10

## スライス (記法はリストと同じ、返り値はビュー)
## a[start:end:step]
>>> a = np.arange(10)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> a[1:8:2]
array([1, 3, 5, 7])
>>> a[3:]    ## a[3:10:1]
array([3, 4, 5, 6, 7, 8, 9])
>>> a[:8]    ## a[0:8:1]
array([0, 1, 2, 3, 4, 5, 6, 7])
>>> a[::-2]  ## a[-1:-10:-2]
array([9, 7, 5, 3, 1])

## 組み合わせてもOK
>>> a = np.arange(25).reshape((5,5))
>>> a
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19],
       [20, 21, 22, 23, 24]])
>>> a[:,3] = 10     ## a[0:5:1,3] = 10 (複数要素を一度に更新)
>>> a
array([[ 0,  1,  2, 10,  4],
       [ 5,  6,  7, 10,  9],
       [10, 11, 12, 10, 14],
       [15, 16, 17, 10, 19],
       [20, 21, 22, 10, 24]])
>>> a[:2,:3] = 100  ## a[0:2:1,0:3:1] = 100
>>> a
array([[100, 100, 100,  10,   4],
       [100, 100, 100,  10,   9],
       [ 10,  11,  12,  10,  14],
       [ 15,  16,  17,  10,  19],
       [ 20,  21,  22,  10,  24]])
>>> a[2::2,::2]     ## a[2:5:2,0:5:2] (省略すると逆に読みづらいケース)
array([[10, 12, 14],
       [20, 22, 24]])

Fancy Indexing

要素の位置や条件などでマスクした特殊なインデックス指定も可能です。

>>> a = np.arange(15,0,-1)
>>> a
array([15, 14, 13, 12, 11, 10,  9,  8,  7,  6,  5,  4,  3,  2,  1])
## 整数配列でインデックス指定、コピーを返す
>>> a[[0,2,4,8]]
array([15, 13, 11,  7])
## bool型配列でインデックス指定、コピーを返す
>>> n = a>10
>>> n
array([ True,  True,  True,  True,  True, False, False, False, False,
       False, False, False, False, False, False], dtype=bool)
>>> a[n]
array([15, 14, 13, 12, 11])
## インデックスに直接条件を指定してもOK
>>> a[a>10]
array([15, 14, 13, 12, 11])
## 複数の条件がある場合はビット演算子を使う
>>> a[(4<a) & (a<10)]
array([9, 8, 7, 6, 5])
## 値の代入もできる
>>> a[a<8] = 0
>>> a
array([15, 14, 13, 12, 11, 10,  9,  8,  0,  0,  0,  0,  0,  0,  0])

インデックスの検索

## numpy.argmax() で最大値要素の内で最も小さいインデックスを返す
>>> a = np.tile(np.arange(3),3)
>>> a
array([0, 1, 2, 0, 1, 2, 0, 1, 2])
>>> np.argmax(a)
2

## numpy.argmin() で最小値要素の内で最も小さいインデックスを返す
>>> np.argmin(a)
0

## numpy.nonzero() で非ゼロ要素のインデックス配列を返す
>>> a = np.eye(3)
>>> a
array([[ 1.,  0.,  0.],
       [ 0.,  1.,  0.],
       [ 0.,  0.,  1.]])
>>> np.nonzero(a)  ## 二次元配列なので二つの配列が返される
(array([0, 1, 2]), array([0, 1, 2]))

## numpy.where() で条件に合致した要素のインデックス配列を返す
>>> a = np.arange(15).reshape((3,5))
>>> a
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])
>>> np.where(a%2==0)
(array([0, 0, 0, 1, 1, 2, 2, 2]), array([0, 2, 4, 1, 3, 0, 2, 4]))

## numpy.select() で複数条件検索、インデックスに値をセット
## 第一引数: 条件の配列(bool配列)
## 第二引数: 条件に合致した要素のインデックスにセットする値の配列
>>> a = np.arange(10)
>>> np.select([a<3, a>5], [a, a**2])
array([ 0,  1,  2,  0,  0,  0, 36, 49, 64, 81])

ndarrayの内部構造はCの配列と似ていてもインタフェースはさすが高級言語ですね。 ただ、配列のスライス処理でNumPy固有の記法を使いすぎると読みづらくなるような気もするので、複数人で開発するときは節度ある使い方をした方が良さそうです。

配列に対する操作/演算

前述の配列形状の変更以外にも様々な操作が可能です。ここでは紹介しきれないのでほんの一部だけ載せます。

配列の結合/分割、軸操作

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

## numpy.dstack() で二次元配列を結合して三次元配列にする
>>> np.dstack((a,b))
array([[[0, 8],
        [1, 7],
        [2, 6]],

       [[3, 5],
        [4, 4],
        [5, 3]],

       [[6, 2],
        [7, 1],
        [8, 0]]])

## numpy.hstack() で列方向に結合
>>> np.hstack((a,b))
array([[0, 1, 2, 8, 7, 6],
       [3, 4, 5, 5, 4, 3],
       [6, 7, 8, 2, 1, 0]])

## numpy.vstack() で行方向に結合
>>> np.vstack((a,b))
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8],
       [8, 7, 6],
       [5, 4, 3],
       [2, 1, 0]])

## numpy.dsplit() で三次元配列を分割
>>> a = np.arange(16).reshape(2,2,4)
>>> a
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7]],

       [[ 8,  9, 10, 11],
        [12, 13, 14, 15]]])

>>> np.dsplit(a,2)
[array([[[ 0,  1],
        [ 4,  5]],

       [[ 8,  9],
        [12, 13]]]), array([[[ 2,  3],
        [ 6,  7]],

       [[10, 11],
        [14, 15]]])]

## numpy.hsplit() で列方向に分割
>>> a = np.arange(16).reshape(4,4)
>>> a
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])
>>> np.hsplit(a,2)
[array([[ 0,  1],
       [ 4,  5],
       [ 8,  9],
       [12, 13]]), array([[ 2,  3],
       [ 6,  7],
       [10, 11],
       [14, 15]])]

## numpy.vsplit() で行方向に分割
>>> np.vsplit(a,2)
[array([[0, 1, 2, 3],
       [4, 5, 6, 7]]), array([[ 8,  9, 10, 11],
       [12, 13, 14, 15]])]

## numpy.transpose() で配列を転置
>>> a = np.array([[1, 2], [3, 4]])
>>> a
array([[1, 2],
       [3, 4]])
>>> np.transpose(a)
array([[1, 3],
       [2, 4]])
## ndarray.T でも良い
>>> a.T
array([[1, 3],
       [2, 4]])

## numpy.swapaxes() で軸の交換
>>> a = np.array([[1,2,3]])
>>> np.swapaxes(a, 0, 1)
array([[1],
       [2],
       [3]])

配列のソート

## numpy.sort() で配列をソートしてコピーを返す
>>> a = np.random.randint(0,100,10)
>>> a
array([75, 24, 74, 49, 93, 18, 19, 85, 73, 90])
>>> np.sort(a)
array([18, 19, 24, 49, 73, 74, 75, 85, 90, 93])

## ndarray.sort() で配列を破壊的(in-place)にソートする
>>> a.sort()
>>> a
array([18, 19, 24, 49, 73, 74, 75, 85, 90, 93])

## 多次元配列の場合
>> a = np.array([[1,4,2],[9,6,8],[5,3,7]])
>>> a
array([[1, 4, 2],
       [9, 6, 8],
       [5, 3, 7]])
>>> np.sort(a)
array([[1, 2, 4],
       [6, 8, 9],
       [3, 5, 7]])
## 軸を指定する (ここでは列でソート)
>>> np.sort(a, axis=0)
array([[1, 3, 2],
       [5, 4, 7],
       [9, 6, 8]])
## 一次元配列にしてソート
>>> np.sort(a, axis=None)
array([1, 2, 3, 4, 5, 6, 7, 8, 9])

配列要素に対する演算

様々な配列要素に対する(element-wise)演算機能が提供されています。また、それらの機能の多くは各演算子を用いて利用することも可能です。

>>> a = np.array([1,2,3])
>>> b = np.array([4,5,6])
## 加算
>>> a + b    ## np.add(a,b)
array([5, 7, 9])
## 減算
>>> b - a    ## np.subtract(b,a)
array([3, 3, 3])
## 乗算
>>> a * b    ## np.multiply(a,b)
array([ 4, 10, 18])
## 除算
>>> b / a    ## np.divide(b,a)
array([4, 2, 2])
## 剰余
>>> a % b    ## np.mod(a,b)
array([1, 2, 3])
## 冪乗
>>> a ** b   ## np.power(a,b)
array([  1,  32, 729])
## 符号反転
>>> -a       ## np.negative(a)
array([-1, -2, -3])

Broadcasting

前述の配列要素に対する各演算は同じサイズ/形状の配列同士で行っていましたが、NumPyではサイズ/形状の異なる配列同士の演算も可能で、これをブロードキャストを呼んでいます。

>>> a = np.array([1,2,3,4,5])
>>> b = np.array([10])
>>> a * b    ## (1,5) * (1,1) = (1,5)
array([10, 20, 30, 40, 50]

>>> a = np.array([0,10,20,30]).reshape((4,1))
>>> a
array([[ 0],
       [10],
       [20],
       [30]])
>>> b = np.array([0,1,2])
>>> a + b    ## (4,1) + (1,3) = (4,3)
array([[ 0,  1,  2],
       [10, 11, 12],
       [20, 21, 22],
       [30, 31, 32]])

## ブロードキャストできない例
>>> b = np.array([[1,2],[3,4]])
>>> a + b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: operands could not be broadcast together with shapes (4,1) (2,2)

2つめの例は面白いですね。ブロードキャスト機能は上手く使いこなせばコード量を大きく減らすことができそうです。

配列の走査

配列の走査はリスト(list)と同様に for in 構文を使って行うことができます。

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

## リスト(list)と同様に for in で走査できる
## ここでは行毎に走査
>>> for row in a:
...     print row
...
[0 1 2]
[3 4 5]
[6 7 8]

## numpy.ndenumerate() で多次元配列の走査
## enumerate() と同様にインデックスも取得できる
>>> for i, value in np.ndenumerate(a):
...     print i, value
...
(0, 0) 0
(0, 1) 1
(0, 2) 2
(1, 0) 3
(1, 1) 4
(1, 2) 5
(2, 0) 6
(2, 1) 7
(2, 2) 8

## ndarray.flat で一次元配列にして走査
>>> for value in a.flat:
...     print value
...
0
1
2
3
4
5
6
7
8

統計関数

NumPyでは基礎的な統計処理用の関数群が提供されています。

>>> a = np.random.randint(0,500,20)
>>> a
array([260, 253, 185, 240, 252, 375,  63, 413, 293, 431, 207, 230, 288,
       462, 270, 477, 451,  58, 408, 492])
>>> np.amax(a)       ## 最大値
492
>>> np.amin(a)       ## 最小値
58
>>> np.ptp(a)        ## 値の範囲(最大値-最小値)
434
>>> np.mean(a)       ## 算術平均
305.39999999999998
>>> np.median(a)     ## 中央値
279.0
>>> np.std(a)        ## 標準偏差
125.73519793597973
>>> np.var(a)        ## 分散
15809.34
>>> b = np.random.randint(0,500,20)
>>> b
array([313, 117, 167, 353, 468, 289, 177, 196,  20,  70, 235, 280, 480,
       125, 195, 271, 253,  55,  49, 354])
>>> np.corrcoef(a,b) ## 相関係数
array([[ 1.        ,  0.05950681],
       [ 0.05950681,  1.        ]])
>>> c = np.random.randint(0,10,20)
>>> c
array([3, 8, 7, 9, 1, 8, 4, 0, 8, 3, 9, 4, 2, 1, 4, 3, 0, 4, 8, 4])
>>> np.histogram(c)  ## ヒストグラム
(array([2, 2, 1, 3, 5, 0, 0, 1, 4, 2]), array([ 0. ,  0.9,  1.8,  2.7,  3.6,  4.5,  5.4,  6.3,  7.2,  8.1,  9. ]))

より高度な統計処理を行いたい場合は SciPy を利用することになると思います。

ファイル入出力

配列データのファイル入出力もできます。実験作業などではよくお世話になる機能です。

>>> a = np.array([[1,3,2], [4,6,5]])
>>> a
array([[1, 3, 2],
       [4, 6, 5]])
## numpy.savetxt() で配列データをASCII形式でファイルに書き出す
>>> np.savetxt('data.txt', a)
## デリミタはオプションで自由に指定できる (TSV形式)
>>> np.savetxt('data.txt', a, delimiter='\t')

## ASCII形式なのでファイルの中身は人間にも読める
$ cat data.txt
1.000000000000000000e+00        3.000000000000000000e+00        2.000000000000000000e+00
4.000000000000000000e+00        6.000000000000000000e+00        5.000000000000000000e+00

## numpy.loadtxt() でテキストファイルから配列データを読み込む
>>> np.loadtxt('data.txt', delimiter='\t')
array([[ 1.,  3.,  2.],
       [ 4.,  6.,  5.]])

## numpy.save() で配列データをバイナリ形式でファイルに書き出す
## 出力ファイルの拡張子は .npy となる
>>> np.save('data', a)

## numpy.load() でバイナリファイルから配列データを読み込む
>>> np.load('data.npy')
array([[1, 3, 2],
       [4, 6, 5]])

## numpy.savez() で複数の配列データをバイナリ形式でファイルに書き出す
>>> a, y = np.meshgrid([1,2,3], [4,5,6,7])
>>> a
array([[1, 2, 3],
       [1, 2, 3],
       [1, 2, 3],
       [1, 2, 3]])
>>> y
array([[4, 4, 4],
       [5, 5, 5],
       [6, 6, 6],
       [7, 7, 7]])
## 出力ファイルの拡張子は .npz となる
>>> np.savez('data', x=x, y=y)

## .npy, .npzファイル共に numpy.load() で読み込み可能
>>> npz = np.load('data.npz')
>>> npz.files
['y', 'x']
>>> npz['x']
array([[1, 2, 3],
       [1, 2, 3],
       [1, 2, 3],
       [1, 2, 3]])
>>> npz['y']
array([[4, 4, 4],
       [5, 5, 5],
       [6, 6, 6],
       [7, 7, 7]])

巨大なデータを保存するときは、バイナリ形式で保存した方がファイルサイズが小さくなるので良いかと。

今回はNumPyの基礎について整理してみました。僕自身はNumPyを単体で利用することはほとんどないのですが、OpenCVのPythonバインディングを利用するときによくお世話になっています。NumPyは機能が豊富なため、必要に応じてその都度公式リファレンスを参照しながら使っていく必要がありそうです。