diff --git a/paper/imgs/logistic_growth-dark.png b/paper/imgs/logistic_growth-dark.png new file mode 100644 index 0000000..94bea08 Binary files /dev/null and b/paper/imgs/logistic_growth-dark.png differ diff --git a/paper/imgs/logistic_growth.png b/paper/imgs/logistic_growth.png new file mode 100644 index 0000000..f710a6e Binary files /dev/null and b/paper/imgs/logistic_growth.png differ diff --git a/paper/imgs/sin-composed.png b/paper/imgs/sin-composed.png new file mode 100644 index 0000000..c50a577 Binary files /dev/null and b/paper/imgs/sin-composed.png differ diff --git a/paper/imgs/sinusoid-full.png b/paper/imgs/sinusoid-full.png new file mode 100644 index 0000000..d23127a Binary files /dev/null and b/paper/imgs/sinusoid-full.png differ diff --git a/paper/imgs/sinusoid.png b/paper/imgs/sinusoid.png new file mode 100644 index 0000000..a9cdff1 Binary files /dev/null and b/paper/imgs/sinusoid.png differ diff --git a/paper/imgs/sinusoid_1-dark.png b/paper/imgs/sinusoid_1-dark.png new file mode 100644 index 0000000..32e0820 Binary files /dev/null and b/paper/imgs/sinusoid_1-dark.png differ diff --git a/paper/imgs/sinusoid_1.png b/paper/imgs/sinusoid_1.png new file mode 100644 index 0000000..3f1f780 Binary files /dev/null and b/paper/imgs/sinusoid_1.png differ diff --git a/paper/imgs/sinusoid_2-dark.png b/paper/imgs/sinusoid_2-dark.png new file mode 100644 index 0000000..1f1d624 Binary files /dev/null and b/paper/imgs/sinusoid_2-dark.png differ diff --git a/paper/imgs/sinusoid_2.png b/paper/imgs/sinusoid_2.png new file mode 100644 index 0000000..ee3f694 Binary files /dev/null and b/paper/imgs/sinusoid_2.png differ diff --git a/paper/imgs/sinusoid_3-dark.png b/paper/imgs/sinusoid_3-dark.png new file mode 100644 index 0000000..1aa59ad Binary files /dev/null and b/paper/imgs/sinusoid_3-dark.png differ diff --git a/paper/imgs/sinusoid_3.png b/paper/imgs/sinusoid_3.png new file mode 100644 index 0000000..06b5b70 Binary files /dev/null and b/paper/imgs/sinusoid_3.png differ diff --git a/paper/imgs/sinusoid_4-dark.png b/paper/imgs/sinusoid_4-dark.png new file mode 100644 index 0000000..c2ed012 Binary files /dev/null and b/paper/imgs/sinusoid_4-dark.png differ diff --git a/paper/imgs/sinusoid_4.png b/paper/imgs/sinusoid_4.png new file mode 100644 index 0000000..c8c8c9b Binary files /dev/null and b/paper/imgs/sinusoid_4.png differ diff --git a/paper/imgs/threshold-dark.png b/paper/imgs/threshold-dark.png new file mode 100644 index 0000000..9f55ed7 Binary files /dev/null and b/paper/imgs/threshold-dark.png differ diff --git a/paper/imgs/threshold.png b/paper/imgs/threshold.png new file mode 100644 index 0000000..6d4629d Binary files /dev/null and b/paper/imgs/threshold.png differ diff --git a/paper/paper.bib b/paper/paper.bib new file mode 100644 index 0000000..a45797f --- /dev/null +++ b/paper/paper.bib @@ -0,0 +1,68 @@ +@Article{Hunter:2007, + Author = {Hunter, J. D.}, + Title = {Matplotlib: A 2D graphics environment}, + Journal = {Computing in Science \& Engineering}, + Volume = {9}, + Number = {3}, + Pages = {90--95}, + abstract = {Matplotlib is a 2D graphics package used for Python for + application development, interactive scripting, and publication-quality + image generation across user interfaces and operating systems.}, + publisher = {IEEE COMPUTER SOC}, + doi = {10.1109/MCSE.2007.55}, + year = 2007 +} + +@unpublished{interactive_Jupyter_widgets:2015, +author = {Community}, +title = {Ipywidgets}, +year = {2015}, +url = {https://github.com/jupyter-widgets/ipywidgets} +} + +@article{VanderPlas2018, + doi = {10.21105/joss.01057}, + url = {https://doi.org/10.21105/joss.01057}, + year = {2018}, + publisher = {The Open Journal}, + volume = {3}, + number = {32}, + pages = {1057}, + author = {Jacob VanderPlas and Brian E. Granger and Jeffrey Heer and Dominik Moritz and Kanit Wongsuphasawat and Arvind Satyanarayan and Eitan Lees and Ilia Timofeev and Ben Welsh and Scott Sievert}, + title = {Altair: Interactive Statistical Visualizations for Python}, + journal = {Journal of Open Source Software} +} + +@software{philipp_rudiger_2020_3634720, + author = {Philipp Rudiger and + Julia Signell and + James A. Bednar and + Andrew and + Jean-Luc Stevens and + Chris B and + Jordan Samuels and + Todd and + Thomas PEDOT and + Sander van den Oord and + Jon Mease and + Isaac Virshup and + Gabriel Corona and + Danny Hermes and + Curtis H. and + Anita Graser}, + title = {holoviz/hvplot: Version 0.5.2}, + month = feb, + year = 2020, + publisher = {Zenodo}, + version = {v0.5.2}, + doi = {10.5281/zenodo.3634720}, + url = {https://doi.org/10.5281/zenodo.3634720} +} +@online{plotly, + author = {Plotly Technologies Inc.}, + title = {Collaborative data science}, + publisher = {Plotly Technologies Inc.}, + address = {Montreal, QC}, + year = {2015}, + url = {https://plot.ly} +} diff --git a/paper/paper.md b/paper/paper.md new file mode 100644 index 0000000..a1cd68b --- /dev/null +++ b/paper/paper.md @@ -0,0 +1,155 @@ +--- +title: "mpl-interactions: A Python Package for Interactive Matplotlib Figures" +tags: + - Python + - Matplotlib + - Ipywidgets + - Interactive + - Visualization +authors: + - name: Ian Hunt-Isaak + orcid: 0000-0002-7591-083X + affiliation: 1 # (Multiple affiliations must be quoted) + - name: John Russell + orcid: 0009-0002-0402-2306 + affiliation: 2 # (Multiple affiliations must be quoted) + - name: Doeke Hekstra + orcid: 0000-0003-2332-9223 + affiliation: "1, 2" # (Multiple affiliations must be quoted) +affiliations: + - name: John A. Paulson School of Engineering and Applied Sciences, Harvard University, Cambridge, MA, USA + index: 1 + - name: Department of Molecular and Cellular Biology, Harvard University, Cambridge, MA, USA + index: 2 +date: 7 Decemeber 2023 +bibliography: paper.bib +--- + +# Summary + + + +Data exploration, model building and pedagogy all benefit from the ability to interactively update elements in Matplotlib [@Hunter:2007] figures. `mpl-interactions` enables this by making it easy for users to create Matplotlib figures in which the displayed data can be dynamically controlled through widgets. These widgets can be automatically generated by passing arguments such as arrays or shorthands (such as a tuple of numbers to generate a slider) to modified pyplot functions. After creation of these widgets, `mpl-interactions` updates plot elements without further user intervention. For ease of use, it adds these features while otherwise staying close to the `matplotlib.pyplot` interface. `mpl-interactions` is built such that parameters controlled by the generated widgets are easy to re-use for multiple plot elements, while not interfering with static elements. This design allows for building any figure that `Matplotlib` can produce, while adding interactivity to specific parts as desired. + +Complete tutorials, examples, and API documentation are available on https://mpl-interactions.readthedocs.io/en/stable/. + +# Statement of Need + + + +The ability to interact dynamically with plots through widgets such as sliders can be a powerful tool in the scientific process and in pedagogy. For instance, varying a parameter of a mathematical model plotted on top of data helps to understand the relationship between the model and the data. Similarly, exploratory data analysis can be enhanced by interactively modifying aspects of the plot such as which points are displayed, or the threshold level of a displayed image. `mpl-interactions`' core goal is to make this aspect of interactive plotting easier when using Matplotlib. Other interactive functionalities are out of scope as they are provided by Matplotlib (e.g., zooming and panning), or by other third party packages (e.g., point selection). + +Matplotlib provides mechanisms for updating elements (artists) in figures. However, the APIs for these artists are not consistent and some are under- or undocumented. Furthermore, the creation and positioning of the native Matplotlib widgets is nontrivial. While the `ipywidgets` [@interactive_Jupyter_widgets:2015] library makes widget creation and positioning easier, it is difficult to integrate with Matplotlib in a performant manner. The easiest way to do so is to use the `ipywidgets`' `interact()` function, which automatically generates sliders and other widgets to control arguments to arbitrary python functions. However, this can result in completely regenerating the figure which can be slow. Alternatively, the user needs to remember the specifics of how to update each individual artist. While `Matplotlib` and `ipywidgets` provide the tools for controlling plots with widgets, the overhead of implementing such control can overwhelm its utility. `mpl-interactions` fills this gap by making it easy for users to generate widgets that dynamically control plots. + +There are a wide range of data visualization tools for Python, such as Altair [@VanderPlas2018], Holoviz [@philipp_rudiger_2020_3634720], and Plotly [@plotly], which provide rich interactive plotting experiences. These tools often have overlapping functionality with `mpl-interactions` and in some cases provide a greater range of interactive capabilities. However, they may not be useful for a user already invested in using Matplotlib, or for a user for whom Matplotlib is otherwise the best solution. For such users `mpl-interactions` adds functionality to `Matplotlib`. To this end `mpl-interactions` closely follows the semantics of the Matplotlib API, rather than creating a separate set of semantics, like the other mentioned libraries. Thus, it enhances an analysis workflow that uses Matplotlib by enabling users to add interactive features to a library they are already using. Otherwise user would need to use multiple plotting libraries for different aspects of the data analysis process. + +# Overview + +`mpl-interactions` provides several key features to make generating interactive figures simple. The first is what arguments are accepted. While `Matplotlib` requires users to pass arrays as arguments, `mpl-interactions` allows passing a function that returns numeric values. Parameters to these functions are specified by adding extra keyword arguments (`kwargs`) to the plotting function call. Then, `mpl-interactions` will generate the appropriate widgets for the parameters and run the functions to generate the numerical data to plot. For example, to plot a sinusoid and control its amplitude and frequency using sliders, a function returning the `y` values is defined and passed as the `y` parameter to the `plot` function. The ranges of the `A` and `f` parameters are defined as extra keyword arguments using tuples as a shorthand for what widget to generate. + +```python +import mpl_interactions.ipyplot as iplt +import matplotlib.pyplot as plt +import numpy as np + +fig, ax = plt.subplots() + +def sinusoid(x, A, f): + return A*np.sin(x * f) + +x = np.linspace(0, np.pi, 100) + +ctrls = iplt.plot(x, sinusoid, A=(1, 10), f = (.5, 2)) +plt.show() +``` + +![Multiple states of the figure resulting from moving the sliders after running above example in jupyter lab.\label{fig:sinusoid}](imgs/sin-composed.png) + +A second important feature of `mpl-interactions` is that interactive plot components are not isolated from each other. That is, the control widgets generated from one plotting call can be re-used to control other components. In addition to showing the re-use of control widgets this example demonstrates how Matplotlib styling arguments (such as `vmin`) can be controlled through widgets. + +```python +N = 128 +rng = np.random.default_rng(seed=1995) +im = rng.normal(size=(N,N)) + +fig, axs = plt.subplots(1, 2, figsize=(12, 5)) + + +# create interactive controls +ctrls = iplt.imshow(im, vmin_vmax=("r", im.min(), im.max()), ax=axs[0]) + +# plot histogram of pixel intensities + +# by indexing the ctrls object it is possible to +# re-use the the vmin and vmax created by imshow +# to control the position of the axvlines +iplt.axvline(ctrls["vmin"], ax=axs[1], c="k") +iplt.axvline(ctrls["vmax"], ax=axs[1], c="k"); +axs[1].hist(im.flatten(), bins="auto") +axs[1].set_title("Histogram of Pixel Intensities") +axs[1].set_xlabel('Pixel Intensity') +``` + +![An imshow where the thresholds of the colorbar are controlled by the `vmin_vmax` range slider. While the slider is created by the `imshow` call, its interactive parameters can be reused to control the position of the veritcal lines on the histogram.](imgs/threshold-dark.png) + +Finally, `mpl-interactions` allows the reuse of Python functions performing mathematical operations in multiple parts of user code, rather than requiring users to write a plotting specific version. For example, below the `logistic_growth` function is used for both curve fitting and an interactive display in order to better understand the role of the model parameters. + +```python +%matplotlib ipympl +import matplotlib.pyplot as plt +import numpy as np +from scipy.optimize import curve_fit + +import mpl_interactions.ipyplot as iplt + + +def logistic_growth(t, L, k, t0): + return L / (1 + np.exp(-k * (t - t0))) + + +# create a synthetic dataset of logistic growth +rng = np.random.default_rng(seed=1995) +t_data = np.sort(rng.uniform(0, 10, size=50)) +y_data = logistic_growth( + t_data, L=5, k=1, t0=1) + rng.normal(size=t_data.size, scale=0.1 +) + + +# You can use the `logistic_growth` function to curve_fit +popt, pcov = curve_fit(logistic_growth, t_data, y_data) + + +# Now you can directly the same function to make +# an interactive plot to better understand its parameters +fig, axs = plt.subplots(1, 2, sharey=True) +axs[0].plot(t_data, y_data, "o") +axs[0].plot(t_data, logistic_growth(t_data, *popt)) +axs[0].set_title("Data + Fit") +axs[1].set_title("Interactive Exploration") + +ctrls = iplt.plot( + np.linspace(0, 10), + logistic_growth, + L=(0.5, 10), + k=(0.1, 1), + t0=(0, 2.5), + ax=axs[1], + label="interactive", +) +axs[1].plot(t_data, logistic_growth(t_data, *popt), "--", label="fit") +plt.legend() +``` + +![Generated figure and sliders after running above example in jupyter lab.\label{fig:logistic}](imgs/logistic_growth-dark.png){ width=75% } + +This framework makes it easy to generate complex interactive visualizations. It also enables `mpl-interactions` to manage generating GIFs. Any parameter controlled through `mpl-interactions` can be used to automatically generate a gif of the plot changing as a function of that parameter ([Animation Documentation](https://mpl-interactions.readthedocs.io/en/stable/examples/animations.html)). Thus, `mpl-interactions` can assist across the data visualization process, from initial exploration to the creation of a final animated plot as a GIF. + +# Acknowledgements + +This work was supported by a National Defense Science and Engineering Graduate Fellowship (FA9550-19-F-0008, to IHI), the George W. Merck Fund of the New York Community Trust (award 338034, to DRH), and funds from Harvard University. + +We thank Dr. K. Dalton from stimulating discussion, and Easun Arunachalam for feedback on drafts of this paper. In addition, many users have contributed features and bug fixes. Of particular note are Remco de Boer, and Samantha Hamilton who made contributions to documentation and code, and documentation respectively. A full list of coding contributors can be found here: https://github.com/mpl-extensions/mpl-interactions/graphs/contributors + +Finally, many users have contributed in ways other than coding. For example by raising issues either with the package or documentation. These users are recognized here: https://github.com/mpl-extensions/mpl-interactions#contributors- + +# References