-1

I have functions or methods in a class that produce plots with matplotlib. I would like to use that functions in various contexts. I never can be sure about what the function is supposed to return. I will be use that function essentially in jupyter notebooks but also in widgets or qt app for example.

Here is a minimal example:

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

# some interesting data
df = pd.DataFrame({c: np.random.randn(10) for c in "ABC"})

Here is the function. I used to use as optional argument the matplotlib axes and I return it. But sometimes (like in the example) I have a twin axes or other things and thus returning only one axes is strange and I return the whole figure object.

def make_fake_plot(
    df: pd.DataFrame,
    col_x: str,
    col_y: str,
    show_mean: bool = True,
    ax: plt.Axes = None
):
    """ make a plot
    
    Args:
        df (pd.DataFrame): The data
        col_x (str): name of col for x
        col_y (str): name of col for y
        show_mean (bool): an option
        ax (plt.Axes): axes on which make the plot
    """
    
    if ax is None:
        fig, ax = plt.subplots()
        # What is best here ?
        # ax = plt.subplot(1, 1, 1)
    
    ax = df.plot.scatter(x=col_x, y=col_y, marker="d", s=20, ax=ax)
    if show_mean:
        ax2 = ax.twiny()
        ax2.set_xlim(ax.get_xlim())
        mu = df[col_x].mean()
        ax2.axvline(mu, color="C3")
        ax2.set_xticks([mu])
        ax2.set_xticklabels(["mean"])
        
    # is something is supposed to be returned ??
    # return ax
    # return fig
    # return fig, ax
    # nothing ??

This function work quite well in a jupyter notebook. I can even put the plots in subplots while the function does not return anything (that is a point also I do not undersdant).

And here is an example using that function in widgets. But here it dos not work, the plot is not updated. The problem is that if I return something from the function, in the notebook, sometimes figures appear two times, and in the widget, instead of update the figure, the plots stack again and again one below the previous one.

import ipywidgets as ipw
from IPython.display import display

def show_nice_app(dataset):
    
    output = ipw.Output()

    dropdown_xcol = ipw.Dropdown(
        options=dataset.columns,
        value=dataset.columns[0],
        description='X col:')
    dropdown_ycol = ipw.Dropdown(
        options=dataset.columns, 
        value=dataset.columns[1],
        description='Y col:')

    def dropdown_xcol_eventhandler(change):
        with output:
            output.clear_output(wait=True)
            make_fake_plot(dataset, col_x=change.new, col_y=dropdown_ycol.value)
            # display(fig)

    def dropdown_ycol_eventhandler(change):
        with output:
            output.clear_output(wait=True)
            make_fake_plot(dataset, col_x=dropdown_xcol.value, col_y=change.new)
            # display(fig)
            
    dropdown_xcol.observe(dropdown_xcol_eventhandler, names='value')
    dropdown_ycol.observe(dropdown_ycol_eventhandler, names='value')
    
    input_widgets = ipw.HBox([dropdown_xcol, dropdown_ycol])
    all_widgets = ipw.VBox([input_widgets, output])
    
    display(all_widgets)
    with output:
        output.clear_output(wait=True)
        make_fake_plot(dataset, col_x=dropdown_xcol.value, col_y=dropdown_ycol.value)

At the end, these functions (widget and plots functions) are not supposed to be in the notebook. They are supposed to be in a library (in classes or simply in module) which will be imported in the notebook.

Ger
  • 9,076
  • 10
  • 37
  • 48
  • 1
    If ultimately the code producing the plot isn't going to be inside the notebook and will in instead imported, you are better off working like that now. They changed the way modern Jupyter handles matplotlib plot objects. Now if Jupyter detects a plot object being generated in a cell it tries to display it. This is contributing to the doubling you are seeing. You can suppress that handling by clearing the active plot object **after you assigned it/returned it**. However given you'll ultimately want to develop this as an imported function, you should be already developing it as such so the ... – Wayne May 14 '23 at 18:55
  • display handing matches your needs. Going back to the suppressing by clearing active object, see [here](https://stackoverflow.com/a/75488973/8508004) for one way. – Wayne May 14 '23 at 19:16
  • I did not understand what you mean by " you should be already developing it as such so the handling matches your needs". Do you mean that I have to manage what is returned? Following your link, do you mean I should close first the fig and return it ? – Ger May 15 '23 at 12:37
  • 1
    I mean if you are going to be ultimately importing the function, then move it to a file and import now. You'll maybe see if the handling of the generated plot is different then whether you call an import or run the code in the notebook. Otherwise you could end up designing in things that you don't need to code for. And then by also moving it to an import as you intend, you can check yourself if you, "should close first the fig and return it." Another good idea is to look at how other projects based on matplotlib handle things. – Wayne May 15 '23 at 15:14
  • 1
    @Wayne That is exactly what I did. The function in the question is just a toy to give an example. I already have functions in a module/class that I import. What I would like to know and understand is what is the best practice in order to make the library easy to use by people at the end and avoid playing with ; or other tricks to avoid duplicate outputs. Using close() and returning nothing in the plot function seems to do the job. Do you have suggestion of middle size project on which I could look into? – Ger May 15 '23 at 21:27
  • I posted [some resources you can use as a guide in a gist here](https://gist.github.com/fomightez/4bb73cca0e1becefa5cc821365075448) since someone commented a list of resources isn't welcomed. – Wayne May 16 '23 at 04:07

1 Answers1

0

You can mine the examples in the gist I included in a comment to see how others approach building plot tools on top of Matplotlib and use some of those as a guide to implement what will work for you.

The list was here but someone pointed out it wasn't welcome on StackOverflow.

Wayne
  • 6,607
  • 8
  • 36
  • 93
  • Hello, and welcome to Stack Overflow. Unfortunately, a mere collectioneof links to off-site solutions is not an acceptable answer here; at the very least, please summarize what’s behind the links, in case it stops working, or visitors are unable or unwilling to click through. – tripleee May 16 '23 at 03:47