toshiakiasakura / contextplt

Context manager style of matpotlib.
MIT License
0 stars 0 forks source link

Idea to simplify multi plot #17

Closed guneco closed 2 years ago

guneco commented 2 years ago

(日本語で失礼します)

記事でコメントいただいた件について、 こういう解決策もあるのかな…? と思いついたことをシェアします。 https://qiita.com/mochi_gu_ma/items/aea51bb986092783f073

要点は

  1. fig,pltの処理とaxを設定する処理を分ける
  2. axの設定をクロージャに移譲する
  3. コンテキストマネージャにクラスではなく、関数を渡す

とりあえずのアイデアなので、プルリク出せるかたちにはできていません。 ご参考までm( )m

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

class CorePlot():
    def __init__(self):
        self.fig = plt.figure(figsize=(5,3))
        self.ax = None
        self.axes = None

    def __enter__(self):
        return(self)

    def __exit__(self, exc_type, exc_value, exc_traceback):
        plt.show()

def plot(ax_func):
    cp = CorePlot()
    ax = cp.fig.add_subplot(111)

    ax = ax_func(ax)

    cp.ax = ax
    return cp

def multiplot(ax_funcs, indices):
    cp = CorePlot()
    axes = []

    for func, index in zip(ax_funcs, indices):
        ax = cp.fig.add_subplot(index)
        ax = func(ax)
        axes.append(ax)

    cp.axes = axes
    return cp

def set_ax(xlim:list[float]):
    # 今回は簡単のためxlimだけ設定する
    def _set_ax(ax):
        ax.set_xlim(*xlim)
        return ax

    return _set_ax

xx = np.linspace(-5,5,20)
yy = xx*xx

ax0 = set_ax(xlim=[0,5])
ax1 = set_ax(xlim=[0,2])
ax2 = set_ax(xlim=[-5,0])

with plot(ax0) as p:
    p.ax.plot(xx,yy)

with multiplot([ax0, ax1, ax2],[221,222,223]) as p:
    p.axes[0].plot(xx,yy)
    p.axes[1].plot(xx,yy)
    p.axes[2].plot(xx,yy)
toshiakiasakura commented 2 years ago

Sorry for late reply.

Thank you for sharing your ideas. I do not come up with ideas passing function to context manager.

However, it has a caveat. Different from my blog post, I let package to process majority of figure settings in the enter part. See the original code below.

https://github.com/toshiakiasakura/contextplt/blob/6770c4ef7474504647f7fe0a348f6725dc27f088/contextplt/main.py#L84-L92

The reason why I did so comes from pandas plot functionality

Some plot package automatically set x label or y label.

For example Consider the following two classes, one setting xlabel and ylabel in init, and one setting xlabel and ylabel in exit.

class Single1():
    def __init__(self, xlabel : str, ylabel : str):
        self.xlabel = xlabel
        self.ylabel = ylabel
        self.fig = plt.figure(figsize=(5,3),dpi=150)
        self.ax = self.fig.add_subplot(111)
        self.ax.set_xlabel(self.xlabel)
        self.ax.set_ylabel(self.ylabel)

    def __enter__(self):
        return self
    def __exit__(self,exc_type, exc_value, exc_traceback):
        pass

class Single2():
    def __init__(self, xlabel : str, ylabel : str):
        self.xlabel = xlabel
        self.ylabel = ylabel
        self.fig = plt.figure(figsize=(5,3),dpi=150)
        self.ax = self.fig.add_subplot(111)

    def __enter__(self):
        return self
    def __exit__(self,exc_type, exc_value, exc_traceback):
        self.ax.set_xlabel(self.xlabel)
        self.ax.set_ylabel(self.ylabel)

and run the following codess yeilding two figures.

anes96 = sm.datasets.anes96
df = anes96.load_pandas().data
x = "age"
y = "logpopul"
with Single1(xlabel="X label", ylabel="Y label") as p:
    df.plot(x=x,y=y, kind="scatter", ax=p.ax)
with Single2(xlabel="X label", ylabel="Y label") as p:
    df.plot(x=x,y=y, kind="scatter", ax=p.ax)

download download

For the first one, x label and y label are overwritten by pandas functionality, and often we want xlabel/ylabel in parameters of context manager to take precedence over pandas functionality.

Therefore, I think we need to use at least decorators or context manager to creat figrues. Same is applied to Multiple and its component functinos/classes.

Thanks for you suggestions. It is insightful to me.

toshiakiasakura commented 2 years ago

I came up ideas with using class variables to pass Multiple object to MulSingle object before initializing class of MulSingle. Then, writing style is simplified. Look at example. https://toshiakiasakura.github.io/contextplt/notebooks/usage.html#cplt.Multiple

and an applicable part is here. https://github.com/toshiakiasakura/contextplt/blob/6770c4ef7474504647f7fe0a348f6725dc27f088/contextplt/main.py#L170-L178 and here https://github.com/toshiakiasakura/contextplt/blob/6770c4ef7474504647f7fe0a348f6725dc27f088/contextplt/main.py#L202-L211

I'm wondering how class with context manager inner class with context manager works, since this sytle of writing uses class variable so that it causes weired behavior when parallization use of this package (such use case may be rare? but I want to prepare for that.) , or is there any partial initialization of class?

If any further suggestions or questions you have, please let me know.

guneco commented 2 years ago

Thank you for your comment! I did not know how pandas works in that situation. Two examples are very helpful for me to understand it. Thank you.