From 3e6716a32823a70f3585a217235a72804953ed3c Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Wed, 4 Oct 2017 11:23:55 -0500 Subject: [PATCH 1/2] start --- pandas/core/config_init.py | 16 +++++++ pandas/core/frame.py | 5 +- pandas/core/series.py | 5 +- pandas/plotting/_core.py | 25 +++++----- pandas/plotting/base.py | 96 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 131 insertions(+), 16 deletions(-) create mode 100644 pandas/plotting/base.py diff --git a/pandas/core/config_init.py b/pandas/core/config_init.py index 59578b96807e1..ad23e2039f374 100644 --- a/pandas/core/config_init.py +++ b/pandas/core/config_init.py @@ -480,3 +480,19 @@ def use_inf_as_na_cb(key): cf.register_option( 'engine', 'auto', parquet_engine_doc, validator=is_one_of_factory(['auto', 'pyarrow', 'fastparquet'])) + + +# -------- +# Plotting +# -------- + +plotting_engine_doc = """ +: str + The name of a plotting engine. +""" + + +with cf.config_prefix("plotting"): + cf.register_option( + "engine", "auto", plotting_engine_doc, validator=str + ) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 982b27fd21fb5..0c230b88b032b 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -99,6 +99,7 @@ import pandas.io.formats.console as console from pandas.io.formats.printing import pprint_thing import pandas.plotting._core as gfx +import pandas.plotting.base as gfx_base from pandas._libs import lib, algos as libalgos @@ -6128,8 +6129,8 @@ def isin(self, values): # ---------------------------------------------------------------------- # Add plotting methods to DataFrame - plot = accessor.AccessorProperty(gfx.FramePlotMethods, - gfx.FramePlotMethods) + plot = accessor.AccessorProperty(gfx_base.Dispatcher, + gfx_base.Dispatcher) hist = gfx.hist_frame boxplot = gfx.boxplot_frame diff --git a/pandas/core/series.py b/pandas/core/series.py index dd86e51ee8154..dd888f9b0fd53 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -79,6 +79,7 @@ from pandas.core.config import get_option import pandas.plotting._core as gfx +import pandas.plotting.base as gfx_base __all__ = ['Series'] @@ -3090,8 +3091,8 @@ def to_period(self, freq=None, copy=True): # ---------------------------------------------------------------------- # Add plotting methods to Series - plot = accessor.AccessorProperty(gfx.SeriesPlotMethods, - gfx.SeriesPlotMethods) + plot = accessor.AccessorProperty(gfx_base.SeriesPlotMethods, + gfx_base.SeriesPlotMethods) hist = gfx.hist_series diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 62b2899f49413..89b5b0b5f9617 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -10,7 +10,6 @@ import numpy as np from pandas.util._decorators import cache_readonly -from pandas.core.base import PandasObject from pandas.core.dtypes.missing import isna, notna, remove_na_arraylike from pandas.core.dtypes.common import ( is_list_like, @@ -30,6 +29,7 @@ from pandas.io.formats.printing import pprint_thing from pandas.util._decorators import Appender +from pandas.plotting import base from pandas.plotting._compat import (_mpl_ge_1_3_1, _mpl_ge_1_5_0, _mpl_ge_2_0_0) @@ -2458,16 +2458,7 @@ def _grouped_plot_by_column(plotf, data, columns=None, by=None, return result -class BasePlotMethods(PandasObject): - - def __init__(self, data): - self._data = data - - def __call__(self, *args, **kwargs): - raise NotImplementedError - - -class SeriesPlotMethods(BasePlotMethods): +class MPLSeriesPlotMethods(base.SeriesPlotMethods): """Series plotting accessor and method Examples @@ -2480,6 +2471,9 @@ class SeriesPlotMethods(BasePlotMethods): with the ``kind`` argument: ``s.plot(kind='line')`` is equivalent to ``s.plot.line()`` """ + @property + def engine_name(self): + return 'matplotlib' def __call__(self, kind='line', ax=None, figsize=None, use_index=True, title=None, grid=None, @@ -2624,7 +2618,7 @@ def pie(self, **kwds): return self(kind='pie', **kwds) -class FramePlotMethods(BasePlotMethods): +class MPLFramePlotMethods(base.FramePlotMethods): """DataFrame plotting accessor and method Examples @@ -2637,6 +2631,9 @@ class FramePlotMethods(BasePlotMethods): method with the ``kind`` argument: ``df.plot(kind='line')`` is equivalent to ``df.plot.line()`` """ + @property + def engine_name(self): + return 'matplotlib' def __call__(self, x=None, y=None, kind='line', ax=None, subplots=False, sharex=None, sharey=False, layout=None, @@ -2844,3 +2841,7 @@ def hexbin(self, x, y, C=None, reduce_C_function=None, gridsize=None, if gridsize is not None: kwds['gridsize'] = gridsize return self(kind='hexbin', x=x, y=y, C=C, **kwds) + + +base.register_engine("matplotlib", 'series', MPLSeriesPlotMethods) +base.register_engine("matplotlib", 'frame', MPLFramePlotMethods) diff --git a/pandas/plotting/base.py b/pandas/plotting/base.py new file mode 100644 index 0000000000000..dd71197d11cc6 --- /dev/null +++ b/pandas/plotting/base.py @@ -0,0 +1,96 @@ +""" +Base module for plotting engines to override and register +with pandas. +""" +from pandas.core.base import PandasObject + +engines = {} + + +def register_engine(name, kind, engine): + # XXX: get rid of the kind parameter + global engines + engines[(name, kind)] = engine + + +def deregister_engine(name, kind): + # XXX: get rid of the kind parameter + global engines + engines.pop((name, kind)) + + +def get_engine(kind): + # XXX: get rid of the kind parameter + from pandas import get_option + + active = get_option('plotting.engine') + if active == 'auto': + active = 'matplotlib' + + return engines[(active, kind)] + + +class Dispatcher(object): + + def __init__(self, data): + self._data = data + + def __call__(self, *args, **kwargs): + kind = 'frame' if self._data.ndim == 2 else 'series' + engine = get_engine(kind) + return engine(self._data)(*args, **kwargs) + + def __getattribute__(self, name): + if name == '_data': + return object.__getattribute__(self, name) + kind = 'frame' if self._data.ndim == 2 else 'series' + + engine = get_engine(kind)(self._data) + return getattr(engine, name) + + +class BasePlotMethods(PandasObject): + + def __init__(self, data): + self._data = data + + def __call__(self, *args, **kwargs): + """Make a plot""" + raise NotImplementedError + + def area(self, **kwargs): + raise NotImplementedError("This backend doesn't support this method") + + def bar(self, **kwargs): + raise NotImplementedError("This backend doesn't support this method") + + def barh(self, **kwargs): + raise NotImplementedError("This backend doesn't support this method") + + def box(self, **kwargs): + raise NotImplementedError("This backend doesn't support this method") + + def density(self, **kwargs): + raise NotImplementedError("This backend doesn't support this method") + + def hist(self, **kwargs): + raise NotImplementedError("This backend doesn't support this method") + + def line(self, **kwargs): + raise NotImplementedError("This backend doesn't support this method") + + def pie(self, **kwargs): + raise NotImplementedError("This backend doesn't support this method") + + +class SeriesPlotMethods(BasePlotMethods): + pass + + +class FramePlotMethods(BasePlotMethods): + + def hexbin(self, x, y, **kwargs): + raise NotImplementedError("This backend doesn't support this method") + + def scatter(self, x, y, **kwargs): + raise NotImplementedError("This backend doesn't support this method") From 1143fe6c40037c159db7382420b04bbe344e102f Mon Sep 17 00:00:00 2001 From: rs2 Date: Tue, 14 Nov 2017 20:41:00 +0000 Subject: [PATCH 2/2] Add a placeholder for `bokeh` engine --- pandas/plotting/_core.py | 62 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 89b5b0b5f9617..2709f008fd99e 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -2458,6 +2458,65 @@ def _grouped_plot_by_column(plotf, data, columns=None, by=None, return result +class BokehSeriesPlotMethods(base.SeriesPlotMethods): + + @property + def engine_name(self): + return 'bokeh' + + def __call__(**kwds): + import logging + logging.error(kwds) + + +class BokehFramePlotMethods(base.FramePlotMethods): + """ + repro notebook: + + import pandas as pd + import numpy as np + import bokeh + from bokeh.io import output_notebook, INLINE + + output_notebook(hide_banner=True) + pd.set_option('plotting.engine', 'bokeh') + df = pd.DataFrame(np.random.rand(5, 3), columns='foo bar baz'.split()) + df.plot() + """ + + @property + def engine_name(self): + return 'bokeh' + + def __call__(self, x=None, y=None, kind='line', figure=None, + use_index=True, title=None, grid=None, + legend=True, style=None, + fontsize=None, colormap=None, + yerr=None, xerr=None, + secondary_y=False, sort_columns=False, **kwds): + + from bokeh.plotting import Figure, show + from bokeh.models import ColumnDataSource, HoverTool + + df = self._data + source = ColumnDataSource(df) + hover_tool = HoverTool( + tooltips=[ + ('index', '$index'), + ('(x,y)', '($x, $y)'), + ] + ) + plot = Figure( + width=450, + height=300, + logo=None, + tools=[hover_tool] + ) + for column in df.columns: + plot.line(x='index', y=column, source=source); + return show(plot) + + class MPLSeriesPlotMethods(base.SeriesPlotMethods): """Series plotting accessor and method @@ -2845,3 +2904,6 @@ def hexbin(self, x, y, C=None, reduce_C_function=None, gridsize=None, base.register_engine("matplotlib", 'series', MPLSeriesPlotMethods) base.register_engine("matplotlib", 'frame', MPLFramePlotMethods) + +base.register_engine('bokeh', 'series', BokehSeriesPlotMethods) +base.register_engine('bokeh', 'frame', BokehFramePlotMethods) \ No newline at end of file