Part 3: より進んだ計算

写像(マッピング)

写像は、値 (values) の組にキー (keys) の組を付随させて格納するもので、列の一般化とみなすことができます。なぜなら、列はある範囲の整数をキーとする写像のようなものだからです。 写像はより一般的なキーが使えますが、要素の並ぶ順番を指定することはできません。 列と写像の間には他にも基本的な相違があるので、両者ははっきりと区別されるべきです。

写像の扱いは列の場合とほぼ同様です:mapping[key] はそのキーに当てられた値を返しますし、値を変更するには mapping[key]=value とします。 全てのキーまたは値からなるリストは、それぞれ mapping.keys()mapping.values() で得られます。 キーと値のタプル (key, value) からなるリストは、mapping.items() により得られます。

辞書

最も頻繁に使われる(そしてこのコースで議論する唯一の)写像は、辞書です。辞書のキーには不易のオブジェクト、すなわち変更不可能なオブジェクトであれば何でも許されます。 つまり、リスト、辞書、配列はキーとしては使えませんが、整数、タプル、文字列(他にも沢山あります)は許されます。 値には任意のオブジェクトが使えます。

次の例は、いくつかの元素の原子質量からなる辞書を生成した後、もう一つ項目を加え、最後に水分子の質量を計算します。

atomic_mass = {'H': 1., 'C': 12, 'S': 32}
atomic_mass['O'] = 16.
print atomic_mass['O'] + 2*atomic_mass['H']

辞書の項目は、del dictionary[key] によって削除できます。

配列

列オブジェクトにはもう一種類、特に数値計算のために最適化された、配列というものがあります。配列と、配列を扱う関数は、Numeric モジュールで定義されています。 これは、既に導入した数学関数も含んでいます。数値計算用の対話型セッションを始める際に、まず from Numeric import * と入力するのは良い考えです。

配列は、他の列と異なる二つの性質を持っています:多次元配列が使えること、および配列要素は整数、実数、複素数のいずれかで、全要素が同じ型であることです。 (文字、単精度実数、一般の Python オブジェクトの配列もあるのですが、このコースでは対象としません。) 次元数は40次元までという制限があります。

配列は、他の列から Numeric.array 関数によって生成されます。 多次元配列は、入れ子になった列から作ります。 この関数には、二番目の引数によって配列の型を指定するオプションがあります(Integer, Float, Complex のいずれか)。 指定がなかった場合には、データの型から推定します。

次の例は、一次元の整数配列と三次元の実数配列を生成します:

from Numeric import *

integer_array = array([2, 3, 5, 7])
print integer_array.shape

real_array = array([ [ [0., 1.],
                       [2., 3.] ],
                     [ [4., 5.],
                       [6., 7.] ],
                     [ [8., 9.],
                       [10., 11.] ] ])
print real_array.shape
例中で、配列の形状 (shape) が印字されていますが、これは配列の各次元の長さを表すタプルで、一番目の配列に対しては (4,)、二番目のついては (3, 2, 2) となります。 次元数、すなわち様式の長さは、配列の階数 (rank) と呼ばれるものです。 通常の数オブジェクト(整数、実数、複素数)は、階数ゼロの配列と見なすことができます。

Numeric モジュールには、特別な配列を生成する関数が2、3あります。 zeros(shape, type) 関数は、shape (というタプル)で指定された形状を持ち、全要素がゼロであるような配列を返します。

配列では、列の場合よりも拡張された添字付けが使えます。 a[i](単一要素)やa[i:j](i から j の部分列)に加えて、a[i:j:k] という形が使え、これは増分 k で i から j までの部分列を取り出します。 多次元配列では、添字および範囲はカンマで区切ります。次元数よりも少ない添字しか与えられなかった場合には、添字は左から割り当てられます。 添字付けた配列は、リストの場合と同様、代入先としても使えます。

配列添字の例を示します:

import Numeric

# ゼロ配列を生成
a = Numeric.zeros((4, 2, 3), Numeric.Float)

# 特定の要素に1を代入
a[2, 1, 0] = 1.

# 第一添字がゼロである全ての要素に 2.5 を代入
a[0] = 2.5

# 最初の添字がゼロ、最後の添字が1であるような全ての要素を印字
print a[0,:,1]

直接的に要素を指定しない特別な添字が二種類あります。特殊添字 ...(点3つ)は、それに続く添字が右から割り付けられる様に、次元を "スキップ" します。 例えば、a[..., 0] は最後の添字がゼロであるような全要素を選び出します。これは任意の次元数に使えます。 特殊添字 NewAxisNumeric モジュールで定義)は、長さ1の新しい次元を一つ挿入します。 例えば、a(2, 2) という形状 (shape) だとすると、a[:, Numeric.NewAxis, :](2, 1, 2) という形状で、a と同じ要素を引き継ぎます。

配列を、例えばループ変数の列として用いることも出来ます。この場合、配列の最初の添字が列の添字になり、列の要素はより小さな階数の配列になります。これは、添字を左から各次元に割り当てるという規則に由来するものです。

配列には、標準的な算術演算(加法、乗法、など)や、Numeric モジュールの算術関数を用いることができます。この場合、それらの演算や関数は、配列の各要素に適用されます。 二数演算(加法など)では、配列の形状 (shape) が適合している必要があります。ただし、ここで "適合" とは "同一" という意味ではありません。例えば、配列全体に一つの数を掛けたり、ある行列の全ての列に他の列を加えることも可能です。 正確な適合の規則は次のようなものです: 二つの配列の形状を各要素ごとに右から比較し、小さな方が尽きるまで続ける; 全ての次元について長さが一致するか、またはどちらかの長さが1であったら、二つの配列は適合している。 より厳密でない言い方をすれば、一方の配列に長さ1の次元があったならば、他方の配列の対応する次元に沿って繰り返されます。

例:

a = array([[1, 2]])               # shape (1, 2)
b = array([[10], [20], [30]])     # shape (3, 1)
a+b を求めると、a は第一の軸に沿って3回、b は第二の軸に沿って2回繰り返され、結果は次の様になります
a = array([[1, 2], [1, 2], [1, 2]])
b = array([[10, 10], [20, 20], [30, 30]])
これで両者は同じ形状となり、a+barray([[11, 12], [21, 22], [31, 32]]) となります。 もちろん、配列が実際に反復されるわけではないので、メモリが足りなくなるといった心配はありません。

二数演算は(Numeric モジュール中の)関数にもあります:add(a, b)a+b と等価ですし、subtractmultiplydividepower も相当する他の二数演算の代りに使えます。 関数としてのみ存在する二数演算も、もっとあります:
maximum(a, b), minimum(a, b) larger/smaller of a and b a と b の大きい/小さい方
equal(a, b), not_equal(a, b) equality test (returns 0/1) 同一の判定(0 または 1 を返す)
greater(a, b), greater_equal(a, b) comparison 比較
less(a, b), less_equal(a, b) comparison 比較
logical_and(a, b), logical_or(a,b) logical and/or 論理和/積
logical_not(a) logical negation 否定

上記の二数演算は、単一の配列中の要素の組に対しても適用できます。 例えば、add.reduce(a)a の第一の次元に沿って全要素の和を計算し、minimum.reduce(a)a の最小の要素を返します。 オプションで第二引数を与えれば、次元を明示することもできます。その場合の引数は正(0 = 最初の次元)でも、負(-1 = 最後の次元)でも使えます。 他にも accumulate というのがあります:add.accumulate(a) は、a の第1要素、初めの2要素の和、初めの3要素の和、… から成る配列を返します。 よって、最後の要素は add.reduce(a) と等しくなります。

二変数関数から一つの演算を引き出す方法には、他に outer があります。 add.outer(a, b) は、ab の各要素の全ての可能な組合せについて和をとったものを要素とし、相当する(結合された)次元を持った配列を返します。

配列演算については、また後述します。

関数

すでに多くの Python 関数を使ったので、自分のものを書きたいと思ったかも知れません。 関数を使えば、一度書いた(またテストした)プログラムをまた何度も使うことができます。 このコースでは Python で関数を定義する場合のみを扱いますが、C 言語または適当な低級言語によって関数を定義することも可能です。

次のプログラム例は、空間の二点間の距離を計算する distance という名の関数を定義し、二つの適当なベクトルについてその関数を呼びます:

from Scientific.Geometry import Vector

def distance(r1, r2):
    return (r1-r2).length()

print distance(Vector(1., 0., 0.), Vector(3., -2., 1.))

関数呼び出しは、次の三段階からなると考えることができます:

  1. 関数に渡された引数が、関数定義中の対応する変数に割り当てられる。
  2. 関数中の演算が実行される。
  3. return 文に渡された値が、元の関数の呼ばれた所に置かれる。

関数の引数および返値の個数はともに任意(ゼロも含む)です。 関数は任意の個数の変数を定義できます。それらの変数はその関数に局所的(local)です。すなわち、他の関数や関数の外部に同じ名前の変数があっても、それらは無関係です。 しかし、関数はその外部で定義された変数の値を使うことは許されています。 関数の引数も、その関数に局所的となり、関数の内部では他の変数と同様に使えます。

次の関数は、直交座標を極座標に変換します。二つの引数を受け取り、二つの値を返します。 局所変数も定義しています。

import Numeric

def cartesianToPolar(x, y):
    r = Numeric.sqrt(x**2+y**2)
    phi = Numeric.arccos(x/r)
    if y < 0.:
        phi = 2.*Numeric.pi-phi
    return r, phi

radius, angle = cartesianToPolar(1., 1.)
print radius
print angle

関数は、特別な種類のデータオブジェクトにすぎません。 他の全てのデータオブジェクトと同様に、関数を変数に割り当てたり、リストや辞書に格納したり、他の関数に引数として渡したりすることが可能です。 次の例で示す関数は、引数として他の関数を受け取り、その値を表にして印字します。

import Numeric

def printTable(function, first_value, last_value, step):
    for x in Numeric.arrayrange(first_value, last_value, step):
	print x, function(x)


def someFunction(x):
    return Numeric.sin(x**2)

printTable(someFunction, 0., 5., 0.1)

モジュール

ここまでのプログラムは全て、標準モジュールから取り入れた定義の他に必要なものは全て自身で持つような、単一のファイルとして書かれていました。 この場合、複数のプログラムで使われる関数はそれぞれのファイルにコピーされなくてはなりませんが、これは余り実用的ではありません。 そのような一般的に有用な定義は、モジュールに集めておくべきです。

Python は標準ライブラリと他のモジュールを区別しません;標準ライブラリは、単にたまたまインタープリタが始めから装備しているに過ぎないわけです。 それが何処からであるかに関わらず、モジュールから取り込む仕組みは同一です。

モジュールは単に、定義(変数、関数など)からなるファイルです。 ファイル名は、.py で終らなくてはなりません;その前がモジュール名になります。 例えば、import Numeric という命令は、Numeric.py という名前のファイルを探して実行し、その結果得られる定義を、取り込み元のプログラムが使えるようにします。

しかし、Python はファイルシステム全体からモジュールを探すことはしません;それは非能率的で危険でしょう。 Python は Unix がファイルの場所を指定するのに用いているのと類似の方法を使います:Python を実行する前に、PYTHONPATH という名の環境変数を設定することができます。 コロンで区切った一連のディレクトリ名をその値とします。 Python は、指示された順でディレクトリ中のモジュールを探します;モジュールが見つからなかった場合は、標準ライブラリの検索を試みます。

これまでに使ったモジュールには、名前に幾つかのドット(点)を含むものがありました。 これらのモジュールは、パッケージに含まれています。 パッケージは、他のモジュールを含むモジュールです。 ライブラリを構造化し、相異なる人々によって書かれたライブラリ間の名前の衝突を防ぐために、パッケージを使います。 ここまでに使ったパッケージは Scientific だけで、GeometryIO(他にもあります)といったモジュールを含んでいました。 しかも、これらのモジュールはそれ自身がパッケージになっています;例えば IO は、以前使った TextFileFortranFormat モジュールを含んでいます。 Scientific パッケージ全体の説明はマニュアルにあります。

パラメータ付きプログラム

よく使う Python プログラムを書く際に、シェルのコマンド行からパラメータを受け取るようにしたい、すなわち、プログラムを次のように実行したい場合もあるでしょう。

python my_program.py a b c

これには、コマンド行中のパラメータを参照する方法があれば良いわけです。 sys モジュールには、argv という変数があり、それは全パラメータのリストを値として持ちます。リストの最初の値はその Python プログラムの名前になります。 上の例では、sys.argv['my_program.py', 'a', 'b', 'c'] です。

#!/usr/local/bin/python

Unix システムでは、Python プログラムを直接起動するようにもできます。 これには二つの手続きが必要です:まず、Python プログラムの最初の行に

#!/usr/local/bin/python
という行を加えます。 Python が他の場所にインストールされているならば、パス名を変える必要があります。 次に、Unix の chmod コマンドによって、そのプログラムファイルを実行可能にします。

Python は、上のおまじないのような行を無視します。 それはコメント(# で始まる行)と見なされるからです。 しかし、Unix シェルはその行を認識して、そのファイルを実行するために起動するべきプログラムを指示するものと見なします。

統計

Scientific.Statistics モジュールには、初歩的な統計解析の関数が含まれています。

average(data)(平均)、variance(data)(分散)、standardDeviation(data)(標準偏差)といった関数は、いずれもデータ値の列を受け取り、その名の示す統計量を返します。

Histogram という副モジュールは、棒グラフオブジェクトを定義します。 Histogram(data, number_of_bins) は、データの列を受け取り、データ範囲を指定された個数の箱に分け、各々の箱に幾つのデータ値が入るかを勘定して、棒グラフを計算します。 オプションで三番目の引数に、解析する範囲(下限と上限)を指定するタプルを渡すこともできます。 ヒストグラムは、階数2の配列によって箱の列を表したもので、各々の箱にはその中心値とカウント数が記されています。

図示

Python には、専用の図示用モジュールはまだありません。 とりあえず、図示は外部プログラムに委ねられています。 簡単な線図表示用の関数を含むモジュールは二つあります: Scientific.MathematicaGnuplot です。 これらは、その名の示す外部プログラムを利用します。 これらのモジュールは、共に plot という名の関数を定義しており、振る舞いはいずれも共通です。

plot 関数は、任意の個数の引数を受け取ります。 引数の各々が一つのデータセットを指定し、それらは同じグラフに表示されます。 各データセットは点の列です。 一点は、二要素から成る列(x、y座標)または単一の数、で指定されます。 後者の場合、その数がy座標と見なされ、x座標は列においてその点を表す添字になります。

plot 関数には、file='something.ps' という形式のキーワード引数 を受け取るオプションがあります。 この引数があると、図は指定されたファイルにポストスクリプト形式で書き出されます。

例:

from Gnuplot import plot
from Scientific.Statistics.Histogram import Histogram
from Numeric import arrayrange, sqrt

plot(Histogram(sqrt(arrayrange(100.)), 10))

練習問題


目次