plotnine icon indicating copy to clipboard operation
plotnine copied to clipboard

Secondary and Multiple x and y axis

Open IGGold opened this issue 7 years ago • 12 comments

Hello,

searched repository and documentation for secondary x & y axis, @has2k1 you mentioned in #63 , "When Matplotlib ships the constraint layout manager, I'll look into adding secondary axes functionality."

Do you have any idea what the approx timeframe for this is, months or quarters away?

For the finance guys having not only secondary but multiple x & y axes is extremely handy, which is something that users of bloomberg and reuters eikon are familiar with, and has been a major problem for excel users as they still haven't managed to even introduce a tertiary, or let alone even more levels in axes options, people have to index data if they need to compare more than 2 different time series still even in excel 2016 as we all know, which is a time consuming pain.

2017-10-19_14-15-32

e.g.

(ggplot(df, aes(x1="", x2="", x3="", ...  , y1="" y2="", y3="", ... ))
+ scale_x1_datetime(breaks=date_breaks('1 year'), 
+ scale_x2_datetime(breaks=date_breaks('1 quarter'),
+ scale_x3_datetime(breaks=date_breaks('1 month'), 
+ geom_line(aes(y1='US'), color='#00355F', size=1)
+ geom_line(aes(y2='Turkey'), color='#2494EA', size=1)
+ geom_line(aes(y3='Australia'), color='#0222FF', size=1)
+ labs(x1="", x2="", x3="", y1="" y2="", y3="")

matplot example code: multiple_yaxis_with_spines.py https://matplotlib.org/gallery/ticks_and_spines/multiple_yaxis_with_spines.html

Kind Regards,

IGGold avatar Oct 19 '17 04:10 IGGold

There is no time frame.

has2k1 avatar Oct 20 '17 08:10 has2k1

is there a method to force plotnine to use a defined axis? for example if you create a plotnine figure 1 and return the axis, can you create another plotnine figure 2 and force it to output on the plotnine figure 1 axis?

BlackArbsCEO avatar Nov 28 '17 20:11 BlackArbsCEO

It is an internal API, but if you do not care for such see how the figure and axes are reused when creating animations.

has2k1 avatar Nov 28 '17 20:11 has2k1

I can take a look, do you have a link to the example?

BlackArbsCEO avatar Nov 28 '17 20:11 BlackArbsCEO

Added the link.

has2k1 avatar Nov 28 '17 20:11 has2k1

ok I downloaded the latest development version of plotnine and tried to implement it like so:

g = (pn.ggplot(df, pn.aes(col, color='factor(other_col)')) + pn.geom_density())
figure, plot = g.draw(return_ggplot=True)
axs = plot.axs

and it outputs the first figure correctly. Then I try to reuse the axis like so:

from copy import copy

g2 = (pn.ggplot(df, pn.aes(col)) + pn.geom_density())
g2 = copy(g2) # tried both with and without this line I get same error
g2._draw_using_figure(figure, axs)

and it outputs the following traceback error:

---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
~/anaconda3/envs/pymc3/lib/python3.6/copy.py in deepcopy(x, memo, _nil)
    179                 else:
--> 180                     y = _reconstruct(x, memo, *rv)
    181 

~/anaconda3/envs/pymc3/lib/python3.6/copy.py in _reconstruct(x, memo, func, args, state, listiter, dictiter, deepcopy)
    279         if deep:
--> 280             state = deepcopy(state, memo)
    281         if hasattr(y, '__setstate__'):

~/anaconda3/envs/pymc3/lib/python3.6/copy.py in deepcopy(x, memo, _nil)
    149     if copier:
--> 150         y = copier(x, memo)
    151     else:

~/anaconda3/envs/pymc3/lib/python3.6/copy.py in _deepcopy_dict(x, memo, deepcopy)
    239     for key, value in x.items():
--> 240         y[deepcopy(key, memo)] = deepcopy(value, memo)
    241     return y

~/anaconda3/envs/pymc3/lib/python3.6/copy.py in deepcopy(x, memo, _nil)
    179                 else:
--> 180                     y = _reconstruct(x, memo, *rv)
    181 

~/anaconda3/envs/pymc3/lib/python3.6/copy.py in _reconstruct(x, memo, func, args, state, listiter, dictiter, deepcopy)
    279         if deep:
--> 280             state = deepcopy(state, memo)
    281         if hasattr(y, '__setstate__'):

~/anaconda3/envs/pymc3/lib/python3.6/copy.py in deepcopy(x, memo, _nil)
    149     if copier:
--> 150         y = copier(x, memo)
    151     else:

~/anaconda3/envs/pymc3/lib/python3.6/copy.py in _deepcopy_dict(x, memo, deepcopy)
    239     for key, value in x.items():
--> 240         y[deepcopy(key, memo)] = deepcopy(value, memo)
    241     return y

~/anaconda3/envs/pymc3/lib/python3.6/copy.py in deepcopy(x, memo, _nil)
    160             if copier:
--> 161                 y = copier(memo)
    162             else:

~/anaconda3/envs/pymc3/lib/python3.6/site-packages/matplotlib/transforms.py in __copy__(self, *args)
    120             "TransformNode instances can not be copied. " +
--> 121             "Consider using frozen() instead.")
    122     __deepcopy__ = __copy__

NotImplementedError: TransformNode instances can not be copied. Consider using frozen() instead.

The above exception was the direct cause of the following exception:

SystemError                               Traceback (most recent call last)
~/anaconda3/envs/pymc3/lib/python3.6/site-packages/IPython/core/formatters.py in __call__(self, obj)
    691                 type_pprinters=self.type_printers,
    692                 deferred_pprinters=self.deferred_printers)
--> 693             printer.pretty(obj)
    694             printer.flush()
    695             return stream.getvalue()

~/anaconda3/envs/pymc3/lib/python3.6/site-packages/IPython/lib/pretty.py in pretty(self, obj)
    378                             if callable(meth):
    379                                 return meth(obj, self, cycle)
--> 380             return _default_pprint(obj, self, cycle)
    381         finally:
    382             self.end_group()

~/anaconda3/envs/pymc3/lib/python3.6/site-packages/IPython/lib/pretty.py in _default_pprint(obj, p, cycle)
    493     if _safe_getattr(klass, '__repr__', None) is not object.__repr__:
    494         # A user-provided repr. Find newlines and replace them with p.break_()
--> 495         _repr_pprint(obj, p, cycle)
    496         return
    497     p.begin_group(1, '<')

~/anaconda3/envs/pymc3/lib/python3.6/site-packages/IPython/lib/pretty.py in _repr_pprint(obj, p, cycle)
    691     """A pprint that just redirects to the normal repr function."""
    692     # Find newlines and replace them with p.break_()
--> 693     output = repr(obj)
    694     for idx,output_line in enumerate(output.splitlines()):
    695         if idx:

~/anaconda3/envs/pymc3/lib/python3.6/site-packages/plotnine/ggplot.py in __repr__(self)
     84         Print/show the plot
     85         """
---> 86         self.draw()
     87         plt.show()
     88         return '<ggplot: (%d)>' % self.__hash__()

~/anaconda3/envs/pymc3/lib/python3.6/site-packages/plotnine/ggplot.py in draw(self, return_ggplot)
    176         # ggplot object. Do the copy here as we may/may not
    177         # assign a default theme
--> 178         self = deepcopy(self)
    179         self._build()
    180 

~/anaconda3/envs/pymc3/lib/python3.6/copy.py in deepcopy(x, memo, _nil)
    159             copier = getattr(x, "__deepcopy__", None)
    160             if copier:
--> 161                 y = copier(memo)
    162             else:
    163                 reductor = dispatch_table.get(cls)

~/anaconda3/envs/pymc3/lib/python3.6/site-packages/plotnine/ggplot.py in __deepcopy__(self, memo)
    105                 memo[id(new[key])] = new[key]
    106             else:
--> 107                 new[key] = deepcopy(old[key], memo)
    108 
    109         return result

~/anaconda3/envs/pymc3/lib/python3.6/copy.py in deepcopy(x, memo, _nil)
    159             copier = getattr(x, "__deepcopy__", None)
    160             if copier:
--> 161                 y = copier(memo)
    162             else:
    163                 reductor = dispatch_table.get(cls)

~/anaconda3/envs/pymc3/lib/python3.6/site-packages/plotnine/facets/facet.py in __deepcopy__(self, memo)
    296                 memo[id(new[key])] = new[key]
    297             else:
--> 298                 new[key] = deepcopy(old[key], memo)
    299 
    300         return result

~/anaconda3/envs/pymc3/lib/python3.6/copy.py in deepcopy(x, memo, _nil)
    178                     y = x
    179                 else:
--> 180                     y = _reconstruct(x, memo, *rv)
    181 
    182     # If is its own copy, don't memoize.

~/anaconda3/envs/pymc3/lib/python3.6/copy.py in _reconstruct(x, memo, func, args, state, listiter, dictiter, deepcopy)
    278     if state is not None:
    279         if deep:
--> 280             state = deepcopy(state, memo)
    281         if hasattr(y, '__setstate__'):
    282             y.__setstate__(state)

~/anaconda3/envs/pymc3/lib/python3.6/copy.py in deepcopy(x, memo, _nil)
    148     copier = _deepcopy_dispatch.get(cls)
    149     if copier:
--> 150         y = copier(x, memo)
    151     else:
    152         try:

~/anaconda3/envs/pymc3/lib/python3.6/copy.py in _deepcopy_dict(x, memo, deepcopy)
    238     memo[id(x)] = y
    239     for key, value in x.items():
--> 240         y[deepcopy(key, memo)] = deepcopy(value, memo)
    241     return y
    242 d[dict] = _deepcopy_dict

~/anaconda3/envs/pymc3/lib/python3.6/copy.py in deepcopy(x, memo, _nil)
    159             copier = getattr(x, "__deepcopy__", None)
    160             if copier:
--> 161                 y = copier(memo)
    162             else:
    163                 reductor = dispatch_table.get(cls)

SystemError: <built-in method __deepcopy__ of numpy.ndarray object at 0x7f03376a42b0> returned a result with an error set

BlackArbsCEO avatar Nov 28 '17 21:11 BlackArbsCEO

g = (pn.ggplot(df, pn.aes(col, color='factor(other_col)')) + pn.geom_density())
figure, plot = g.draw(return_ggplot=True)
axs = plot.axs
g2 = (pn.ggplot(df, pn.aes(col)) + pn.geom_density())
g2._draw_using_figure(figure, axs)
print(1)

has2k1 avatar Nov 28 '17 22:11 has2k1

that worked thanks.

BlackArbsCEO avatar Nov 28 '17 22:11 BlackArbsCEO

Looking forward to the Secondary y axis in the coming version. Now I have to use the pyplot.twinx() after the ggplot command, but clearly not a good solution for Secondary y axis . Really Wish to have the scale_y_continuous(sec.axis = sec_axis(~./200)) as same as the R-ggplot2.

xuhuizhang avatar Nov 17 '18 18:11 xuhuizhang

This feature would be hugely helpful. Thank you for this great package.

himalayajung avatar Mar 17 '21 17:03 himalayajung

hi, big fan of plotnine, novice user of github. I wanted to ask how to do such a plot now that the feature has been implemented?

mankiddyman avatar Apr 20 '22 21:04 mankiddyman

I tried this as a workaround for the scale_y_continous but it seems that the second plot g2 hides the first g... and I only see the geom_point the geom_box is gone...

Any news on the scale_y_continous availability?

thanks!!

g = (pn.ggplot(df_grouped, pn.aes(x='factor(Day)', y='Cell size')) +
geom_boxplot(varwidth=True,width=1) +
facet_grid('. ~ Sample') +
#scale_y_continuous(name = "Primary axis", sec.axis = sec_axis(~.*100, name = "Secondary")) +
theme(strip_background_y = element_text(color = '#969dff' , width = 0.2),figure_size=(18, 7))
)
figure, plot = g.draw(return_ggplot=True)
axs = plot.axs
g2 = (pn.ggplot(df_volumes, aes(x='factor(Day)', y='Total_Cells')) + 
geom_point(color='red', size=5) +
facet_grid('. ~ Sample'))
g2._draw_using_figure(figure, axs)
print(1)

sgawad avatar Feb 06 '23 20:02 sgawad