Repository navigation
Add marginals_plot #618
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
Add marginals_plot #618
Changes from all commits
af91fd4
d0a89a4
9060ecf
7fc2473
ac39160
eadc040
d45ddbf
17358a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| { | ||
| "cells": [ | ||
| { | ||
| "cell_type": "markdown", | ||
| "id": "0", | ||
| "metadata": {}, | ||
| "source": [ | ||
| "# Marginals plot\n", | ||
| "\n", | ||
| "The marginals plot shows a 2-dimensional image together with the 1-dimensional lines obtained from summing over each dimension." | ||
| ] | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "execution_count": null, | ||
| "id": "1", | ||
| "metadata": {}, | ||
| "outputs": [], | ||
| "source": [ | ||
| "import plopp as pp" | ||
| ] | ||
| }, | ||
| { | ||
| "cell_type": "markdown", | ||
| "id": "2", | ||
| "metadata": {}, | ||
| "source": [ | ||
| "## Basic marginals plot\n", | ||
| "\n" | ||
| ] | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "execution_count": null, | ||
| "id": "3", | ||
| "metadata": {}, | ||
| "outputs": [], | ||
| "source": [ | ||
| "da = pp.data.data2d()\n", | ||
|
nvaytet marked this conversation as resolved.
Resolved
|
||
| "pp.marginals(da)" | ||
| ] | ||
| }, | ||
| { | ||
| "cell_type": "markdown", | ||
| "id": "4", | ||
| "metadata": {}, | ||
| "source": [ | ||
| "## Control the side of the vertical plot\n", | ||
| "\n", | ||
| "Above, the 1D plot as a function of `y` is placed on the left and its data axis increases from right to left.\n", | ||
| "We can alternatively show it on the right. This also results in the axis increasing from left to right as is the more common case:" | ||
| ] | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "execution_count": null, | ||
| "id": "5", | ||
| "metadata": {}, | ||
| "outputs": [], | ||
| "source": [ | ||
| "pp.marginals(da, side=\"right\")" | ||
| ] | ||
| }, | ||
| { | ||
| "cell_type": "markdown", | ||
| "id": "6", | ||
| "metadata": {}, | ||
| "source": [ | ||
| "## Extra arguments\n", | ||
| "\n", | ||
| "`marginals` supports some additional arguments like `title`. It also forward any extra arguments to the underlying plot functions, in this example, `grid`. Note that the arguments are passed to *all* three subplots." | ||
| ] | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "execution_count": null, | ||
| "id": "7", | ||
| "metadata": {}, | ||
| "outputs": [], | ||
| "source": [ | ||
| "pp.marginals(da, title=\"My plot\", grid=True)" | ||
| ] | ||
| } | ||
| ], | ||
| "metadata": { | ||
| "kernelspec": { | ||
| "display_name": "Python 3", | ||
| "language": "python", | ||
| "name": "python3" | ||
| }, | ||
| "language_info": { | ||
| "codemirror_mode": { | ||
| "name": "ipython", | ||
| "version": 2 | ||
| }, | ||
| "file_extension": ".py", | ||
| "mimetype": "text/x-python", | ||
| "name": "python", | ||
| "nbconvert_exporter": "python", | ||
| "pygments_lexer": "ipython2", | ||
| "version": "2.7.6" | ||
| } | ||
| }, | ||
| "nbformat": 4, | ||
| "nbformat_minor": 5 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,7 @@ | |
| 'plotting': [ | ||
| 'DimensionSlicer', | ||
| 'inspector', | ||
| 'marginals', | ||
| 'mesh3d', | ||
| 'plot', | ||
| 'scatter', | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| # SPDX-License-Identifier: BSD-3-Clause | ||
| # Copyright (c) 2026 Scipp contributors (https://github.com/scipp) | ||
|
|
||
| from functools import partial | ||
| from typing import Any, Literal | ||
|
|
||
| import matplotlib.pyplot as plt | ||
| import scipp as sc | ||
| from mpl_toolkits.axes_grid1 import make_axes_locatable | ||
|
|
||
| from ..backends.matplotlib.utils import make_figure | ||
| from ..core import Node | ||
| from ..core.typing import FigureLike | ||
| from ..graphics import imagefigure, linefigure | ||
| from .common import input_to_nodes, preprocess | ||
|
|
||
|
|
||
| def marginals( | ||
| data: sc.DataArray | Node, | ||
| *, | ||
| side: Literal["left", "right"] = "left", | ||
| title: str = "", | ||
| cbar: bool = True, | ||
| ax: plt.Axes | None = None, | ||
| ignore_size: bool = False, | ||
| ignore_sorted: bool = False, | ||
| coords=None, | ||
| **plot_kwargs: Any, | ||
| ) -> FigureLike: | ||
| """Plot 2D data and the sums over each dim as a 1D plot. | ||
|
|
||
| This function plots ``data`` as a 2D image with a colorbar. | ||
| It also sums over each of its dimensions and plots the resulting 1D data | ||
| next to and above the main image. | ||
|
|
||
| .. versionadded:: 26.11.0 | ||
|
|
||
| Parameters | ||
| ---------- | ||
| data: | ||
| The data to plot. Must be 2-dimensional. | ||
| side: | ||
| Which side to show the 1D plot along the y-axis. | ||
| Note that when ``side == "left"``, the x-axis of the side plot is inverted, | ||
| i.e., it increases from right to left. | ||
| title: | ||
| Title of the plot. | ||
| cbar: | ||
| Whether to use a colorbar. | ||
| ax: | ||
| Optional axes to plot into. If None, ``marginals_plot`` creates new Axes. | ||
| ignore_size: | ||
| If ``True``, skip the check that prevents the rendering of very large data. | ||
| ignore_sorted: | ||
| Do not check if the coordinates are sorted before plotting. | ||
| coords: | ||
| If supplied, use these coords instead of the input's dimension coordinates. | ||
| **plot_kwargs: | ||
| Forwarded to *all* subplots :func:`plopp.graphics.linefigure`, | ||
| and :func:`plopp.graphics.imagefigure`. | ||
|
|
||
| Returns | ||
| ------- | ||
| : | ||
| A figure that contains all axes. | ||
| """ | ||
| if ax is None: | ||
| ax_2d = make_figure().subplots() | ||
| else: | ||
| ax_2d = ax | ||
|
|
||
| divider = make_axes_locatable(ax_2d) | ||
| ax_top = divider.append_axes("top", 1.2, pad=0.1, sharex=ax_2d) | ||
| ax_side = divider.append_axes(side, 1.2, pad=0.1, sharey=ax_2d) | ||
| cax = divider.append_axes("right", "4%", pad="5%") if cbar else None | ||
|
|
||
| main_data_node = input_to_nodes( | ||
| data, | ||
| processor=partial( | ||
| preprocess, | ||
| ignore_size=ignore_size, | ||
| ignore_sorted=ignore_sorted, | ||
| coords=coords, | ||
| ), | ||
| ) | ||
|
|
||
| if len(main_data_node) > 1: | ||
| raise ValueError("The marginals plot accepts only a single data input.") | ||
| main_data_node = main_data_node[0] | ||
|
|
||
| fig = imagefigure(main_data_node, ax=ax_2d, cax=cax, cbar=cbar, **plot_kwargs) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you're tired of trying to get this working for everything, I can take over. I just realised that the plot won't work for inputs that are missing coords (and probably other things).
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I worked around one issue when building the side data. But
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I pushed an update with a flag to suppress the unsorted coord warning. |
||
|
|
||
| # Main plot (2D) | ||
| y_label = ax_2d.get_ylabel() | ||
| if side == "left": | ||
| ax_2d.yaxis.set_tick_params(labelleft=False) | ||
| ax_2d.set_ylabel("") | ||
|
|
||
| # Top Plot | ||
| top_node = Node(lambda da: da.sum(da.dims[0]), main_data_node) | ||
| linefigure(top_node, ax=ax_top, title=title, **plot_kwargs) | ||
| ax_top.xaxis.set_tick_params(labelbottom=False) | ||
| ax_top.set_xlabel("") | ||
|
|
||
| # Side plot | ||
| def make_side_data(da: sc.DataArray) -> sc.DataArray: | ||
| if da.coords.is_edges(da.dims[0]): | ||
| raise sc.DimensionError( | ||
| "marginals() does not support bin edges along the y-axis." | ||
| ) | ||
| summed = da.sum(da.dims[1]) | ||
| return sc.DataArray( | ||
| data=summed.coords[summed.dim], | ||
| # rename to work around https://github.com/scipp/plopp/issues/616. | ||
| coords={summed.dim: summed.data}, | ||
| ).rename({summed.dim: " "}) | ||
|
|
||
| side_node = Node(make_side_data, main_data_node) | ||
| linefigure(side_node, ax=ax_side, **plot_kwargs) | ||
| ax_side.set_xlabel(ax_top.get_ylabel()) | ||
| if side == "left": | ||
| ax_side.set_ylabel(y_label) | ||
| ax_side.invert_xaxis() | ||
| else: | ||
| ax_side.yaxis.set_tick_params(labelleft=False) | ||
| ax_side.set_ylabel("") | ||
|
|
||
| return fig | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you also need to add it to the top-level index (currently does not show up there).