Skip to main content

Manipulate like plotting in python

Project description

Description {#orgedbdd51}

The package repository is located at https://github.com/bottom-bracket/manipylate. It can also be installed from pypi with pip install manipylate.

In research we spend some time with plotting data, comparing measurement results or testing the influence of parameter changes. Often the most intuitive approach is by looking at different plots. However, while python is great to create reproducible figures, I found no way to quickly create plots with which one can interact without changing the code used to create the plot as in mathematica’s Manipulate. The closest one can get to this is interact from ipywidgets which is limited to run in Jupyter notebooks.

Manipylate shall provide the simplified functionalities from its bigger sibling Manipulate. The tasks I had in mind while writing the tool are:

  • Plotting 1d and 2d cuts of multidimensional data and changing the slice index with sliders
  • Plot a function for changing parameters which one can modify interactively

I also wanted to keep things as simple as possible and be able to use manipylate outside jupyter notebooks.

Interactivity {#org1aa4893}

  • Sliders are added automatically
  • Views can be saved with Shift + middle mouse click in the figure if used in pure matplotlib mode and by click on the camera button in jupyter

Examples {#org69b706b}

First we get the environment ready (leave out the lines starting with % for non-ipython shells).

%matplotlib qt
%load_ext autoreload
%autoreload 2
import matplotlib as mpl
from matplotlib import pyplot as plt
import numpy as np
from manipylate import ifigure

The autoreload extension is already loaded. To reload it, use:
  %reload_ext autoreload

1d cut through a 2d array {#orgd93ca72}

Let’s create some data:

x = np.linspace(-5, 5, 200)
X, Y = np.meshgrid(x, x)
data = np.sinc(X * Y**2)

The figure is created by creating an ifigure object. We can then call its add_plot method to add an axis and a line plot. Here axpos defines where the plot shall be placed, x is the x-axis data. data the 2d array of which we want to plot a cut along the axis given by plot_ax, and parameters is a list of dimension parameters we want to change using sliders.

Calling show creates the figure. It needs to be called after all plots have been added to the figure since here we’ll create the sliders and arange the slider axes (in case of execution outside jupyter).

ifig = ifigure(1,1)
ifig.add_plot(axpos=[0,0],x=x,data=data,parameters=['x'],plot_ax=1)
ifig.show()

img

In jupyter notebook {#org51186c9}

In order to use the ipywidget widgets, we call ifigure with the jupyterwidget keyword argument.

ifig = ifigure(1,1,jupyterwidget=True)
ifig.add_plot(axpos=[0,0],x=x,data=data,parameters=['x'],plot_ax=1)
ifig.show()

img

Adding a second plot {#orgb7c979f}

In the same axis {#orgc24bc08}

Axes are remembered by the ifigure object based on their position, so we can call the same add_plot command with another dataset. Note that any keyword arguments are passed to matplotlib.axes.plot, so we can use this to change the line style.

ifig = ifigure(1,1)
ifig.add_plot(axpos=[0,0],x=x,data=data,parameters=['x'],plot_ax=1)
ifig.add_plot(axpos=[0,0],x=x,data=-data,parameters=['x'],plot_ax=1,ls=':')
ifig.show()

img

Note that here both plots share the same parameter. In the next example we will see that if called with a different name, a new slider will be created.

As twinned axis {#orgd05a4f2}

Sometimes I want to compare two different values (like amplitude and phase) over the same x axis. This can be done by passing the twinx=True to the add_plot method. Here I also use the fix_lim flag to prevent the phase axis from updating. Note that add_plot returns a matplotlib axis instance which we can modify as usual (here setting the y scale to logarithmic).

fc = np.linspace(-5, 5, 50) ** 2 + 250
x = np.linspace(200, 300, 120)
FC, X = np.meshgrid(fc, x, indexing="ij")
data = 1 / ((FC ** 2 - X ** 2 - 1j * X * 1) ** 2)

ifig = ifigure(1, 1)
ax = ifig.add_plot(axpos=[0, 0], x=x, data=np.abs(data), parameters=["fc"], plot_ax=1)
ax.set_yscale("log")
ifig.add_plot(
    axpos=[0, 0],
    x=x,
    data=np.angle(data, deg=True),
    parameters=["x"],
    plot_ax=1,
    twinx=True,
    ls=":",
    fix_lim=True,
)
ifig.show()

img

As new axis {#org08b1530}

In order to add more than one subplots we adjust the ifigure creation. Here we also use another parameter for the second plot.

fc = np.linspace(-5, 5, 50) ** 2 + 250
x = np.linspace(200, 300, 120)
FC, X = np.meshgrid(fc, x, indexing="ij")
data = 1 / ((FC ** 2 - X ** 2 - 1j * X * 1) ** 2)

ifig = ifigure(2, 1)
ax = ifig.add_plot(axpos=[0, 0], x=x, data=np.abs(data), parameters=["fc"], plot_ax=1)
ax.set_yscale("log")
ifig.add_plot(
    axpos=[1, 0],
    x=fc,
    data=np.abs(data),
    parameters=["x"],
    plot_ax=0,
    ls=":",
)
ifig.show()

img

2d cut trough nd array {#orgb43b3a9}

Plotting a 2d cut is nearly the same as a 1d line. ifigure creates an imshow plot when being passed data which has two more dimensions than the number of parameters passed to the function. For the 2d plot we need to specify along which axes we want to cut by changing the plot_ax argument to a list of length 2.

x = np.linspace(-5, 5, 200)
y = np.linspace(-3, 3, 100)
z = np.linspace(-4, 4, 80)
X, Y ,Z = np.meshgrid(x, y, z,indexing='ij')
data = np.sinc(X * Y**2 * Z**3)

ifig=ifigure(1,1)

ifig.add_plot(axpos=[0,0],x=[x,y],data=data,parameters=['x'],plot_ax=[0,1])
ifig.show()

img

Plotting a functions value {#org86172e6}

The data argument can be replaced by a function that returns either a 1d array for a line plot or a 2d array for a map plot.

In 1d {#org01d16db}

The main difference to calling the plot on an array is that we do not need to specify the plot_ax parameter but we need to define a range and step size for the slider, which is done by replacing the string argument in the parameter list by a list containing name,minimum, maximum and step size.

fc = 250
x = np.linspace(200, 800, 120)
def lor(y):
    return np.abs(1 / ((4*fc ** 2 - (x+y) ** 2 - 1j * x * 20) ** 2))

ifig = ifigure(1, 1)
ax = ifig.add_plot(axpos=[0, 0], x=x, data=lor, parameters=[["y",-100,100,1]])
ax.set_yscale("log")
ifig.show()

img

Furthermore it is possible to do parametric plots using a function for x and data arguments. Make sure that the variable names are the same in the definition and add_plot call.

import numpy as np
from manipylate import ifigure

def x(theta,npts):
    th = np.linspace(1,theta,npts)
    return th/50*np.cos(th)
def y(theta,npts):
    th = np.linspace(1,theta,npts)
    return -th/50*np.sin(th)

ifig = ifigure(1,1)
ax = ifig.add_plot(axpos=[0,0],x=x,data=y,parameters=[['theta',1,100,1],['npts',10,1000,1]],fix_lim=True)
ax.set_ylim(-2,2)
ax.set_xlim(-2,2)
ax.set_aspect('equal')
ifig.show()

img

If one of the functions x,y takes more arguments than the other just add a **kwargs to its definition (e.g. def x(theta,npts,**kwargs)).

In 2d {#org898b6eb}

Complex figure layout {#org3f5c33c}

Since the subplot layout creation is based on GridSpec, we can create more complicated layouts. We use the handy numpy.s_ to create the exact slices to index the GridSpec.

x = np.linspace(-5, 5, 200)
y = np.linspace(-3, 3, 100)
z = np.linspace(-4, 4, 80)
X, Y ,Z = np.meshgrid(x, y, z,indexing='ij')
data = np.sinc(X * Y**2 + Z)

ifig = ifigure(6, 4,figsize=(12,8))
ax=ifig.add_plot(axpos=np.s_[0,:], x=x, data=data, parameters=["y",'z'],plot_ax=0)
ax.set(xlabel='x')
ax=ifig.add_plot(axpos=np.s_[1,:2], x=y, data=data, parameters=["x","z"],plot_ax=1)
ax.set(xlabel='y')
ax=ifig.add_plot(axpos=np.s_[1,2:], x=z, data=data, parameters=["x","y"],plot_ax=2)
ax.set(xlabel='z')
ax=ifig.add_plot(axpos=np.s_[2::,:], x=[x,z], data=data, parameters=["y"],plot_ax=[0,2])
ax.set(xlabel='x',ylabel='y')
ifig.show()

img

Naming and saving views {#org6c3014c}

The ifigure class can be called with the figurename parameter which will determine the base name and the extension for individually saved views. The full filename is constructed from the figurename and the current slider names and values. Slider axes are removed during saving.

TODOs {#orgab035fc}

TODOs {#org5997233}

  • Comment and document code

Bugs {#org662cf6f}

Missing Features {#org873105d}

  • add convert_param method to display physical parameter values (e.g. 0-1μm instead of index values 1-51)
  • 3d plots ?
  • choice for 2d plots (contour)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

manipylate-0.1.4.tar.gz (5.1 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

manipylate-0.1.4-py3-none-any.whl (16.5 kB view details)

Uploaded Python 3

File details

Details for the file manipylate-0.1.4.tar.gz.

File metadata

  • Download URL: manipylate-0.1.4.tar.gz
  • Upload date:
  • Size: 5.1 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.1 CPython/3.9.0

File hashes

Hashes for manipylate-0.1.4.tar.gz
Algorithm Hash digest
SHA256 dc4c311e2d0609548612b82bff0d9b691ac79456126569b38993b9b6465f533d
MD5 1d1cd15ceaf85f8a6dbc6b595c347e19
BLAKE2b-256 6822d9788b1d6501576ff06a18be4bbf611c3a5256b7914c1d8a1a45c66bb222

See more details on using hashes here.

File details

Details for the file manipylate-0.1.4-py3-none-any.whl.

File metadata

  • Download URL: manipylate-0.1.4-py3-none-any.whl
  • Upload date:
  • Size: 16.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.1 CPython/3.9.0

File hashes

Hashes for manipylate-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 a4c37a607591645c0c2a8b5b333287a48932aa25684c328c9cf85882cc88c867
MD5 a49bcc5d6543aedac05609b5e5978516
BLAKE2b-256 2352922811ca49f1f326893355146b59801dadff24d68f440678583f6975e52e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page