Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

ENH: Decorate plot functions with draw if interactive calls #513

Closed
wants to merge 2 commits into from

3 participants

@jseabold
Owner

Addresses #355.

@josef-pkt
Owner

As we discussed last time something similar came up, I think it's not a good idea to use a decorator for this.

We get all the negative effects of a decorator, and it is just one line of code to do the draw if interactive before the return.

@rgommers
Collaborator

-1 on this. Personally I don't want figures to pop up before I tell them to. More importantly, both Fernando and John Hunter advised against using draw_if_interactive in this thread: https://groups.google.com/forum/#!msg/pystatsmodels/biNlCvJPNNY/BT7bQJmOa1cJ
It messes up the IPython notebook apparently.

@jseabold
Owner

Hmm. Ok. Well this is a bit annoying then for people who do choose to work in interactive mode and not with qtconsole or the notebooks. I'm often sitting there wondering why changes never show up on plots compared to the R experience of updating on command. My understanding is that matplotlib uses this internally in pyplot and IPython catches it, so it's handled but the recommendation is not to use it? Also, calling draw_if_interactive won't make your figures pop up if you're not in interactive mode.

@rgommers
Collaborator

If this is what you always want in an IPython console, you can add it to your IPython startup config right? Or type it once on the command line. Adding this to every plot in statsmodels just seems like the wrong solution.

@jseabold
Owner

Yes, I have interactive = True in my mpl or ipython config. But when I have, say, a scatter plot of points already, then I pass this axes to abline_plot, I expect that my regression line will just show up when it returns the new fig. But the plot won't get updated until I import and call draw_if_interactive. If you don't have interactive = True, then the call is innocuous. I brought it up on ipython-user. Since the notebooks have been through many more iterations since that discussion, I'm hoping that it's handled now. It seems like a useful feature to me.

@rgommers
Collaborator

OK, let's see what the IPython folks say then.

@josef-pkt
Owner

I never use interactive, so I'm don't have a personal preference either way.

I think I remember now from the discussion that one reason not to import pyplot is if there is no displayed backend.
(But I'm not sure I remember correctly.)
this would mean protecting the import of pyplot (beyond the matplotlib import)

If there is a real desire to have both behaviors, then one possibility would be to add our own draw_if_interactive global option, that can be turned off even if matplotlib is in interactive mode.

@jseabold
Owner

I asked on the ipython list. The answer I got did not really clear anything up (or did it now that I reread?). It seems that issue is with show. I'm not proposing calling show. I'm just proposing updating an axes on interaction. I also don't think there's a performance hit unless the user is actually in interactive mode, which means there's no show call anyway. Still don't see how this is a problem and haven't been told that there actually is one yet. Here's the thread

http://python.6.n6.nabble.com/IPython-User-using-matplotlib-draw-if-interactive-in-library-code-td4991275.html

@jseabold
Owner

Maybe I should ask on mpl-user.

@josef-pkt
Owner

Looking back at John Hunter's recommendation

"Finally, I would avoid anything in pyplot (gca, gcf) because you can't
assume your user has imported pyplot (eg they may be embedding your
stats plots in a GUI and importing pyplot could cause conflicting GUI
mainloops). I would only import pyplot if the axes is None"

I think the problem is if we need to import if_interactive from pyplot.
The recommendation was to make all pyplot imports part of an optional codepath. I think the examples are some embedding code and web servers, that should run with only the object oriented interface.

(I definitely don't like the decorator solution.)

@josef-pkt
Owner

as I mentioned before, we can use our own global settings, and only import from pyplot if "can_use_pyplot" (or something) is true.

@jseabold
Owner

I'm not using gca or gcf or anything that assumes you've imported pyplot and are working this way. If you're not, then the call is innocuous AFAICT. In a GUI or as an embedded plot, interactive will be False, so nothing happens. We can assume the user has pyplot because they have matplotlib to even use this part of the code. There's nothing in draw_if_interactive that assumes that the plot has been created using the pyplot commands.

Decorators are not evil. This isn't that magical and it's the easiest way to get this behavior for all the plotting functions without having to copy and paste a bunch of code.

@jseabold
Owner

Again, I really don't know the internals of MPL well so I don't want to come off like I do and am arguing hard in favor of this, but I've still yet to hear a definitive answer that this can produce problems. I'll ask on MPL.

@jseabold
Owner

Closing this because it sounds dead in the water. I'm not convinced there are actual side effects though. (And decorators are useful sometimes.)

@jseabold jseabold closed this
@TomAugspurger TomAugspurger referenced this pull request from a commit
@minrk minrk add 4-space soft-tabs to qtconsole
pressing tab inserts four spaces, so there should never be any '\t' characters
inserted by typing.

closes gh-900
closes gh-513
33cfae8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
3  statsmodels/graphics/boxplots.py
@@ -12,7 +12,7 @@
__all__ = ['violinplot', 'beanplot']
-
+@utils.drawifinteractive
def violinplot(data, ax=None, labels=None, positions=None, side='both',
show_boxplot=True, plot_opts={}):
"""Make a violin plot of each dataset in the `data` sequence.
@@ -223,6 +223,7 @@ def _set_ticks_labels(ax, data, labels, positions, plot_opts):
return
+@utils.drawifinteractive
def beanplot(data, ax=None, labels=None, positions=None, side='both',
jitter=False, plot_opts={}):
"""Make a bean plot of each dataset in the `data` sequence.
View
2  statsmodels/graphics/correlation.py
@@ -12,6 +12,7 @@
from . import utils
+@utils.drawifinteractive
def plot_corr(dcorr, xnames=None, ynames=None, title=None, normcolor=False,
ax=None, cmap='RdYlBu_r'):
"""Plot correlation of many variables in a tight color grid.
@@ -128,6 +129,7 @@ def plot_corr(dcorr, xnames=None, ynames=None, title=None, normcolor=False,
return fig
+@utils.drawifinteractive
def plot_corr_grid(dcorrs, titles=None, ncols=None, normcolor=False, xnames=None,
ynames=None, fig=None, cmap='RdYlBu_r'):
"""Create a grid of correlation plots.
View
2  statsmodels/graphics/factorplots.py
@@ -3,7 +3,7 @@
from statsmodels.graphics.plottools import rainbow
import utils
-
+@utils.drawifinteractive
def interaction_plot(x, trace, response, func=np.mean, ax=None, plottype='b',
xlabel=None, ylabel=None, colors=[], markers=[],
linestyles=[], legendloc='best', legendtitle=None,
View
2  statsmodels/graphics/functional.py
@@ -12,6 +12,7 @@
__all__ = ['fboxplot', 'rainbowplot', 'banddepth']
+@utils.drawifinteractive
def fboxplot(data, xdata=None, labels=None, depth=None, method='MBD',
wfactor=1.5, ax=None, plot_opts={}):
"""Plot functional boxplot.
@@ -205,6 +206,7 @@ def fboxplot(data, xdata=None, labels=None, depth=None, method='MBD',
return fig, depth, ix_depth, ix_outliers
+@utils.drawifinteractive
def rainbowplot(data, xdata=None, depth=None, method='MBD', ax=None,
cmap=None):
"""Create a rainbow plot for a set of curves.
View
5 statsmodels/graphics/gofplots.py
@@ -374,6 +374,7 @@ def probplot(self, line=None, ax=None, exceed=False):
return fig
+@utils.drawifinteractive
def qqplot(data, dist=stats.norm, distargs=(), a=0, loc=0, scale=1, fit=False,
line=False, ax=None):
"""
@@ -477,6 +478,7 @@ def qqplot(data, dist=stats.norm, distargs=(), a=0, loc=0, scale=1, fit=False,
fig = probplot.qqplot(ax=ax, line=line)
return fig
+@utils.drawifinteractive
def qqplot_2samples(data1, data2, xlabel=None, ylabel=None, line=None, ax=None):
"""
Q-Q Plot of two samples' quantiles.
@@ -548,6 +550,7 @@ def qqplot_2samples(data1, data2, xlabel=None, ylabel=None, line=None, ax=None):
return fig
+@utils.drawifinteractive
def qqline(ax, line, x=None, y=None, dist=None, fmt='r-'):
"""
Plot a reference line for a qqplot.
@@ -636,6 +639,7 @@ def plotting_pos(nobs, a):
"""
return (np.arange(1.,nobs+1) - a)/(nobs- 2*a + 1)
+@utils.drawifinteractive
def _fmt_probplot_axis(ax, dist, nobs):
"""
Formats a theoretical quantile axis to display the corresponding
@@ -674,6 +678,7 @@ def _fmt_probplot_axis(ax, dist, nobs):
verticalalignment='center')
ax.set_xlim([axis_qntls.min(), axis_qntls.max()])
+@utils.drawifinteractive
def _do_plot(x, y, dist=None, line=False, ax=None, fmt='bo'):
"""
Boiler plate plotting function for the `ppplot`, `qqplot`, and
View
1  statsmodels/graphics/plot_grids.py
@@ -38,6 +38,7 @@ def _make_ellipse(mean, cov, ax, level=0.95, color=None):
ax.add_artist(ell)
+@utils.drawifinteractive
def scatter_ellipse(data, level=0.9, varnames=None, ell_kwds=None,
plot_kwds=None, add_titles=False, keep_ticks=False,
fig=None):
View
7 statsmodels/graphics/regressionplots.py
@@ -22,6 +22,7 @@
'plot_regress_exog']
+@utils.drawifinteractive
def plot_fit(res, exog_idx, exog_name='', y_true=None, ax=None, fontsize='small'):
"""Plot fit against one regressor.
@@ -82,6 +83,7 @@ def plot_fit(res, exog_idx, exog_name='', y_true=None, ax=None, fontsize='small'
+@utils.drawifinteractive
def plot_regress_exog(res, exog_idx, exog_name='', fig=None):
"""Plot regression results against one regressor.
@@ -174,6 +176,7 @@ def _partial_regression(endog, exog_i, exog_others):
return res1c, (res1a, res1b)
+@utils.drawifinteractive
def plot_partregress_ax(endog, exog_i, exog_others, varname='',
title_fontsize=None, ax=None):
"""Plot partial regression for a single regressor.
@@ -217,6 +220,7 @@ def plot_partregress_ax(endog, exog_i, exog_others, varname='',
return fig
+@utils.drawifinteractive
def plot_partregress(results, exog_idx=None, xnames=None, grid=None, fig=None):
"""Plot partial regression for a set of regressors.
@@ -308,6 +312,7 @@ def plot_partregress(results, exog_idx=None, xnames=None, grid=None, fig=None):
return fig
+@utils.drawifinteractive
def plot_ccpr_ax(res, exog_idx=None, ax=None):
"""Plot CCPR against one regressor.
@@ -351,6 +356,7 @@ def plot_ccpr_ax(res, exog_idx=None, ax=None):
return fig
+@utils.drawifinteractive
def plot_ccpr(res, exog_idx=None, grid=None, fig=None):
"""Generate CCPR plots against a set of regressors, plot in a grid.
@@ -413,6 +419,7 @@ def plot_ccpr(res, exog_idx=None, grid=None, fig=None):
return fig
+@utils.drawifinteractive
def abline_plot(intercept=None, slope=None, horiz=None, vert=None,
model_results=None, ax=None, **kwargs):
"""
View
2  statsmodels/graphics/tsaplots.py
@@ -7,6 +7,7 @@
from statsmodels.tsa.stattools import acf, pacf
+@utils.drawifinteractive
def plot_acf(x, ax=None, lags=None, alpha=.05, use_vlines=True, unbiased=False,
fft=False, **kwargs):
"""Plot the autocorrelation function
@@ -87,6 +88,7 @@ def plot_acf(x, ax=None, lags=None, alpha=.05, use_vlines=True, unbiased=False,
return fig
+@utils.drawifinteractive
def plot_pacf(x, ax=None, lags=None, alpha=.05, method='ywm',
use_vlines=True, **kwargs):
"""Plot the partial autocorrelation function
View
14 statsmodels/graphics/utils.py
@@ -1,5 +1,5 @@
"""Helper functions for graphics with Matplotlib."""
-
+from functools import wraps
__all__ = ['create_mpl_ax', 'create_mpl_fig']
@@ -89,3 +89,15 @@ def create_mpl_fig(fig=None, figsize=None):
fig = plt.figure(figsize=figsize)
return fig
+
+def drawifinteractive(func):
+ """
+ Decorates plot functions with a draw_if_interactive call.
+ """
+ @wraps(func)
+ def new(*args, **kwargs):
+ import matplotlib.pyplot as plt
+ fig = func(*args, **kwargs)
+ plt.draw_if_interactive()
+ return fig
+ return new
Something went wrong with that request. Please try again.