Improve plot_summary with overlay support, colors, labels, grid, and title (#1733)#1814
Improve plot_summary with overlay support, colors, labels, grid, and title (#1733)#1814Abraham-y wants to merge 1 commit intosbi-dev:mainfrom
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1814 +/- ##
==========================================
+ Coverage 87.86% 87.88% +0.01%
==========================================
Files 140 140
Lines 12726 12753 +27
==========================================
+ Hits 11182 11208 +26
- Misses 1544 1545 +1
Flags with carried forward coverage won't be shown. Click here to find out more.
|
janfb
left a comment
There was a problem hiding this comment.
Great addition @Abraham-y , thanks!
This already looks good overall, but I suggest a bit of refactoring, see comments below.
| **plot_options["subplots"], | ||
| ) | ||
| axes = np.atleast_1d(axes) # type: ignore | ||
| assert fig is not None and axes is not None |
There was a problem hiding this comment.
it's better practice to do an if-else and raise a suitable error here, e.g., RuntimeError or ValueError with an informative error message.
| ) | ||
|
|
||
| ax.set_ylabel(ylabel[i], fontsize=fontsize) | ||
| if overlay: |
There was a problem hiding this comment.
I think we will have a kwargs problem here: both branches of these if-else case always pass explicit color=color and label=_labels[i] keyword arguments to ax.plot() as well as **(plot_kwargs or {}).
Thus, if a user passes both plot_kwargs={"color": "red"} or plot_kwargs={"label": "foo"}, it raises TypeError: got multiple values for keyword argument 'color'.
The fix would be merging all kwargs into a single dict before passing to ax.plot(), where we implement a hierarchy: explicit colors/labels params over plot_kwargs, over tag-name defaults. I suggest implement a short helper function, e.g., _build_plot_kwargs that does this and then returns the final kwargs to be passed to ax.plot()
| if legend: | ||
| ax.legend(fontsize=fontsize) | ||
| if grid: | ||
| ax.grid(True) | ||
| if title: | ||
| t = title if isinstance(title, str) else title[0] | ||
| ax.set_title(t, fontsize=fontsize) |
There was a problem hiding this comment.
can we move these if-else cases outside of the overlay if-else case? this would reduce code duplication, plus at the moment the legend would not be rendered for the non-overlay case (or is this on purpose?).
| xlabel: x-axis label describing 'steps' attribute of tensorboards ScalarEvent. | ||
| ylabel: list of alternative ylabels for items in tags. Optional. | ||
| plot_kwargs: will be passed to ax.plot. | ||
| overlay: if True, plots all tags on a single axes intead of separate subplots. |
There was a problem hiding this comment.
I made a couple of comments on the if-else structures below. but more generally, I think we can avoid it entirely. I suggest to construct a list of tags which can be a list with a single tag as well (for the overlay=False case). then you need just a single loop over this list for rendering the figure.
I suggest: Before creating the figure, convert the flat tags list into subplot_tags, a list of lists where each inner list is the group of tags for one subplot. In overlay mode, this is [tags] (one subplot, all tags). In non-overlay mode it's [[tag] for tag in tags] (one subplot per tag). Similarly, precompute subplot_ylabels: in overlay mode, join the ylabels if they differ (e.g. "training_loss / validation_loss"), otherwise use them directly. Does this make sense?
You would still need the helper function _build_plot_kwargs(tag_idx) that constructs the keyword arguments dict for a single ax.plot() call. It starts from a copy of plot_kwargs (or empty dict), then overrides color if colors was provided, overrides label if labels was provided, and falls back to the tag name as label if neither labels nor a plot_kwargs['label'] key exists. This avoids the duplicate-keyword problem (see comment below).
Overall, with this approach you can have a single loop and we avoid the current code duplication in the two branches.
| yield Path("/fake/log/dir") | ||
|
|
||
|
|
||
| class TestPlotSummaryBackwardCompat: |
There was a problem hiding this comment.
let's please use plain test functions instead of these test classes, unless we really need the classes, e.g., when we share init or fixtures within classes.
There was a problem hiding this comment.
good to this! a couple of comments:
- let's try to keep it as concise as possible but make the how-to-guide code cells executable as well, e.g., not just plain text.
- Guide 22 already covers
TensorBoardsetup, training, and viewing logs. This guide should focus narrowly onplot_summaryusage and link to guide 22 for the setup, rather than re-explaining how to pass a Path or inference object.
I suggest the following outline / cells:
- One-liner intro: what
plot_summarydoes, link to guide 22 for training/tracking setup - One runnable setup cell: quick NPE training (reuse the pattern from guide 22: prior, simulator, train, get log_dir)
- Basic usage: single
plot_summarycall, show output - Overlay with customization: one cell combining overlay, colors, labels, title, grid. This is the main feature to showcase
- Drop the standalone "Using a log directory" section. just mention in the intro that you can pass either an inference object or a Path
Closes #1733
This PR improves the plot_summary function with the following changes:
New parameters:
All changes are backward compatible. Existing calls to plot_summary behave identically.
Also includes a test suite and a short how to guide notebook under the visualization docs.
AI disclosure: I used Claude for assistance in planning these changes. Every line of code was reviewed and tested by me.