From a79a42d5f766cb20a8ceb98373c4e07e3bb5604e Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Wed, 21 Nov 2018 08:51:01 -0800 Subject: [PATCH 1/2] FIX: translate timedeltas in _to_ordinalf FIX: translate timedelta64 in _dt64_to_ordinalf --- lib/matplotlib/dates.py | 48 +++++++++++++++++++++++------- lib/matplotlib/tests/test_dates.py | 33 ++++++++++++++++++++ 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index c9eb3a1ca230..e087353e8003 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -222,6 +222,10 @@ def _to_ordinalf(dt): dt = dt.astimezone(UTC) tzi = UTC + if isinstance(dt, datetime.timedelta): + base = dt / datetime.timedelta(days=1) + return base + base = float(dt.toordinal()) # If it's sufficiently datetime-like, it will have a `date()` method @@ -251,6 +255,11 @@ def _dt64_to_ordinalf(d): because we do times compared to ``0001-01-01T00:00:00`` (plus one day). """ + if (isinstance(d, np.timedelta64) or + (isinstance(d, np.ndarray) and + np.issubdtype(d.dtype, np.timedelta64))): + return d / np.timedelta64(1, 'D') + # the "extra" ensures that we at least allow the dynamic range out to # seconds. That should get out to +/-2e11 years. # NOTE: First cast truncates; second cast back is for NumPy 1.10. @@ -271,6 +280,11 @@ def _dt64_to_ordinalf(d): return dt +def _dt64_to_ordinalf_iterable(d): + return np.fromiter((_dt64_to_ordinalf(dd) for dd in d), + float, count=len(d)) + + def _from_ordinalf(x, tz=None): """ Convert Gregorian float of the date, preserving hours, minutes, @@ -405,22 +419,36 @@ def date2num(d): Gregorian calendar is assumed; this is not universal practice. For details see the module docstring. """ + if hasattr(d, "values"): # this unpacks pandas series or dataframes... d = d.values - if not np.iterable(d): - if (isinstance(d, np.datetime64) or (isinstance(d, np.ndarray) and - np.issubdtype(d.dtype, np.datetime64))): - return _dt64_to_ordinalf(d) - return _to_ordinalf(d) - else: - d = np.asarray(d) - if np.issubdtype(d.dtype, np.datetime64): + if not np.iterable(d) and not isinstance(d, np.ndarray): + # single value logic... + if (isinstance(d, np.datetime64) or isinstance(d, np.timedelta64)): return _dt64_to_ordinalf(d) - if not d.size: - return d + else: + return _to_ordinalf(d) + + elif (isinstance(d, np.ndarray) and + (np.issubdtype(d.dtype, np.datetime64) or + np.issubdtype(d.dtype, np.timedelta64))): + # array with all one type of datetime64 object. + return _dt64_to_ordinalf(d) + + elif len(d): + # this is a list or tuple... + if (isinstance(d[0], np.datetime64) or + isinstance(d[0], np.timedelta64)): + return _dt64_to_ordinalf_iterable(d) return _to_ordinalf_np_vectorized(d) + elif hasattr(d, 'size') and not d.size: + # this elif doesn't get tested, but leaving here in case anyone + # needs it. + return d + else: + return [] def julian2num(j): diff --git a/lib/matplotlib/tests/test_dates.py b/lib/matplotlib/tests/test_dates.py index 35e005346335..10ba781609b0 100644 --- a/lib/matplotlib/tests/test_dates.py +++ b/lib/matplotlib/tests/test_dates.py @@ -680,3 +680,36 @@ def test_datetime64_in_list(): dt = [np.datetime64('2000-01-01'), np.datetime64('2001-01-01')] dn = mdates.date2num(dt) assert np.array_equal(dn, [730120., 730486.]) + + +def test_timedelta(): + """ + Test that timedelta objects are properly translated into days. + """ + dt = [datetime.datetime(2000, 1, 1, 0, 0, 0), + datetime.timedelta(days=1, hours=2)] + assert mdates.date2num(dt[1]) == 1 + 2 / 24 + # check that mixed lists work.... + assert mdates.date2num(dt)[0] == 730120.0 + assert mdates.date2num(dt)[1] == 1 + 2 / 24 + + dt = (np.datetime64('2000-01-01'), + np.timedelta64(26, 'h')) + assert mdates.date2num(dt[1]) == 1 + 2 / 24 + # check that mixed lists work.... + assert mdates.date2num(dt)[0] == 730120.0 + assert mdates.date2num(dt)[1] == 1 + 2 / 24 + + dt = [datetime.timedelta(days=1, hours=1), + datetime.timedelta(days=1, hours=2)] + assert mdates.date2num(dt)[0] == 1 + 1 / 24 + assert mdates.date2num(dt)[1] == 1 + 2 / 24 + + dt = (np.timedelta64(25, 'h'), + np.timedelta64(26, 'h')) + assert mdates.date2num(dt)[0] == 1 + 1 / 24 + assert mdates.date2num(dt)[1] == 1 + 2 / 24 + + dt = np.array([25, 26], dtype='timedelta64[h]') + assert mdates.date2num(dt)[0] == 1 + 1 / 24 + assert mdates.date2num(dt)[1] == 1 + 2 / 24 From b12584caa54b4d4e279c072727a0bbfde9321e0b Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 27 Nov 2018 13:55:37 -0800 Subject: [PATCH 2/2] ENH: change to convert_delta approach --- lib/matplotlib/artist.py | 34 +++++++++++++++ lib/matplotlib/axes/_axes.py | 10 ++++- lib/matplotlib/axis.py | 13 ++++++ lib/matplotlib/dates.py | 81 ++++++++++++++++++++++++++++++++---- 4 files changed, 127 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 7fb017f262ad..f0ce9aae2f28 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -190,6 +190,40 @@ def convert_yunits(self, y): return y return ax.yaxis.convert_units(y) + def convert_xunits_delta(self, dx): + """ + Convert *dx* using the unit type of the yaxis. + + This is used for APIs where the data is passed as ``(x, dx)``, and + the ``dx`` can be in different units than the ``x``. For instance, + for dates ``x`` is often of type ``datetime``, and ``dx`` of type + ``timedelta`` and these are converted differently. + + If the artist is not in contained in an Axes or if the yaxis does not + have units, *deltax* itself is returned. + """ + ax = getattr(self, 'axes', None) + if ax is None or ax.xaxis is None: + return dx + return ax.xaxis.convert_units_delta(dx) + + def convert_yunits_delta(self, dy): + """ + Convert *dy* using the unit type of the yaxis. + + This is used for APIs where the data is passed as ``(y, dy)``, and + the ``dy`` can be in different units than the ``y``. For instance, + for dates ``y`` is often of type ``datetime``, and ``dy`` of type + ``timedelta`` and these are converted differently. + + If the artist is not in contained in an Axes or if the yaxis does not + have units, *deltay* itself is returned. + """ + ax = getattr(self, 'axes', None) + if ax is None or ax.yaxis is None: + return dy + return ax.yaxis.convert_units_delta(dy) + @property def axes(self): """The `~.axes.Axes` instance the artist resides in, or *None*.""" diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 9037674780e2..de99ea918617 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2526,10 +2526,16 @@ def broken_barh(self, xranges, yrange, **kwargs): self._process_unit_info(xdata=xdata, ydata=ydata, kwargs=kwargs) - xranges = self.convert_xunits(xranges) + + xnew = [] + for ind in range(len(xranges)): + xr = [[],[]] + xr[0] = self.convert_xunits(xranges[ind][0]) + xr[1] = self.convert_xunits_delta(xranges[ind][1]) + xnew.append(xr) yrange = self.convert_yunits(yrange) - col = mcoll.BrokenBarHCollection(xranges, yrange, **kwargs) + col = mcoll.BrokenBarHCollection(xnew, yrange, **kwargs) self.add_collection(col, autolim=True) self.autoscale_view() diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 4f174b0da45e..34cf2ad3e1ca 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -795,6 +795,7 @@ def cla(self): self.reset_ticks() self.converter = None + self.converter_delta = None self.units = None self.set_units(None) self.stale = True @@ -1490,6 +1491,18 @@ def convert_units(self, x): ret = self.converter.convert(x, self.units, self) return ret + def convert_units_delta(self, dx): + # If dx is already a number, doesn't need converting + if (munits.ConversionInterface.is_numlike(dx) or + self.converter is None): + return dx + + if hasattr(self.converter, 'convert_delta'): + return self.converter.convert_delta(dx, self.units, self) + else: + return self.converter.convert(dx, self.units, self) + + def set_units(self, u): """ Set the units for axis. diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index e087353e8003..93d91ce8c666 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -222,10 +222,6 @@ def _to_ordinalf(dt): dt = dt.astimezone(UTC) tzi = UTC - if isinstance(dt, datetime.timedelta): - base = dt / datetime.timedelta(days=1) - return base - base = float(dt.toordinal()) # If it's sufficiently datetime-like, it will have a `date()` method @@ -242,8 +238,21 @@ def _to_ordinalf(dt): return base +def _td_to_ordinalf(dt): + if isinstance(dt, datetime.timedelta): + base = dt / datetime.timedelta(days=1) + return base + + # a version of _to_ordinalf that can operate on numpy arrays _to_ordinalf_np_vectorized = np.vectorize(_to_ordinalf) +# a version of _to_ordinalf that can operate on numpy arrays +_td_to_ordinalf_np_vectorized = np.vectorize(_td_to_ordinalf) + + +def _td64_to_ordinalf(d): + print('d', d) + return d / np.timedelta64(1, 'D') def _dt64_to_ordinalf(d): @@ -255,11 +264,6 @@ def _dt64_to_ordinalf(d): because we do times compared to ``0001-01-01T00:00:00`` (plus one day). """ - if (isinstance(d, np.timedelta64) or - (isinstance(d, np.ndarray) and - np.issubdtype(d.dtype, np.timedelta64))): - return d / np.timedelta64(1, 'D') - # the "extra" ensures that we at least allow the dynamic range out to # seconds. That should get out to +/-2e11 years. # NOTE: First cast truncates; second cast back is for NumPy 1.10. @@ -285,6 +289,11 @@ def _dt64_to_ordinalf_iterable(d): float, count=len(d)) +def _td64_to_ordinalf_iterable(d): + return np.fromiter((_td64_to_ordinalf(dd) for dd in d), + float, count=len(d)) + + def _from_ordinalf(x, tz=None): """ Convert Gregorian float of the date, preserving hours, minutes, @@ -451,6 +460,56 @@ def date2num(d): return [] +def timedelta2num(d): + """ + Convert datetime objects to Matplotlib dates. + + Parameters + ---------- + d : `datetime.datetime` or `numpy.datetime64` or sequences of these + + Returns + ------- + float or sequence of floats + Number of days (fraction part represents hours, minutes, seconds, ms) + since 0001-01-01 00:00:00 UTC, plus one. + + Notes + ----- + The addition of one here is a historical artifact. Also, note that the + Gregorian calendar is assumed; this is not universal practice. + For details see the module docstring. + """ + + if hasattr(d, "values"): + # this unpacks pandas series or dataframes... + d = d.values + + if not np.iterable(d) and not isinstance(d, np.ndarray): + # single value logic... + if isinstance(d, np.timedelta64): + return _td64_to_ordinalf(d) + else: + return _td_to_ordinalf(d) + + elif (isinstance(d, np.ndarray) and + np.issubdtype(d.dtype, np.timedelta64)): + # array with all one type of datetime64 object. + return _td64_to_ordinalf(d) + + elif len(d): + # this is a list or tuple... + if isinstance(d[0], np.timedelta64): + return _td64_to_ordinalf_iterable(d) + return _td_to_ordinalf_np_vectorized(d) + elif hasattr(d, 'size') and not d.size: + # this elif doesn't get tested, but leaving here in case anyone + # needs it. + return d + else: + return [] + + def julian2num(j): """ Convert a Julian date (or sequence) to a Matplotlib date (or sequence). @@ -1834,6 +1893,10 @@ def convert(value, unit, axis): """ return date2num(value) + @staticmethod + def convert_delta(value, units, axis): + return timedelta2num(value) + @staticmethod def default_units(x, axis): """ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy