3
\$\begingroup\$

There is few points In the script I don't like I think it can be improved, but I need a second opinion, here is the interactive plot I'm trying to build.

enter image description here

here is the code with thought in comments.

#!/usr/bin/python3

from functools import lru_cache

import numpy as np
import scipy.stats as ss
import matplotlib.pyplot as plt
import matplotlib.widgets as widgets

@lru_cache # beacuse it redraws each time
def get_pdf(mu, sigma=1, offset=4):
    o = sigma * offset
    x = np.linspace(mu - o, mu + o, 100)
    rv = ss.norm(mu, sigma)
    return x, rv.pdf(x)

fig, ax = plt.subplots()
# added the subplot for bottom margin and the slider, since its also the ax
plt.subplots_adjust(bottom=0.25)

ax.fill_between(*get_pdf(0), label='A', alpha=0.7)
ax.set_xlim(-10, 10)
ax.set_ylim(0, 1)

# my guess is this has to be integrated with the update rather than outside
# and using t variable as global which I don't like, and guess it can be improved
t = ax.fill_between(*get_pdf(2), label='B', color='crimson', alpha=0.7)

slider = widgets.Slider(
    # ax position are absolute, should be easy without subplot may be
    ax      = plt.axes([0.25, 0.1, 0.5, 0.03]), 
    label   = "shift",
    valmin  = -5,
    valmax  = 5,
    valinit = 2,
    valstep = 0.5
)

def update(val):
    x, y = get_pdf(val)
    global t # really hate to do this
    t.remove()
    t = ax.fill_between(*get_pdf(val), color='crimson', alpha=0.7)
    fig.canvas.draw_idle()

slider.on_changed(update)
ax.legend()
plt.show()
\$\endgroup\$
3
\$\begingroup\$

If you want to get rid of the global t you could consider creating a class to manage the data and updating. I quickly whipped something together, although I'm not well versed enough in stats to have a good idea about what names would be more appropriate.

from functools import lru_cache

import numpy as np
import scipy.stats as ss
import matplotlib.pyplot as plt
import matplotlib.widgets as widgets

#probability density function generator: chached
@lru_cache # beacuse it redraws each time
def get_pdf(mu, sigma=1, offset=4):
    o = sigma * offset
    x = np.linspace(mu - o, mu + o, 100)
    rv = ss.norm(mu, sigma)
    return x, rv.pdf(x)

# This simple class will hold the reference to `t` so that it doesn't need to 
# be a global
class Data():
    '''
    A simple class that plots data on an axis and contains a method for updating
    the plot.
    '''

    def __init__(self, ax, **properties):
        self.properties = properties
        self.t = ax.fill_between(*get_pdf(2), **properties)

    def update(self, val):
        x, y = get_pdf(val)
        self.t.remove()
        self.t = ax.fill_between(x, y, **self.properties)
        fig.canvas.draw_idle()


# Generate the figure and axis for our widget

fig, ax = plt.subplots()
# added the subplot for bottom margin and the slider, since its also the ax
plt.subplots_adjust(bottom=0.25)

# add a slider widget
slider = widgets.Slider(
    # ax position are absolute, should be easy without subplot may be
    ax      = plt.axes([0.25, 0.1, 0.5, 0.03]), 
    label   = "shift",
    valmin  = -5,
    valmax  = 5,
    valinit = 2,
    valstep = 0.5
)


# add a reference distribution *A*
ax.fill_between(*get_pdf(0), label='A', alpha=0.7)
ax.set_xlim(-10, 10)
ax.set_ylim(0, 1)

# Create a data instance
properties = dict(label='B', color='crimson', alpha=0.7)
data = Data(ax=ax, **properties)

# link data update method to slider.on_changed
slider.on_changed(data.update)
ax.legend()
plt.show()

```
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ You are not using the x, y you defined in the update method. You can either remove them or re-use them in the fill_between. \$\endgroup\$ – Graipher May 28 at 10:27
  • \$\begingroup\$ Ah, just saw that the same is true in the OP :) \$\endgroup\$ – Graipher May 28 at 10:28
  • \$\begingroup\$ Thanks, want to edit the post perhaps? \$\endgroup\$ – Andrew Micallef May 28 at 10:29
  • \$\begingroup\$ No, that would be a valid point to include in a review! Feel free to edit your post to include it, though. \$\endgroup\$ – Graipher May 28 at 10:30
  • 1
    \$\begingroup\$ actually, on reflection I feel like getting x,y = get_pdf(val) and then ax.fill_between(x,y...) is more pythonic than *get_pdf(val) \$\endgroup\$ – Andrew Micallef May 28 at 11:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.