From 79fdb1727efaedb0e3e584b089669ffd60fe1a75 Mon Sep 17 00:00:00 2001 From: Michael Hirsch Date: Wed, 11 Dec 2019 14:51:47 -0500 Subject: [PATCH] Add Support for Series Histograms (#95) * add support for series plotting * update docs for series plotting support * add tests for series plotting * fix typo * adds comment to ed_hist_series --- .../reference/api/eland-Series-hist-1.png | Bin 0 -> 45659 bytes .../reference/api/eland.Series.hist.rst | 8 +++ eland/operations.py | 16 +++++- eland/plotting.py | 45 ++++++++++++++++ eland/query.py | 5 +- eland/series.py | 3 ++ .../tests/plotting/test_series_hist_pytest.py | 33 ++++++++++++ eland/tests/series/test_hist_pytest.py | 49 ++++++++++++++++++ 8 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 docs/source/reference/api/eland-Series-hist-1.png create mode 100644 docs/source/reference/api/eland.Series.hist.rst create mode 100644 eland/tests/plotting/test_series_hist_pytest.py create mode 100644 eland/tests/series/test_hist_pytest.py diff --git a/docs/source/reference/api/eland-Series-hist-1.png b/docs/source/reference/api/eland-Series-hist-1.png new file mode 100644 index 0000000000000000000000000000000000000000..d1c0465a82b7a3c984b560e51fa38b520d86a667 GIT binary patch literal 45659 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 df = ed.DataFrame('localhost', 'ecommerce') + >>> hist = df['taxful_total_price'].hist(figsize=[10,10]) # doctest: +SKIP + """ + # this is mostly the same code as above, it has been split out + # to a series specific method now so we can expand series plotting + + + # Start with empty pandas data frame derived from + ed_s_bins, ed_s_weights = ed_s._hist(num_bins=bins) + + if by is not None: + raise NotImplementedError("TODO") + + # raise error rather than warning when series is not plottable + if ed_s_bins.empty: + raise ValueError("{} has no meaningful histogram interval. All values 0." + .format(ed_s.name)) + + naxes = len(ed_s_bins.columns) + fig, axes = _subplots(naxes=naxes, ax=ax, squeeze=False, figsize=figsize, layout=layout) + _axes = _flatten(axes) + for i, col in enumerate(com.try_sort(ed_s_bins.columns)): + ax = _axes[i] + ax.hist(ed_s_bins[col][:-1], bins=ed_s_bins[col], weights=ed_s_weights[col], **kwds) + ax.grid(grid) + + _set_ticks_props(axes, xlabelsize=xlabelsize, xrot=xrot, + ylabelsize=ylabelsize, yrot=yrot) + fig.subplots_adjust(wspace=0.3, hspace=0.3) + + return axes \ No newline at end of file diff --git a/eland/query.py b/eland/query.py index 19bce86..de82744 100644 --- a/eland/query.py +++ b/eland/query.py @@ -148,7 +148,10 @@ class Query: "interval": interval } } - self._aggs[name] = agg + + if not min == max == 0: + self._aggs[name] = agg + def to_search_body(self): if self._query.empty(): diff --git a/eland/series.py b/eland/series.py index 250c37b..d695eff 100644 --- a/eland/series.py +++ b/eland/series.py @@ -40,6 +40,7 @@ from pandas.io.common import _expand_user, _stringify_path from eland import NDFrame from eland.common import DEFAULT_NUM_ROWS_DISPLAYED, docstring_parameter from eland.filter import NotFilter, Equal, Greater, Less, GreaterEqual, LessEqual, ScriptFilter, IsIn +import eland.plotting as gfx def _get_method_name(): @@ -106,6 +107,8 @@ class Series(NDFrame): index_field=index_field, query_compiler=query_compiler) + hist = gfx.ed_hist_series + @property def empty(self): """Determines if the Series is empty. diff --git a/eland/tests/plotting/test_series_hist_pytest.py b/eland/tests/plotting/test_series_hist_pytest.py new file mode 100644 index 0000000..6fb85b7 --- /dev/null +++ b/eland/tests/plotting/test_series_hist_pytest.py @@ -0,0 +1,33 @@ +# Copyright 2019 Elasticsearch BV +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# File called _pytest for PyCharm compatability +import pytest +from matplotlib.testing.decorators import check_figures_equal + +from eland.tests.common import TestData + + +@check_figures_equal(extensions=['png']) +def test_plot_hist(fig_test, fig_ref): + test_data = TestData() + + pd_flights = test_data.pd_flights()['FlightDelayMin'] + ed_flights = test_data.ed_flights()['FlightDelayMin'] + + pd_ax = fig_ref.subplots() + ed_ax = fig_test.subplots() + + pd_flights.hist(ax=pd_ax) + ed_flights.hist(ax=ed_ax) diff --git a/eland/tests/series/test_hist_pytest.py b/eland/tests/series/test_hist_pytest.py new file mode 100644 index 0000000..9fdbc79 --- /dev/null +++ b/eland/tests/series/test_hist_pytest.py @@ -0,0 +1,49 @@ +# Copyright 2019 Elasticsearch BV +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# File called _pytest for PyCharm compatability + +import numpy as np +import pandas as pd +from pandas.util.testing import assert_almost_equal +import pytest + +from eland.tests.common import TestData + + +class TestSeriesFrameHist(TestData): + + def test_flight_delay_min_hist(self): + pd_flights = self.pd_flights() + ed_flights = self.ed_flights() + + num_bins = 10 + + # pandas data + pd_flightdelaymin = np.histogram(pd_flights['FlightDelayMin'], num_bins) + + pd_bins = pd.DataFrame( + {'FlightDelayMin': pd_flightdelaymin[1]}) + pd_weights = pd.DataFrame( + {'FlightDelayMin': pd_flightdelaymin[0]}) + + ed_bins, ed_weights = ed_flights['FlightDelayMin']._hist(num_bins=num_bins) + + # Numbers are slightly different + assert_almost_equal(pd_bins, ed_bins) + assert_almost_equal(pd_weights, ed_weights) + + def test_invalid_hist(self): + with pytest.raises(ValueError): + assert self.ed_ecommerce()['products.tax_amount'].hist()