俺言語。

自分にしか理解できない言語で書かれた備忘録

【Python】numpy ndarrayとmatrixの特徴 覚書き

MATLABからの移行組にはとっつき辛いところが多々あるnumpyの配列,

ndarrayの気付いた点まとめ。


numpyは基本、横ベクトル

xx.shape -> (n, ) はベクトル

x.shape -> (n,m) は配列

array([1, 2, 3])やarange(10)で作成されるのはベクトル

ベクトルは標記こそ横ベクトルだが向きは無い。

よってベクトル(1×N)を転置をしてもMATLABの様にN×1ベクトルにはならない。

ベクトルをイテレータとして使うと数値が一つずつ取り出せる。

e.q. np.range(3) -> 最初:1, 次:2, 次:3

配列をイテレータとして使うと 1×Nのベクトルが取り出される

e.q. np.range([[1, 2, 3], [4, 5, 6]]) -> 最初:[1, 2, 3], 次:[4, 5, 6]

よって配列をスライスする際はベクトルでスライスするか配列(1*N)でスライスするかで

挙動が変わるので注意

data = np.array([[1, 2, 3],[4, 5, 6],[7, 8, 9]])

index1 = np.array([[0, 1, 2]])

data[index1, 0])

-> array([[1, 4, 7]]) # 1*3配列


index2 = np.array([0, 1, 2]) 

data[index2, 0])

-> array([1, 4, 7]) # ベクトル

【Python】listやndarrayの中身をエクセルに張り付けるためにクリップボードへコピー

listやndarrayの中身をエクセルに張り付けて検証や検討をしたい場合に有効。


肝になるのが

・リスト内容標記を使って数値を文字列にして末尾に改行コード\nを追加

・"".join(データ)で文字を結合

import pyperclip

def copy(data):
    temp = [str(i) + "\n" for i in data]
    pyperclip.copy("".join(temp))

    print("data was copied in clipboard")

クリップボードコピー用のモジュール、pyperclipを使って関数化し
リストからクリップボードコピーまで1行で処理可能にした。

【Python】グラフ枠(Spines)の色・太さ変更

グラフ枠(Spines)を一度描画した後に変更する方法。


グラフ枠は

ax.Spines{"Bottom"}
ax.Spines{"Top"}
ax.Spines{"Left"}
ax.Spines{"Right"}

に枠線のインスタンスがあり,

色変更は ax.Spines{"**"}.set_color(色)

太さ変更は ax.Spines{"**"}.set_linewidth(太さ)

で変更する。

問題は枠線(Spines)がAXESエリアのぎりぎり外側にいるため,

背景を保存する際や再描画エリアを指定する際に使う

ax.bboxでは範囲に含まれないこと。


解決方法として

ax.bbox.extended(縦?倍率,横?倍率)でax.bboxの範囲を少し広げてやる。

 問題点はAxesサイズが変わると拡張される面積も変わるためプログラム毎で調整が必要になってしまう。


ax.xaxis.get_tightbbox(ax.get_renderer_cache())でtickラベルを含むaxisのbboxを取得し,

 ax.bbox.union([結合したいbbox, 結合したいbbox])でbboxをつなげる。

 注意点はax.xaxis.get_tightbbox()がほんとにtightでtickラベルぎりぎりのbboxを返す。
 
 そのためtickラベルの桁数が変わる場合など簡単にエリアをはみ出すので注意。



グラフをダブルクリックしてグラフ枠を赤&太く -> 黒&細くを繰り返す。

def event_datacheck(self, event):
    if event.dblclick:

        if event.inaxes.spines["bottom"].get_edgecolor()[0] == 0:

            # 赤線を引く前にbgを保存
            # キーをイベントのaxesインスタンスにしてクリックされたaxesにbgを引き当て
            # key:axインスタンス value:bg
            self.ax_dict[event.inaxes] = event.canvas.copy_from_bbox(event.inaxes.bbox.expanded(1.1, 1.1))

            event.inaxes.spines["bottom"].set_color("red")
            event.inaxes.spines["top"].set_color("red")
            event.inaxes.spines["left"].set_color("red")
            event.inaxes.spines["right"].set_color("red")

            event.inaxes.spines["bottom"].set_linewidth(2)
            event.inaxes.spines["top"].set_linewidth(2)
            event.inaxes.spines["left"].set_linewidth(2)
            event.inaxes.spines["right"].set_linewidth(2)

            print("Axes was SELECTED")

        else:
            # イベント内のaxesに対応するbgを使って画面をbgに戻す
            event.canvas.restore_region(self.ax_dict[event.inaxes])

            event.inaxes.spines["bottom"].set_color("black")
            event.inaxes.spines["top"].set_color("black")
            event.inaxes.spines["left"].set_color("black")
            event.inaxes.spines["right"].set_color("black")

            event.inaxes.spines["bottom"].set_linewidth(0.5)
            event.inaxes.spines["top"].set_linewidth(0.5)
            event.inaxes.spines["left"].set_linewidth(0.5)
            event.inaxes.spines["right"].set_linewidth(0.5)

            print("Axes was RELEASED")

        event.inaxes.draw_artist(event.inaxes.spines["bottom"])
        event.inaxes.draw_artist(event.inaxes.spines["top"])
        event.inaxes.draw_artist(event.inaxes.spines["left"])
        event.inaxes.draw_artist(event.inaxes.spines["right"])

        # これで更新
        event.inaxes.figure.canvas.blit(event.inaxes.bbox.expanded(1.2, 1.2))
        # event.inaxes.figure.canvas.update() <- wxaggだと動かない

【Python】matplotlibの手動で描画更新

matplotlibでアニメーションほど早い更新周期ではないものの,

イベント(たとえばグラフをダブルクリック)でグラフを更新(変化)させたい場合など,

グラフ更新の関数を実行してあげる必要がある。


最も一般的なのは 

canvas.draw()

しかしこれがすごく遅い。

Figureクラスの中の関数のためかfigure全体を更新してしまうためらしい。


色々試した結果,

bg = canvas.copy_from_bbox(self.ax.bbox) # bboxのエリアを背景(というか戻したい状態)として保存

canvas.restore_region(bg) # 戻したい状態へ描画

ax.draw_artist(artist) # 描画したいartist(LineやPatch)を登録

canvas.blit(ax.bbox) # bboxのエリアだけ描画更新 以降②へ戻る

※ ④ canvas.blitよりcanvas.update()の方がメモリ使用が少なく早い情報もあるが
   ケースによるのとqt4aggでしか動かなかった。


でやると更新したい場所のみ変更するのでmatplotlibとは思えないほど早い。

bboxのエリアを前に保存した状態に戻す手順とその効果はペイントみたいに感じる。


参考にしたサイトにあったcanvas.draw()と他のもっと早い手順との速度比較を試してみた。

import matplotlib.pyplot as plt
import numpy as np
import time

from scipy.stats._continuous_distns import t_gen


def case1():

    fig, ax = plt.subplots()

    t_start = time.time()
    num_plot = 0

    while time.time() - t_start < 1:
        ax.clear()
        ax.plot(np.random.randn(100))
        plt.pause(0.001)

        num_plot += 1

    return num_plot


def case2():

    fig, ax = plt.subplots()
    line, = ax.plot(np.random.randn(100))

    t_start = time.time()
    num_plot = 0

    while time.time() - t_start < 1:
        line.set_ydata(np.random.randn(100))
        plt.pause(0.001)

        num_plot += 1

    return num_plot


def case3():

    fig, ax = plt.subplots()
    line, = ax.plot(np.random.randn(100))
    fig.canvas.draw()
    fig.show()

    t_start = time.time()
    num_plot = 0

    while time.time() - t_start < 1:
        line.set_ydata(np.random.randn(100))
        fig.canvas.draw()
        fig.canvas.flush_events() # <-これがないと画面に描画されない。

        num_plot += 1

    return num_plot

def case4():

    fig, ax = plt.subplots()
    line, = ax.plot(np.random.randn(100))
    fig.canvas.draw()
    fig.show()

    t_start = time.time()
    num_plot = 0

    while time.time() - t_start < 1:
        line.set_ydata(np.random.randn(100))

        ax.draw_artist(ax.patch)
        ax.draw_artist(line)
        #fig.canvas.update()
        fig.canvas.blit(ax.bbox)

        fig.canvas.flush_events()
        num_plot += 1

    return num_plot

def case5():

    fig, ax = plt.subplots()
    fig.canvas.draw()

    bg = fig.canvas.copy_from_bbox(ax.bbox)

    line, = ax.plot(np.random.randn(100))
    fig.show()

    t_start = time.time()
    num_plot = 0

    while time.time() - t_start < 1:
        line.set_ydata(np.random.randn(100))

        fig.canvas.restore_region(bg)
        ax.draw_artist(line)

        #fig.canvas.update()
        fig.canvas.blit(ax.bbox)

        fig.canvas.flush_events()
        num_plot += 1

    return num_plot


def case6():

    fig, ax = plt.subplots()
    fig.canvas.draw()

    bg = fig.canvas.copy_from_bbox(ax.bbox)

    line, = ax.plot(np.random.randn(100))
    fig.show()

    t_start = time.time()
    num_plot = 0

    while time.time() - t_start < 1:
        line.set_ydata(np.random.randn(100))

        fig.canvas.restore_region(bg)
        ax.draw_artist(line)

        fig.canvas.update()

        fig.canvas.flush_events()
        num_plot += 1

    return num_plot


if __name__ == "__main__":
    print("case1: " + str(case1()) + "fps")
    print("case1: " + str(case2()) + "fps")
    print("case3: " + str(case3()) + "fps")
    print("case4: " + str(case4()) + "fps")
    print("case5: " + str(case5()) + "fps")
    print("case6: " + str(case6()) + "fps")

Case1: 最も原始的。Lineを書いては消して。Pauseを使うことで画面を更新。

Case2: Case1からLineをいちいち消すのではなくLine1のデータを書き換えて更新

Case3: Case2 + canvas.draw() これが基準になる

Case4: 背景を白で描画する(ax.draw_artist(ax.patch))ことで前の描画をクリア。再描画はcanvas.blit()を使う

Case5: Case4から前描画クリアをcanvas.restore_region()に変更

Case6: Case5から再描画をcanvas.update()に変更


いろんなケースを試したけど要は

Case3基準に対してCase5がどれだけ速いか。
Case5とCase6どっちが速いか。

結果は

case1: 5fps

case1: 7fps

case3: 44fps

case4: 227fps

case5: 218fps

case6: 183fps

Case3に対してCase5は約5倍速い。

Case5よりCase6の方が約1.2倍速い結果となった。

意外にCase4が遅くない。。

Speeding up Matplotlib

【Python】win32clipboardが無いと言われる

新しい環境にpipでpywin32をインストールした時,過去に動いていたプログラムで

ImportError: DLL load failed when importing win32clipboard とエラーが発生。
(実際は日本語のエラー)

.dllにパスが通っていなのか、などいろいろ試したが

exe版のpywin32でインストールすると動くようになった。

【Python】Pycharm + wx3 でエラー

PycharmのPython Console使用時に発生したエラー。

おそらくbackednをpyqt4からwxに変更したことも関係あると思われる。

C:\Program Files (x86)\JetBrains\PyCharm Community Edition 2017.1\helpers\pydev\pydev_ipython\inputhookwx.py

def inputhook_wx3():
    """Run the wx event loop by processing pending events only.

    This is like inputhook_wx1, but it keeps processing pending events
    until stdin is ready.  After processing all pending events, a call to
    time.sleep is inserted.  This is needed, otherwise, CPU usage is at 100%.
    This sleep time should be tuned though for best performance.
    """
    # We need to protect against a user pressing Control-C when IPython is
    # idle and this is running. We trap KeyboardInterrupt and pass.
    try:
        app = wx.GetApp()  # @UndefinedVariable
        if app is not None:
            assert wx.IsMainThread()  # @UndefinedVariable

            # The import of wx on Linux sets the handler for signal.SIGINT
            # to 0.  This is a bug in wx or gtk.  We fix by just setting it
            # back to the Python default.
            if not callable(signal.getsignal(signal.SIGINT)):
                signal.signal(signal.SIGINT, signal.default_int_handler)

            evtloop = wx.GUIEventLoop()  # @UndefinedVariable
            ea = wx.EventLoopActivator(evtloop)  # @UndefinedVariable
            t = clock()
            while not stdin_ready():
                while evtloop.Pending():
                    t = clock()
                    evtloop.Dispatch()
                #app.ProcessIdle()
                wx.WakeUpIdle()

wx3になって
wx.Thread_IsMain() が wx.IsMainThread に変更されているのに
プログラム内ではwx2のままが原因。

ほかにも
wx.EventLoop() が wx.GUIEventLoop() を使うよう推奨されていたので変更した。