12A猫で学んだこと-Memoir-

...What are you learning now?

折れ線グラフのAxesからデータを復元したい

状況

matplotlib, plot関数を使用して折れ線グラフを作成した。
plot関数の対象となったAxes オブジェクトから、折れ線グラフの作成に使われた データを抽出したい。

""" ## Purpose   
This is a problem related to [matplotlib](https://matplotlib.org/)
As you know, you can make a figure of line graph with such as the following functions.  

> fig, (ax) = plt.subplots(1, 1)  
> ax.plot([1, 2, 3], [4, 5, 6])

Here, I'd like the function to read the representation of the data 
from given ``Axes`` class. 


### Environment  
* matplotlib==3.0.3    
* pandas==0.24.2    

"""

import warnings
from collections import defaultdict 
import random

import matplotlib
import matplotlib.pyplot as plt 
from matplotlib.axes._axes import Axes
import pandas as pd

def read_plot(ax, *, fill=None): 
    """ Speculate the data of plotted ``Axes``. 
    Args:
        ax(Axes): The target ``Axes`` of read.
        fill(Number): The value for empty ``X``.  

    Return: 
        ``dict`` of ``list`` format of the estimated data. 
    """

    assert isinstance(ax, Axes), "Given Object must be ``Axes`` "


    """ If ``xaxis.label`` is existent, 
    then it is regarded as label. 
    Otherwiser, ``X`` is used. 
    """
    x_text = ax.xaxis.label.get_text()
    if x_text:
        xlabel = x_text
    else:
        warnings.warn("``label`` of ``X`` is not specified. ")
        xlabel =  "X"
    
    if len(ax.lines) == 0:
        raise ValueError("There is no ``Line2D`` given ``Axes``.")

    # Construct the data dependent on ``X``.  
    x_to_data = defaultdict(dict) 

    """
    ### As for the label of ``Y``.
    * Priority 1.
    If the ``label`` is set at ``line``, 
    then it is regarded as label of ``Y``. 
    """
    labeled_lines = [line for line in ax.lines if line._label]
    if labeled_lines:
        for line in labeled_lines:
            label = line._label
            for x, y in zip(*line.get_data()):
                x_to_data[x][label] = y
    else:
        if len(ax.lines) == 1:
            """
            ### As for the label of ``Y``.
            * Priority2.
            As long as the ``label`` of ``yaxis`` exists.
            it is regarded as the column of ``Y``. 
            """
            label =  ax.yaxis.label.get_text()
            if label:
                for x, y in zip(*ax.lines[0].get_data()):
                    x_to_data[x][label] = y

    if not x_to_data:
        raise ValueError("Cannot read any semantic data.")


    listmap = []
    x_values = x_to_data.keys()
    columns = {c for x in x_values for c in x_to_data[x].keys()} 

    for x in sorted(x_to_data.keys()):
        row = dict()
        row[xlabel] = x
        for c in columns:
            row[c] = x_to_data[x].get(c, fill)
        listmap.append(row)

    return listmap


if __name__ == "__main__":
    """ Example. 
    1. Plot an artificial data to ``in_ax``. 
    2. Using ``read_plot``, the target data is given from ``in_ax``. 
    3. Read data is plotted to ``out_ax``. 
    """

    fig, (in_ax, out_ax) = plt.subplots(1, 2)
    func = lambda x: 3 * x + 1 + random.random() * 10

    x_values = range(10)
    y_values = tuple(map(func, x_values))
    in_ax.plot(x_values, y_values, "-*", color="C0", label="LINE_SAMPLE1")
    y_values = tuple(map(func, x_values))
    in_ax.plot(x_values, y_values, "-*", color="C1", label="LINE_SAMPLE2")
 
    in_ax.set_title("This is ``in_ax``. ", fontsize=16)
    in_ax.set_xlabel("X_LABEL")
    in_ax.set_ylabel("Y_LABEL")

    in_ax.legend()
    in_ax.tick_params(labelsize=12)
    

    """ Read the contents of ``in_ax``, 
    and plot to ``out_ax``. 
    """
    listmap = read_plot(in_ax)

    xlabel = in_ax.get_xlabel()
    df = pd.DataFrame.from_records(listmap)
    df.plot(ax=out_ax, x=xlabel, style="-o")

    out_ax.set_title("This is read from ``in_ax``.", fontsize=16)
    out_ax.tick_params(labelsize=12)

    fig.tight_layout()
    plt.show()  # Display


""" Comment
* Return of ``read_plot`` may be preferable if the type is ``Dataframe``.   

"""

ユースケース

見当たらない。

作成動機1

JSON, YAML, CSV なんでもいいから、テキスト形式でデータをください... 再利用可能な形でデータを提供できないのは、貴方が騙りだからですよ!」
という知人の呟きを聞いたから

作成動機2

逆変換ができることって、大切だと思うんだ。

いつでも恋人同士に戻れる夫婦はいいなーって、学校の先生が言っていたし、 別れた後も友達に戻れたカップルの先輩たちはどっちもいい人だった。

安心できるから、ちょっと怖くても一歩を踏み出せるんだよね!