Python3 – map関数

基本

コレクションやイテレーターの戻り値に何らかの処理を施したい場合、たとえば内包表記を使うことが課が得られる。

同様の効果があるmap()関数は、コレクションやイテレーターの戻り値に関数を作用させた結果を返す。第1引数には適用したい関数、第2引数には適用対象となるコレクション等を指定する。

以下の例は、冒頭に示した内包表記と同じくリストの要素を2倍する。使い方としては、与えられた引数を2倍する関数を準備し、関数を第1引数に、対象となるリストを第2引数に与える。

なおmapオブジェクトはイテレーターとして振舞うので、結果をリスト表示するためにはlist()関数を通す必要がある。

再利用不可

map()関数で得られたオブジェクトを変数にセットして使うこともできる。ただしmapオブジェクトはイテレーターなので1度利用したものをそのまま再度利用することはできない

イテレーター等の指定

また第2引数にはコレクションではなく、rangeオブジェクトやイテレーターを渡すこともできる。

ラムダ式が可能

map()関数の第1引数には、関数のほかにラムダ式を指定することも可能。

辞書によるマッピング

コードが格納されているコレクションから、辞書とmap()関数でコードに対応する値を得ることができる。

逆に値が格納されているコレクションから、辞書を使ってコードのコレクションを得ることもできる。この場合、辞書のキーとコードを入れ替えて適用する。

 

Python3 – スライスとfindでの位置指定の違い

文字列のスライスとfind/rfindで検索範囲の指定が違っていて混乱したので整理する。

文字列のスライスでは、次のように指定する。

このとき、stepが正なら前方から後方へ、負なら後方から前方へ向かって文字列が取り出されるが、そのときのstartとendの指定は、stepが正か負かで次のように違ってくる。

つまりstepが正の時は、前方から後方へ文字列が取り出されるのでstart<stop、stepが負の時は逆にstart>stopでなければならない。

一方、find/rfindについてはスライスと違ってくる。

(string, start, stop)の指定で、検索範囲の指定は常にstart < stopでなければならない。

 

 

 

 

Python3 – モジュールとパッケージ

概要

モジュールは共通して利用したい実行ファイルをインポートできるように配置したもの。パッケージはパッケージやモジュールをひとまとめにするもの。

  • ローカルの実行環境では、ディレクトリがパッケージになる
    • パッケージ内の__init__.pyファイルは特別なモジュールで、パッケージをインポートするだけでモジュール内の関数やクラスが使える
    • パッケージ下にモジュールファイルを置くと、[パッケージ名].[モジュール名]でモジュール内の関数やクラスが使える
  • パッケージのセットアップとインストール→今後

カレントディレクトリ下での使用

モジュールは普通のPython実行ファイルで作成。

たとえばカレントディレクトリに以下のようなファイルをtestmodule.pyとして置く。このモジュールには関数が1つとクラスが1つ定義されている。

そして同じカレントディレクトリにあるtest.pyを以下のように書く。

この時点でファイル構成は以下のようになっている。

ここでtest.pyを実行すると次のように表示される。無事インポートできて、モジュールで定義した関数、クラスとも使える。

実行後には新しいディレクトリが追加されて、以下のようになった。

カレントディレクトリ下でのモジュール管理のまとめ

  • 関数・クラスなどを含むモジュールをPythonファイルとして作成し、カレントファイルに置く
  • 利用側で、モジュール名(モジュールのファイル名から拡張子”.py”を除いた名前)を指定してインポート
  • [モジュール名].[関数名/クラス名]として利用

サブディレクトリ管理でのエラー

モジュールをカレントディレクトリではなくサブディレクトリに置いて管理したいので、以下のように置いてみる。

そして以下のように呼び出そうとしたが残念ながらエラー。

ただしこの実行後、サブディレクトリの下に新しいディレクトリがつくられていた。

これはimport文を読んだPythonがsubdirというパッケージを探しに行ったが見つからなかったためで、モジュールを置いたディレクトリをパッケージとしてPythonに認識させる必要がある。

パッケージ

パッケージ直下のモジュール

ディレクトリをパッケージとしてPythonに認識させるためには__init__.pyというファイルが必要になる。

たとえば以下のようなディレクトリ・ファイル構造として

__init__.pyファイル内容を前のtestmodule.pyと同じとする。

カレントディレクトリ下のtest.pyでは前は[モジュール名].[関数/クラス]としていたが、今回は以下のようにパッケージから呼び出すように変更。__init__.pyファイルに書いたモジュールは、パッケージ直下から呼び出される。

出力は以下の通りとなって成功。

またここでもmypackageディレクトリの下に新しいディレクトリが作成された。

パッケージ内のモジュール

testpackageディレクトリ下にothermodule.pyファイルを置く。

test.pyを以下のようにして実行。

以下のように実行される。

一度実行されると、pycacheディレクトリに新たなファイルが追加される。

 

 

待ち行列(M/M/1)の再現 – 時刻制御

考え方

時刻制御(time driven)による待ち行列の計算の考え方は次の通り。

  1. 時刻t0から始めて、以下時間間隔Δtずつ増やしながら計算を進めていく
  2. ある時刻で一様乱数の値がλΔtより小さければ到着が発生
    • 新たにトランザクションを生成し、到着時刻を記録し、システムに投入する
  3. ある時刻で到着が発生せず、システムにトランザクションがある場合、一様乱数の値がμΔtより小さければサービスが終了
    • サービス中のトランザクションのサービスを終了させ、終了時刻を記録する
    • このとき待ち行列にトランザクションがあれば、先頭のトランザクションのサービスを開始し、サービス開始時刻を記録する
  4. 予め設定していた時刻(またはトランザクション数)に達したら終了

queue-mm1-time-driven-fig1

たとえば上の図でトランザクション②に注目すると、

  1. 時刻t4でトランザクションが発生
  2. t7でトランザクション①のサービスが終了し、②のサービスが開始
  3. t8でサービス終了

イベント制御のケースではRを用いたが、今回はPythonを使う。なおM/M/1待ち行列の解析的アプローチはこちら

待ち行列システムのクラスをつくり、システムの外からはトランザクションの到着、サービス終了の操作を行うだけで、内部的に処理が進むようにする。

クラス構成

待ち行列システムを表すQueueSystemクラス、システムのサービス窓口を表すServiceクラス、システム中の1つのトランザクションを表すTransactionクラスから構成される。システム中の待ち行列はインスタンス変数のリストとして持つ。

M/M/1型の行列システムとして、QueueSystemクラスは1つのServiceオブジェクトと1つの待ち行列リストをメンバーに持つ。

トランザクションの到着の指示が出るとTransactionオブジェクトが生成され、システムに登録される。

その一方で実行中のサービス終了の指示が出るとサービスを受けているトランザクションが解放される。

各Transactionオブジェクトに対して、システムの到着、サービスの開始、サービスの終了の時刻が自動的に登録される。

Transactionクラス

  • 識別子としての整数値のindexのほか、到着・サービス開始・サービス終了の時刻、到着時のシステム内トランザクション数と待ち行列長をメンバーとして持つ
    • これらのメンバーに対しては直接アクセスして参照・代入する
  • このクラスのオブジェクトの文字列表現を__str__メソッドで実装している

Serviceクラス

  • privateなメンバ_transactionを持ち、これはサービス中のトランザクションを保持する
    • サービス中のトランザクションがない場合はNone
  • サービス窓口が空いているか(vacant)、サービス中か(occupied)を判定するメソッドを持つ
  • サービス中のトランサクションへの参照を返すtransaction_in_service()メソッドを持つ
  • サービス開始時の処理を行うstart()メソッドを持つ
    • サービスを開始するTransactionオブジェクトとサービス開始時刻を指定する
  • サービス終了時の処理を行うend()メソッドを持つ
    • サービス終了時刻を指定し、サービス中のTransactionオブジェクトにその時刻を設定し、オブジェクトへの参照を返す

QueueSystemクラス

メンバとなるインスタンス変数は4つ

  • 待ち行列リスト_queue
  • サービス窓口オブジェクト_service
  • 到着済みのトランザクションを登録する_arrived_transaction_list
  • サービス完了済みのトランザクションを登録する_departed_transaction_list

getter系のメソッドは

  • システム中にあるトランザクション数を返すtransaction_number_in_system()
  • 待ち行列長を返すqueue_length()
  • システム中にあるトランザクションへの参照を返すtransaction_in_service()
  • 待ち行列中にあるトランザクションへの参照を返すtransaction_in_queue()
  • 到着済みのトランザクション数を返すarrived_transaction_number()
  • 完了済みのトランザクション数を返すdeparted_transaction_number()
  • 到着済みのトランザクションのn番目を返すarrived_transaction()
  • 完了済みのトランザクションのn番目を返すdeparted_transaction()

システムを操作するメソッドは

  • システムに到着したトランザクションを登録するarrive()
  • サービスが終了したトランザクションをシステムから取り出すdepart()

システムの状態を表示するメソッドとして以下の2つを実装している。

  • システムの現在の状態を表示するdisplay_current_status()
  • サービスを完了したトランザクション群の各パラメータを表示するdisplay_transaction_summary()

動作テスト

上記のクラス群を使って以下を実行。規則正しく刻まれた時刻に4つのトランザクションが到着し、その後規則正しくサービスが終了していく様子と、その結果を表示させている。

実行結果は以下の通りで、意図したようにトランザクションの到着に従って登録され、終了指示によって待ち行列・サービス状況が変化している。

時刻制御の実装

考え方

概略の流れは以下の通り。

  • 計算に必要なパラメータを設定する
    • 到着率 λ
    • サービス率 μ
    • 計算時間範囲 t_max
    • 計算時間間隔 dt
  • 時刻tが0からt_maxになるまでdtずつ増やしながら以下を実行(以下、randは[0, 1)の一様乱数値)
    • λdt < randならトランザクションが一つ到着して次の時間ステップへ
    • サービス中のトランザクションがあって、μdt < randならトランザクションが一つ終了して次の時間ステップへ
  • トランザクションのサマリーを表示

テスト実行

実行結果は以下の通り。

実行結果

到着時間間隔の分布の確認

およそ1000個のデータを発生させるため、観測時間を10000秒にして計算し、到着したトランザクションの到着時間間隔を確認した。確認部分のコードの未以下に示す。

実行結果は以下の通りで、指数分布の理論値とよく合っている。

matplotlibのhist関数はnormed=Trueとした場合に期待した動作をしないことがあるが、このケースではうまくいっている。

queue-mm1-time-driven-arrival-interval

平均トランザクション数・平均待ち行列長など

10000秒の観測時間で、システム内の平均トランザクション数、平均待ち行列長と、それらに対する平均応答時間、平均待ち時間について計算し、λ、μから計算される理論値と比較した。

計算結果は以下の通りで、かなり整合している。

各実行結果の傾向

およそ1000個のデータに相当する10000時間単位まで、時間ステップ0.1で計算を行い、理論値との違いを確認した。

ρ = 0.5のケース

λ = 1/10、μ = 1/5、ρ = 0.5のケース。平均トランザクション数と平均待ち行列長の理論値は、L = 1、Lq = 0.5で、これらに対する待ち時間はT = 10、Tw = 5。

トランザクション数に関する計算結果は理論値とかなりよく合っている。

L T 1/λ
1 1.19667319 11.32504892 9.46377759
2 1.14047151 10.43113949 9.14633938
3 1.01360544 9.81409135 9.68235860
4 1.10038241 10.86032505 9.86959165
5 1.21322160 11.32597765 9.33545664
6 0.91197544 9.24390993 10.13613912
7 0.92921236 9.52532403 10.25096570
8 0.92452830 9.96117180 10.77432870
9 0.87450199 9.12868526 10.43872440
10 0.93159923 9.19017341 9.86494311
AVG 1.02361714 10.08058469 9.89626249
STD 0.12142639 0.81382679 0.48579815

待ち行列長は若干ばらつきが大きくなっている。

Lq Tq 1/λ
1 0.67906067 6.27358121 9.23861664
2 0.63555992 5.54204322 8.71993819
3 0.50728863 4.87269193 9.60536397
4 0.57456979 5.73049713 9.97354408
5 0.63314711 6.07476723 9.59455888
6 0.42169908 4.29559877 10.18640773
7 0.46660020 4.73389831 10.14551282
8 0.44985104 5.01837140 11.15562921
9 0.38844622 4.27151394 10.99641011
10 0.44797688 4.41705202 9.85999996
AVG 0.52041995 5.12300151 9.94759816
STD 0.09722322 0.70052360 0.70110321

ρ = 0.9のケース

λ = 1/10、μ = 1/9、ρ = 0.9のケース。平均トランザクション数と平均待ち行列長の理論値は、L = 9、Lq = 8.1で、これらに対する待ち時間はT = 90、Tw = 81。

トランザクション数に関する計算結果は、理論値の周りに大きくばらついている。

L T 1/λ
1 7.59815951 76.92402863 10.12403445
2 7.72051010 86.82890542 11.24652443
3 7.96595331 80.47908560 10.10288191
4 5.22616633 54.55070994 10.43799728
5 5.11700000 52.88450000 10.33505961
6 8.65760322 89.70845921 10.36181226
7 6.19872476 65.22922423 10.52300703
8 10.88111888 110.54815185 10.15963092
9 8.41194645 86.59763131 10.29460088
10 18.80710660 193.82274112 10.30582456
AVG 8.65842892 89.75734373 10.38913733
STD 3.75303129 38.38222105 0.31302798

待ち行列長の計算結果もかなりばらついている。

Lq Tq 1/λ
1 6.71165644 67.97975460 10.12861061
2 6.82678002 77.21158342 11.31010274
3 7.02334630 71.38190661 10.16351801
4 4.35902637 45.64036511 10.47031177
5 4.22800000 44.04650000 10.41780984
6 7.72205438 80.19728097 10.38548513
7 5.31880978 56.14229543 10.55542457
8 9.95704296 101.38201798 10.18194040
9 7.49742533 77.17693100 10.29379122
10 17.87005076 184.15197970 10.30506193
AVG 7.75141923 80.53106148 10.42120562
STD 3.73662161 38.18242499 0.32416633

ρが高くなる(混雑してくる)と計算結果と理論値のかい離が大きくなるのは、(言語が異なるとはいえ)同じ傾向。時刻制御では計算時間間隔を0.1でも試してみたが、あまり傾向は変わらない。

 

 

Python3 – ジェネレーターの実装

関数とyield文によるジェネレーター

イテレータ__next__メソッドでコレクションの要素を取り出すのに有用なのに対して、ジェネレーターはyield文で任意の結果を順次返していく。関数内にyield文を書くと、その関数はジェネレーターになる。

ジェネレーターは以下のように構成する。

  • ジェネレーターは関数として定義され、その関数への参照として取得する
  • forブロックのループのたびにジェネレーターの中のyieldが順次1つずつ呼び出される
  • 呼び出せるyield文がなくなるか、forブロックで強制的に抜け出したときループが終了

先の例ではjoin文が4回だけ呼び出されるので、それが呼べなくなったときにループが終了する。これに対して次の例では、ジェネレーターは無限に結果を生成し続け、forブロックで条件に合致したときにループを終了させている。

ジェネレーター式

内包表現と同じ表現を()内に書くと、要素を一つずつ生成するジェネレーターオブジェクトを返す。

 

Python3でヒストグラムとplotの重ね合わせ

指数分布に従う乱数を生成するnumpy.random.exponential()関数の確認のため、同関数で発生させた乱数群のヒストグラムと、指数分布の確率密度関数のグラフを重ねて比較してみた。

コードは以下の通りで、ヒストグラムのrangeと関数計算のrangeを合わせている。

実行結果は以下の通り。

python-matplot-pyplot-exponential-random

 

Python3 – numpy.random – 乱数

ライブラリ

乱数を扱うには、numpy.randomライブラリが必要。

seed~乱数系列の指定

乱数の系列を固定したいときはseed()関数でシードを設定する。この設定をしないかシードにNoneを設定した場合は、実行ごとに乱数系列が変わる。

並べ替え

shuffle()

引数で与えた配列の要素をシャッフルする。元の配列を書き換え、戻り値はNoneとなる。

permutation()

引数で与えた配列の要素をシャッフルした結果の配列を返す。元の配列は書き換えられない。

permutation(n)の引数nに整数を指定すると、permutation(np.arange(n))と同じ効果を持つ。

確率分布

一様分布~[0, 1)

rand()

引数なしのrand()関数は[0, 1)の一様分布に従う乱数を1つ発生させる(random()関数でも同じ)。

引数を1つ指定すると、その個数の乱数を要素に持つ配列を返す(random()関数でも同じ)。

引数を2つ指定すると、その行数・列数の2次元配列の乱数を、引数がn1、n2、n3、・・・の場合、n1×n2×n3×・・・の多次元配列を返す。

random_sample()/random()

random_sample()は、引数が1つの場合はrand()と同じだが。配列の場合はリストやタプルで次数を与える。

random()はrandom_sample()と同じ。

一様整数乱数~任意の範囲の整数

randint()

1~3個の引数をとり、終値未満の整数乱数を返す。終値を含まないため、配列からランダムに要素を取り出したい時に便利。

  • 引数が1つ(n)の場合、[0, n)の範囲の整数乱数を1つ返す
  • 引数が2つ(m, n)の場合、[m, n)の範囲の整数乱数を1つ返す
  • 引数が3つ(m, n, s)の場合[m, n)の範囲の整数乱数s個を要素とする配列を返す

サイズをリストやタプルで指定した場合、それに対応した次元・要素数の多次元配列で乱数列を返す。

random_integers()

引数の構成はrandint()と同じだが、

  • 上限値を含む、[start, end]の範囲の整数乱数
  • 引数の意味はrandint()と同じ

正規分布

randn()~標準正規分布

平均が0、標準偏差が1の標準正規分布に従う乱数を発生させる。配列で結果を得たいときはrand()と同じように直接次数を指定する。

normal()~正規分布

normal(loc=0.0, scale=1.0, size=None)

locを平均、scaleを標準偏差とする正規分布に従う乱数を返す。

sizeに整数を指定すると、その個数の乱数を配列で返す。

sizeにリスト・タプルを指定すると、その形状の配列で乱数を返す。

locscaleに同じ次数の配列を指定すると、それらがブロードキャストされた結果に従う乱数が配列で返される。

exponential()~指数分布

指数分布。

exponential(lmd) – 指数分布
lmdは平均の逆数で、ポアソン過程でいう到着率、サービス率にあたる。

Tips

整数の乱数配列を生成する

整数の乱数配列を生成する方法はこちら

 

 

matplotlib.pyplot.bar – 棒グラフ

基本形

pyplot.bar()に2つの引数を与えるのが基本形。

  • 第1引数は横軸のリスト
  • 第2引数は横軸リストの要素に対応した値

python-matplot-pyplot-bar01

bar(x, y)の横軸xの要素はfloatでなければならないが、これを文字表記に変更する。

python-matplot-pyplot-bar02

ただし項目名が左寄せになっているので、これらをセンタリングする。

なおxticksを指定せず数値のままでセンタリングすると、xで与えた数値が中央に来るよう調整される。

python-matplot-pyplot-bar03

棒の位置や幅のコントロール

デフォルトでは棒の位置はxで与えた数値に左寄せされる。またwidth=で棒の幅を指定することができる。

以下のように指定することで、ヒストグラムのような図を描くこともできる。

python-matplot-pyplot-bar04

hist()関数の戻り値を使って、以下のように書けるが、時にグラフの両側に空きができてしまうので、さらに検討が必要。

 

ヒストグラム

pyplotで簡単にヒストグラムが描ける。デフォルトでは縦軸は度数。

python-matplot-pyplot-histgram-normed-false

縦軸を頻度にしたい場合は、normed=TRUEオプションをつける。

縦軸が小数になっていて、各階級の頻度が1/10bins→0.1程度になっていることに注意。

python-matplot-pyplot-histgram-normed-true

頻度分布について

上の例ではうまく頻度分布が表示されているが、以下のようにうまくいかない場合がある。

python-matplot-pyplot-histgram-normed-trouble

ヒストグラムとplotの重ね合わせ

ヒストグラムのrankの範囲とplotのx軸の範囲を同じにすれば、こちらの例のように重ねて描くことができる。

ヒストグラムの戻り値

hist()は3つの戻り値を返す。

bin_count
各ビンの度数。
bin_position
ビン数とrangeに応じたビンの位置。
patches
各ビンのオブジェクトの配列。ビンごとに色を変えるなどの操作ができる。

Python3 – ライブラリのインストールとアップグレード

 

pipでライブラリをインストールしたときの記録。

numpy

pipのアップグレード

最初にnumpyをインストールした直後にメッセージが出た。

matplotlib

scipy

エラー発生

20191023、O’REILLYの”Pythonではじめる機械学習”の勉強のため、scipyのインストールに再挑戦。

パッケージのリストを表示させるとpipが最新バージョンではないというWARNING。

pipをアップグレード。

scipyを無事インストール。

そのまま調子に乗って、ipython、scikit-learn、pandas、pillowもインストール。

20191027、mglearnをインストール。

 

Python3 – __iter()__によるイテレーターの実装

標準形

Pythonの特殊メソッド__iter____next__を使ってオブジェクトのイテレーターをつくることができる。ジェネレーターがyield文によって任意の値を生成するのに対して、イテレーターはコレクションの要素を順次取り出すときなどに有用。

__iter____next__の2つのメソッドを持ったオブジェクトがfor文で参照されるたびに、以下のように動作する。

  1. forを始める前に__iter__メソッドが実行される
  2. forブロックのループのたびに__next__メソッドが実行される

2つのメソッドの書き方は以下の通り。

__iter__
戻り値としてオブジェクト自身(self)を返す。
__next__
イテレートされるべきプロパティを変更し、戻り値として現在値を返す。もし終了条件に合致しいている場合、イテレート終了の例外を発する。

次の例では、1から初めて最初に設定した値まで1ずつ増える数列のイテレーターをつくっている。

最初の__init__メソッドでは引数から数列の最大値をプロパティにセット。

__iter__メソッドはカウンタになるプロパティに初期値をセットして、selfを返す。

__next__メソッドはそれが呼ばれるたびに次のことを行う。

  • カウンタ値が最大値を超えていたらforブロックから抜け出るよう例外を返す
  • 現在のカウンタ値を返す
  • カウンタ値をインクリメントする

forブロックから抜け出るための例外はStopIterationが準備されていて、forブロックがこの例外を受け取ると、エラーを発生させることなくforブロックの次の処理に移る。

インスタンス変数のイテレーターの取得

たとえばあるクラス内にリストがあって、リストそのものの構造は安全に守ったまま、その要素にアクセスする方法を考えてみた。

インスタンス変数のリストそのものやgetterなどで取得したリストへの参照をメソッドで返すと、リストそのものの操作までできてしまうが、それを防ぎたい。

そこで、インスタンス変数のイテレーターを生成するクラスを元のクラスのインナークラスとして準備して、外部へはイテレーターのインスタンスを渡すようにしてみた。

インナークラスでなくてもできるが、元のクラスのインスタンス変数の構造と密接に関連しているなら、インナークラスでまとめた方が明快だと思う。