[Python][Matplotlib] グモウスキー・ミラの写像を描く
はじめに
不思議な模様が現れることで有名な差分方程式系の一つに、グモウスキー・ミラの写像というものがある。
以前、PythonのMatplotlibを使って作図したコードを発見したので貼ってみる。本当はもっときれいに描きたかったのだが、Matplotlibでの方法がよく分からなかった。
出力結果
figure2の方は、五枚の翼を持つ鳥に見えるということで、写像の提案者の一人であるミラは「神話の鳥 (mythic bird)」と呼んだらしい。
ソースコード
import os.path import matplotlib.pyplot as plt import numpy as np from tqdm import tqdm x0 = 0.1 y0 = 0.1 T = 20000 N = 1000 marker = '.' markersize = 1 fillstyle = 'full' DPI = 1200 def F(x, mu): return mu * x + 2 * (1 - mu) * x**2 / (1 + x**2) def func(x, y, alpha, sigma, mu): x_next = y + alpha * (1 - sigma * y**2) * y + F(x, mu) y_next = - x + F(x_next, mu) return (x_next, y_next) def plot_times(*, x_init, y_init, ax, N, params): xs, ys = [np.ones(N) for _ in range(2)] xs[0], ys[0] = func(x_init, y_init, **params) for t in np.arange(N - 1): xs[t + 1], ys[t + 1] = func(xs[t], ys[t], **params) ax.plot( xs, ys, fillstyle=fillstyle, markerfacecolor='C0', markeredgecolor='C0', marker=marker, markersize=markersize, linewidth=0 ) return xs[-1], ys[-1] def plot(params, T, filename): x, y = (x0, y0) fig = plt.figure() ax = fig.add_subplot(111) ax.plot(x, y) times_now = 0 while True: if times_now >= T: break elif times_now + N < T: x, y = plot_times(x_init=x, y_init=y, N=N, ax=ax, params=params) times_now += N elif (times_now < T) and (times_now + N >= T): n = T - times_now x, y = plot_times(x_init=x, y_init=y, N=n, ax=ax, params=params) times_now += n else: raise ValueError fig.savefig(filename, dpi=DPI) return def main(): params = { 'mu': -0.38, 'alpha': 0.0083, 'sigma': 0.1 } plot(params=params, T=T, filename='figure1.jpg') params = { 'mu': -0.8, 'alpha': 0.008, 'sigma': 0.05 } plot(params=params, T=T, filename='figure2.jpg') return if __name__ == '__main__': main()
[Python][Matplotlib] 等高線プロットcontourfで一部に色を塗らない
結論から言えば、色を塗りたくない場所にnp.nan
を代入しておけばよい。
通常のcontourf
from itertools import product import matplotlib.pyplot as plt import numpy as np X = np.linspace(0, 5, 20) Y = np.linspace(0, 5, 20) X, Y = np.meshgrid(X, Y) Z0 = np.cos(1.5 * X) * np.cos(1.5 * Y) fig = plt.figure() ax = fig.add_subplot() cs = ax.contourf(X, Y, Z0) fig.colorbar(cs, ax=ax) fig.savefig('test_Z0.jpg', dpi=600) plt.close(fig)
一部領域で色を塗らないcontourf
例えば右下()で色を塗らない場合。
from itertools import product import matplotlib.pyplot as plt import numpy as np X = np.linspace(0, 5, 20) Y = np.linspace(0, 5, 20) Z0 = np.cos(1.5 * X) * np.cos(1.5 * Y) Z1 = np.copy(Z0) for yi, xi in product(range(Z1.shape[0]), range(Z1.shape[0])): if Y[yi] < X[xi]: Z1[yi][xi] = np.nan else: Z1[yi][xi] = np.cos(1.5 * X[xi]) * np.cos(1.5 * Y[yi]) fig = plt.figure() ax = fig.add_subplot() cs = ax.contourf(X, Y, Z1) fig.colorbar(cs, ax=ax) fig.savefig('test_Z1.jpg', dpi=600) plt.close(fig)
[Python][Matplotlib] よく使うリンク集
Matplotlibを使うときに自分がよく開くページのリンク集。公式ドキュメントがメインだが他のページも。随時追加。
目次
Matplotlibの公式ドキュメントについて
Matplotlibについて検索すると、バージョンの異なる複数のMatplotlib公式ドキュメントがヒットする。開いている公式ドキュメントが扱っているMatplotlibのバージョンは、URLを見るか、ヘッダー画像を見れば分かる(がやや分かりにくい)。
記事執筆時点での最新バージョンは3.2.2。
古いものでは、ver2.0のドキュメントも存在する。少なくともメジャーバージョンが同じドキュメントを参照するように注意した方がよいだろう。
最新バージョンは次のページで確認できる。
ちなみに自分が使用しているバージョンは__version__
変数で次で確認できる。
import matplotlib print(matplotlib.__version__)
あまり見ることはないが、リリース情報はこのページ。
色関連
Color
色の指定の仕方とか、Colormapクラスの説明とか
名前のついている色(文字列で指定可能な色)の一覧
公式ページではないが、色の指定方法について分かりやすくまとめられている。
等高線プロットContour(f)関連
imshow関連
[Python] CSVファイルの表をはてな記法の表に変換する
2020/07/16追記
オンラインコンバータを作ってみた。
cyanatlas.hatenablog.com
はじめに:はてな記法の表とは
はてなブログなどにははてな記法という独自の言語があり、これを使うことでHTMLファイルを編集せずにWEBページが書けます。
例えば次のように書けば表が表示されます。
|*名前|*色|*個数| |りんご|赤|1| |みかん|だいだい|2|
名前 | 色 | 個数 |
---|---|---|
りんご | 赤 | 1 |
みかん | だいだい | 2 |
問題点:はてな記法で表を書く面倒さ
はてな記法で表を書くのは、HTMLで表を書くより楽だが、それでもやや面倒であると感じる。理由は次の二つ。
- 編集中は列(縦のライン)が揃わないので、どこを編集しているのか分かりにくい
- WEBページの表をコピペできない
解決策としては次の二つが考えられる。
一つ目の方法については複数のWEBページで紹介されていたので各自検索してほしい。
一つ目の方法で解決できるとは言え、表組みのためだけに、表組以外は簡単に書けるはてな記法をやめたくないという私のような人もいるだろう。そこで、この記事は二つ目の方法を説明する。
解決策:CSVファイルをはてな記法に変換する(Python)
- Excelなどを使ってCSVファイルを作成する(エンコードUTF8-BOM、コンマ区切りとすれば、後に示すPythonコードをそのまま使えるはず)。
- 後に示すPythonコードで、FILENAME変数を変換したいCSVファイルの名前(あるいは相対パス)に変更する。
- Pythonコードを実行して、はてな記法を含むtxtファイルを出力する(ファイル名は
hatena_FILENAME.txt
)。
コードを示す。
import os import csv FILENAME = 'test.csv' def main(): res = '' with open(FILENAME, mode='r', encoding='utf-8-sig') as f: csvreader = csv.reader(f, delimiter=',') head = True for row in csvreader: for cell in row: if head: res += '|' + '*' + cell else: res += '|' + cell res += '|\n' head = False outputfilename = 'hatena_' + os.path.splitext(os.path.basename(FILENAME))[0] + '.txt' with open(outputfilename, mode='w', encoding='utf8') as f: f.write(res) return if __name__ == '__main__': main()
[Python] タプルには代入できないがタプル内リストには代入できるという話
環境:Windows10 + Python 3.7.6
リストとタプルの違い
Pythonの組み込み型には、配列を表現できるリストとタプルという二つの型が用意されている。この二つには、値の変更を許すかどうかに違いがある。
リスト:要素の変更を許す
タプル:要素の変更を許さない
リストの要素を変更することはできるが、タプルの要素を変更することはできない。もし無理に変更しようとすれば、エラーとなる。
コード 1
x = [1, 2, 3] print(x) x[0] = 9 print(x)
出力 1
[1, 2, 3] [9, 2, 3]
コード 2
x = (1, 2, 3) print(x) x[0] = 9 print(x)
出力 2
(1, 2, 3) Traceback (most recent call last): File "XXX.py", line 3, in <module> x[0] = 9 TypeError: 'tuple' object does not support item assignment
何度も変更する予定の場合はリストを使い、変更する予定がない場合はバグを防ぐためにタプルを使う。というのが基本的な方針である。
では、タプルの中身は変更されないと考えてよいだろうか?
実はそうとは限らない。以下に紹介するタプル内リストの要素を変更する場合、エラーが出ることなくタプルの中身を変更することができてしまう。
タプル内リストの落とし穴
次のコードではタプルの第一要素であるリストを変更しようとしているが、もちろんこれはエラーが出る。
コード 3
x = ([1, 1], [2, 2], [3, 3]) print(x) x[0] = [9, 9] print(x)
出力 3
([1, 1], [2, 2], [3, 3]) Traceback (most recent call last): File "XXX.py", line 3, in <module> x[0] = [9, 9] TypeError: 'tuple' object does not support item assignment
しかし、次のコードが示すように、タプル内リストの要素を変更することはできる。なぜなら、タプル側が参照しているリスト自体は変わっていないためである。タプルからすると、同じリストを参照していることは保証しているが、リストの中身が同じであることまでは保証してくれないのである。
コード 4
x = ([1, 1], [2, 2], [3, 3]) print(x) x[0][0] = 9 print(x)
出力 4
([1, 1], [2, 2], [3, 3]) ([9, 1], [2, 2], [3, 3])
解決策:タプル内リストを避ける
上で紹介したような奇妙な挙動は、バグを引き起こす原因となりやすい。そのため、プログラムの中ではタプル内リストを避けるのが良い。状況に応じてタプル内タプルかリスト内リストを使えば良いだろう。
変更予定のある変数 --> リスト内リストを使う
変更予定のない変数 --> タプル内タプルを使う
[Python][Matplotlib] 一枚の図に複数のグラフを描く
はじめに
Matplotライブラリを使って、一枚の図に複数のグラフを描きたいときがある。ここでは二つの方法を説明する。
add_subplot
メソッドを使う方法(簡単。単純なレイアウトで)Gridspec
オブジェクトを使う方法(ちょっと面倒。複雑なレイアウトも可能)
1. add_subplot
メソッドを使う
コード 1:
import numpy as np import matplotlib.pyplot as plt # test data x = np.linspace(-5, 5, 100) ys = np.ones(shape=(6, len(x))) * np.nan ys[0] = 1 ys[1] = 1 + x ys[2] = 1 + x + x**2 / 2 ys[3] = 1 + x + x**2 / 2 + x**3 / 6 ys[4] = 1 + x + x**2 / 2 + x**3 / 6 + x**4 / 24 ys[5] = 1 + x + x**2 / 2 + x**3 / 6 + x**4 / 24 + x**5 / 120 # plot fig = plt.figure() axs = [fig.add_subplot(2, 3, i + 1) for i in range(6)] for i in range(6): axs[i].plot(x, ys[i], color='C0', zorder=5) axs[i].plot(x, np.exp(x), color='darkgray', linestyle='dashed', zorder=4) axs[i].set_title('plot {}'.format(i)) fig.tight_layout() # save fig.savefig('multiple_plot1.jpg', dpi=600)
出力 1:
2. Gridspec
オブジェクトを使う
コード 2:
import numpy as np import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec # test data x = np.linspace(-5, 5, 100) ys = np.ones(shape=(6, len(x))) * np.nan ys[0] = 1 ys[1] = 1 + x ys[2] = 1 + x + x**2 / 2 ys[3] = 1 + x + x**2 / 2 + x**3 / 6 ys[4] = 1 + x + x**2 / 2 + x**3 / 6 + x**4 / 24 ys[5] = 1 + x + x**2 / 2 + x**3 / 6 + x**4 / 24 + x**5 / 120 # plot fig = plt.figure() gs = gridspec.GridSpec(nrows=2, ncols=3, figure=fig) for i in range(2): for j in range(3): ax = fig.add_subplot(gs[i, j]) ax.plot(x, ys[3 * i + j], color='C0', zorder=5) ax.plot(x, np.exp(x), color='darkgray', linestyle='dashed', zorder=4) ax.set_title('plot ({}, {})'.format(i, j)) fig.tight_layout() # save fig.savefig('multiple_plot2.jpg', dpi=600)
出力 2:
3. Gridspec
を使えば複雑なレイアウトも可能
Gridspec
を使うと、add_subplot
メソッドでは難しいような複雑なレイアウトも簡単に作ることができる。
コード 3:
import numpy as np import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec # test data x = np.linspace(-5, 5, 100) ys = np.ones(shape=(6, len(x))) * np.nan ys[0] = 1 ys[1] = 1 + x ys[2] = 1 + x + x**2 / 2 ys[3] = 1 + x + x**2 / 2 + x**3 / 6 ys[4] = 1 + x + x**2 / 2 + x**3 / 6 + x**4 / 24 ys[5] = 1 + x + x**2 / 2 + x**3 / 6 + x**4 / 24 + x**5 / 120 # plot fig = plt.figure(figsize=(6.4, 6.4)) gs = gridspec.GridSpec(nrows=4, ncols=3, figure=fig) ax = fig.add_subplot(gs[0:2, 0:2]) ax.plot(x, ys[0], color='C0', zorder=5) ax.plot(x, np.exp(x), color='darkgray', linestyle='dashed', zorder=4) ax.set_title('plot (0-1, 0-1)') ax = fig.add_subplot(gs[0, 2]) ax.plot(x, ys[1], color='C0', zorder=5) ax.plot(x, np.exp(x), color='darkgray', linestyle='dashed', zorder=4) ax.set_title('plot (0, 2)') ax = fig.add_subplot(gs[1, 2]) ax.plot(x, ys[2], color='C0', zorder=5) ax.plot(x, np.exp(x), color='darkgray', linestyle='dashed', zorder=4) ax.set_title('plot (1, 2)') ax = fig.add_subplot(gs[2, 0]) ax.plot(x, ys[3], color='C0', zorder=5) ax.plot(x, np.exp(x), color='darkgray', linestyle='dashed', zorder=4) ax.set_title('plot (2, 0)') ax = fig.add_subplot(gs[3, 0]) ax.plot(x, ys[4], color='C0', zorder=5) ax.plot(x, np.exp(x), color='darkgray', linestyle='dashed', zorder=4) ax.set_title('plot (3, 0)') ax = fig.add_subplot(gs[2:, 1:]) ax.plot(x, ys[5], color='C0', zorder=5) ax.plot(x, np.exp(x), color='darkgray', linestyle='dashed', zorder=4) ax.set_title('plot (2-3, 1-2)') fig.tight_layout() # save fig.savefig('multiple_plot3.jpg', dpi=600)
出力 3:
まとめ
レイアウトの複雑さによって、add_subplot
メソッドを使うシンプルな方法と、Gridspec
オブジェクトを使うやや複雑な方法を使い分ければ良いと思う。
慣れてくればGridspec
オブジェクトを使う方が楽ら上に拡張性も高いので、徐々にGridspec
オブジェクトの方法に乗り換えていっても良いと思う。
[Python][Numpy] 組み込みのboolとNumpy.bool_は別物
Python組み込みのbool型とnumpy.bool_型は別物なので、is
演算子で比較する場合は注意が必要、という話。
環境:Windows10 + Python 3.7 + Numpy 1.18.1
numpy.bool_型とPython組み込みのbool型は別物
Numpyを使って真偽値を判定すると、numpy.bool_
という型になって返ってくるが、実はnumpy.bool_
はPython組み込みのboolとは別の型である。
次のコードを見てもらいたい。
コード1:
import numpy as np x = np.log(10) > 1 # -> True print(x, type(x)) print(x is True) print(x is False)
出力1:
True <class 'numpy.bool_'> False False
3行目でx
に、np.log(10) > 1
の計算結果であるTrueが代入されるのだが、その型は組み込みのbool型ではなくnumpy.bool_
というNumpyで実装されているbool型である。
通常、組み込みbool型に対しては、is
演算子を使って、TrueかFalseかを判定する。もちろん==
演算子を使っても判定はできるのだが、Pythonのコードスタイルではis
演算子を使う方法が推奨されている。
www.flake8rules.com
しかし、numpy.bool_
のTrueと組み込みbool型のTrueは別物なので、is
演算子で比較すると、Falseが返ってきてしまう。つまり、「TrueでもないしFalseでもない」ように見えてしまうのである。困るのは、次のような条件分岐を書いた場合である。
コード2
import numpy as np x = np.log(10) > 1 # -> True if x is True: print('True!') else: print('False!')
出力2
False!
xにはTrueが代入されているので、'True'がprintされてほしいところ、'False'がprintされてしまう。
解決策1:bool関数で型変換する
Python組み込みのbool関数を使って、numpy.bool_型を組み込みのbool型に変換してやればよい。
コード3
import numpy as np x = bool(np.log(10) > 1) # -> True print(x, type(x)) print(x is True) print(x is False)
出力3
True <class 'bool'> True False
解決策2:==
演算子で比較する
is
演算子ではなく、==
演算子を使って比較すれば、期待通りの結果が得られる。
コード4
import numpy as np x = np.log(10) > 1 # -> True print(x, type(x)) print(x == True) print(x == False)
出力4
True <class 'numpy.bool_'> True False
ただし、Pythonの推奨コードスタイルから外れるので、エディタによっては注意が表示されるかもしれない*1。その場合、NOQA
コメントを追加すれば、表示を一時的に消すことができる。
コード5
import numpy as np x = np.log(10) > 1 # -> True print(x, type(x)) print(x == True) # NOQA print(x == False) # NOQA
出力5
(出力4と同じ。省略)
解決策3:if True:
の形で使う
そもそもis
演算子を使わずに、条件式をそのまま使えば問題は起こらない。ただし、and
やor
を多用した複雑な条件式の場合は、is
演算子を使って分かりやすく書きたくなるので悩ましいところではある。
コード6
import numpy as np x = np.log(10) > 1 # -> True if x: print('True!') else: print('False!')
出力6
True!
補足:numpy.bool_()関数の挙動
最後に、numpy.bool_()関数の挙動を調べた結果をメモとして残しておく。なぜかnumpy公式ドキュメントには、numpy.bool_関数の説明ページが見当たらなかった。
import numpy as np print(np.bool_(0)) # -> False print(np.bool_(1)) # -> True print(np.bool_(2)) # -> True print(np.bool_(3)) # -> True print(np.bool_(True)) # -> True print(np.bool_(False)) # -> False
*1:私の環境では "comparison to False should be 'if cond is False:' or 'if not cond:'pycodestyle(E712)" という注意が表示された。