Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plotting class structure #18

Open
FlavioRyu opened this issue Dec 3, 2021 · 6 comments
Open

Plotting class structure #18

FlavioRyu opened this issue Dec 3, 2021 · 6 comments
Assignees
Labels
enhancement New feature or request plotting related to plotting code

Comments

@FlavioRyu
Copy link
Collaborator

FlavioRyu commented Dec 3, 2021

I was just brainstorming about the use case of our classes before even starting to have a skeleton and I came up with this:

"""
the desired use case:

                        ┌--> this is a boost-histogram object for now
                        |
histo1 = Hist1D(histogram-object, title, fill=True, color)
histo1.make_grid()
histo1.axis.set_xaxis(hide=True)
histo1.axis.set_yaxis(axis-name)
histo1.title_loc(where)
histo1.display() -> this will draw the plot using mplhep

histo2 = PullPlotHist(histogram-object, title, color1, color2) -> the pull plot will be a sideways histogram
histo1.make_grid()
histo2.line.set_linewidth(number)
histo2.marker.set_markersize(number)
histo2.title_loc(where)
histo2.display()

histo3 = CorrMatrix(histogram-object, title, color_palette)
histo3.set_fontsize(number) -> size of numbers inside the corr matrix
histo3.line.set_style('--') -> style of lines that separate each element in the matrix
histo3.display()
"""

This a brief example of some functionalities that I thought might be useful to have for some histogram types. These classes will all inherit from a more general BasicHisto (which contains methods such as make_grid() or display() and attributes like line or axis) class which will take in the histogram object (which is a boost-hist for now) and extrapolate and store all relevant information about it. The idea is to create a subclass for each histogram type.

It is a very basic scratch so let me know if there is anything that I got wrong or that should be changed about the structure in general.

Edit: this does not make use of any canvas or pad structures.

@MoAly98
Copy link
Owner

MoAly98 commented Mar 3, 2022

Can you update this issue @FlavioRyu

@FlavioRyu
Copy link
Collaborator Author

FlavioRyu commented Mar 3, 2022

Yes, I've changed a lot since I posted this original issue so I will try to give a summary (otherwise this would become a documentation) of what is the current situation.

There are 5 classes: EmptyPlot, PythRatio, PythCMatrix, PythPull and PythProjection, where the first is the mother class of the others (can be considered a virtual class). Each individual plot classes store specific rcParams values in form of a dictionary; the user can pass in the constructor (under the argument rcp_kw) a dictionary containing rcParams keys and this will be used to internally update the plot default rcParams.

The most important function is the display() function which is present in all child classes. This effectively draws the entire plot by calling plotting functions such as matpltolib.errorbar(), matpltolib.imshow() and mplhep.histplot() (where, btw, the last one is a helper for matpltolib.stairs() and matpltolib.errorbar()).

EmptyPlot

Class storing basic attributes such as figure size, master title (or figure title), positioning of subplots present in the figure, etc. Member functioins are functions that are common to all other plot classes such as create_canvas(), make_subplot(), set_color(), save_image() etc. to name a few.

Titling system

The base class also stores two dictionaries (called xtitles_dict and xtitles_dict) to store x and y titles (or labels) of any subplot that might be present in the plot. These titles are set by the user calling the function axes_labels(). Below is an explanation of these two dictionaries (taken from source code)

        # set default dict of axes titles
        self.xtitles_dict = {
            "xmain" : '',
            "xtop"  : '',
            "xbot"  : '',
            "xleft" : '', # not used
            "xright": ''
        }
        self.ytitles_dict = {
            "ymain" : '',
            "ytop"  : '',
            "ybot"  : '',
            "yleft" : '', # not used
            "yright": ''
        }
        """
        The terms 'main', 'top', 'bot' etc. refer to the follownig subplot scheme:
        
                 -------- 
                |  top   |
                 --------        
         -----   --------   -----
        |     | |        | |     |
        |left | |  main  | |right|
        |     | |        | |     |
         -----   --------   -----
                 -------- 
                |  bot   |
                 --------
        
        And each plot type (ratio, pull, projection, corrm) will be a combination of
        these subplots (Ax objects). For example, the ratio plot will be made of main
        and bot subplots and the projection plot will be made of main, top and right
        subplots. The left subplot is currently not used and might be deleted in the 
        future if no use cases are found.
        
        The space between the subplots is called 'spacing' throughout all classes
        """

Font sizes

As matplotlib makes a lot of confusion in the rcParams that control font sizes of each element in the plot, I decided to standardise them in the following way (example from pull plot):

  • Master titles = matplotlib.rcParams['axes.titlesize']
  • Axis tick labels (numbers and strings) = matplotlib.rcParams['font.size']
  • Axis titles (labels) = internal fontsize attribute, which is passed in as first argument in axes_labels() function

PythRatio

Use case example:

my_dict={
    'axes.titlesize': 20,
    'font.size': 11
}

obj = PythRatio(hist_list, size=(6,8), title='Master Title', spacing=0, rcp_kw=my_dict)
obj.set_stack_color(reverse=True, colormap='gist_rainbow')
obj.axes_labels(15, xbot=xaxis_label, ymain='Events', ybot='Data/Pred.')
obj.set_bot_yaxis([0.5, 1.5], 0.25, edges=False) # strongly recommend user to do this
obj.plot_errors(hatch='/////', alpha=0.2)
obj.display()

set_bot_yaxis is recommended because if user doesn't call this, the y axis of the ratio plot (the bottom subplot) will be drawn automatically by matplotlib but this might not always be the desired output. For instance, axis tick labels might overlap to other parts of the plot but this can be avoided by specifying a custom y axis range and the edges boolean.

Output of example:

PythCMatrix

Use case example:

obj = PythCMatrix(data, 0.001, title='Master Title')
obj.set_color(colormap='bwr')
obj.display(set_cbar=False)

In the constructor, the float after the data is the threshold value for which the data will be cut. In the display() function, it is possible to specify whether a colorbar on the side is wanted or not. The figure size changed automatically: figure side length (in inches) is len(data)/3.

Output of example:

PythPull

Use case example:

my_dict={
    'axes.titlesize': 15
}

hist1 = PythPullPlot(data0.iloc[0:40], title='Master Title', rcp_kw=my_dict)
hist1.set_rangex([-6, 11])
hist1.axes_labels(12, xmain=r'$(\hat{\theta}-\theta)/\Delta\theta$')
hist1.display()

set_rangex() can be called by user to impose a custom x range for the plot. The figure size of the pull plot changes according to the length of nuisance parameters (n) to be plotted: figsize = (2, 0.4012*n**(0.763)) (this formula was empirically derived).

Output of example:

PythProjection

Use case example:

my_dict={
    'axes.titlesize': 20,
    'xaxis.labellocation': 'right',
    'yaxis.labellocation': 'top',
}

plot = PythProjection(size=(6,6), title='Master Title', spacing=0.08, rcp_kw=my_dict)
plot.fill(hist)
plot.axes_labels(15, ymain=r'$p_y$ [MeV]', xmain=r'$p_x$ [MeV]', xright='righttitle', ytop='toptitle')
plot.put_grid()
plot.set_color(colormap='binary')
plot.display()

Output of example:

@MoAly98
Copy link
Owner

MoAly98 commented Jun 28, 2022

@jkwinter Can you review this issue and give your updates on what changed/what needs to change?

@MoAly98 MoAly98 added enhancement New feature or request plotting related to plotting code labels Jun 28, 2022
@MoAly98
Copy link
Owner

MoAly98 commented Jun 28, 2022

How will we provide plotting classes to users?
TODO:

  • Config structure
  • Decision on API and stirring
  • Histogram Manager for Plotting
  • Documentation review
  • mk-corr-like excutables to allow pre-fit/post-fit standard plots for a given fit config

@jkwinter
Copy link
Collaborator

In terms of structure, Flavio has documented this in the plotting readme.
In terms of functionality

  • log scales and limits for axes in 1D plot
  • added options for legend placement using matplotlib legend.loc strings
  • added colorbar to ProjectionPlot, with a logz bool and limits passed to plot_options
  • CMatrixPlot and PullPlot remain as described by Flavio in the readme.

Open points:

  • Check handling of errors in ratio plot
  • Custom x, y axes limits in ProjectionPlot
  • set_explabel() equivalent for ProjectionPlot, CMatrixPlot, PullPlot
  • pull plot data currently parsed from a .txt file, are other input types required?
  • threshold function for suppressing CMatrixPlot bins isn't working as described.
  • Labels, legends, markers do not scale with figure size
  • Rescaling of y axis with legend size to remove overlaps (and fix issue of placing the legend) is performed iteratively in mplhep using mplhep.plot.yscale_legend(ax) and takes a long time
  • CMatrixPlot figure size does not scale with number of entries
  • CMatrixPlot entries expected in decimal format and converted into percentages, no checks
  • Option to print contents of boosted histogram as a pandas dataframe not fully implemented
  • Change name of class Hist1D to Hist1DPlot, ProjectionPlot to Hist2DPlot
  • Option to disable projections in ProjectionPlot
  • Some clean-up required
  • update docstrings and readme
  • check_input() only implemented for Hist1D
  • Can placement of label be optimised? Currently uses mplhep.plot.yscale_text(ax)
  • ProjectionPlot colorbar normalisation options https://matplotlib.org/stable/tutorials/colors/colormapnorms.html (only colors.Normalize implemented currently)

@jkwinter
Copy link
Collaborator

jkwinter commented Jul 25, 2022

Plotting to-do list
General

  • Clean-up, docstrings and README
  • Missing axes name and label from boosthistogram objects, used in plotting
  • modules pandas and hist used in plotting. Remove pandas; add hist to required packages?
  • Add option to add multiple entries to PullPlot
  • Style of plots needs to be more flexible; currently only allows for ATLAS, LHCb or CMS
  • Figure sizes inconsistent when producing multiple plots
  • Missing experimental labels for CMatrixPlot, PullPlot, ProjectionPlot
  • labels, legends, markers do not scale with figure size
  • Rescaling of y axis with legend size/experiment label is time consuming. Can this be improved upon?
  • Option to print contents of boostedhistogram as pandas dataframe not implemented
  • check_input only implemented for Hist1D
  • Create directories if images saved

Hist1D

  • Change class name to Hist1DPlot?

RatioPlot

  • Handling of errors in ratio plot needs checking

PullPlot

  • What format are inputs expected in? Add txt parser from Flavio's dev branch to main
  • No option to plot multiple pull plots on same canvas

ProjectionPlot

CMatrixPlot

  • Incorrect threshold function for supressing bins
  • Figure size doesn't scale with number of entries
  • Missing checks on input format (fractional vs percentual)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request plotting related to plotting code
Projects
None yet
Development

No branches or pull requests

3 participants