From 9efa68cb4ac66baaad4c0cc52a888f18a922ae0b Mon Sep 17 00:00:00 2001 From: Nis Martensen Date: Sat, 25 Nov 2017 11:24:39 +0100 Subject: [PATCH 01/11] Fix TimeFormatter for fractional seconds --- pandas/plotting/_converter.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pandas/plotting/_converter.py b/pandas/plotting/_converter.py index 3a3d871c043c4..58712d38f27b4 100644 --- a/pandas/plotting/_converter.py +++ b/pandas/plotting/_converter.py @@ -190,19 +190,20 @@ def __init__(self, locs): self.locs = locs def __call__(self, x, pos=0): - fmt = '%H:%M:%S' + fmt = '%H:%M:%S.%f' s = int(x) ms = int((x - s) * 1e3) - us = int((x - s) * 1e6 - ms) + us = int((x - s) * 1e6 - ms * 1e3) + msus = int((x - s) * 1e6) m, s = divmod(s, 60) h, m = divmod(m, 60) _, h = divmod(h, 24) if us != 0: - fmt += '.%6f' + return pydt.time(h, m, s, msus).strftime(fmt) elif ms != 0: - fmt += '.%3f' + return pydt.time(h, m, s, msus).strftime(fmt)[:-3] - return pydt.time(h, m, s, us).strftime(fmt) + return pydt.time(h, m, s).strftime('%H:%M:%S') # Period Conversion From 9a78cd77b94613fce30ffc85a66d671853ceb396 Mon Sep 17 00:00:00 2001 From: Nis Martensen Date: Tue, 28 Nov 2017 00:21:18 +0100 Subject: [PATCH 02/11] TimeFormatter: use round() for microseconds --- pandas/plotting/_converter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/plotting/_converter.py b/pandas/plotting/_converter.py index 58712d38f27b4..dac92618ffefd 100644 --- a/pandas/plotting/_converter.py +++ b/pandas/plotting/_converter.py @@ -192,9 +192,9 @@ def __init__(self, locs): def __call__(self, x, pos=0): fmt = '%H:%M:%S.%f' s = int(x) - ms = int((x - s) * 1e3) - us = int((x - s) * 1e6 - ms * 1e3) - msus = int((x - s) * 1e6) + msus = int(round((x - s) * 1e6)) + ms = int(msus / 1e3) + us = int(msus - ms * 1e3) m, s = divmod(s, 60) h, m = divmod(m, 60) _, h = divmod(h, 24) From c86238c291902d022fc5159df24e0197780058f2 Mon Sep 17 00:00:00 2001 From: Nis Martensen Date: Sat, 25 Nov 2017 11:27:09 +0100 Subject: [PATCH 03/11] TimeFormatter: Only show seconds if non-zero --- pandas/plotting/_converter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pandas/plotting/_converter.py b/pandas/plotting/_converter.py index dac92618ffefd..278cb258aa22a 100644 --- a/pandas/plotting/_converter.py +++ b/pandas/plotting/_converter.py @@ -202,8 +202,10 @@ def __call__(self, x, pos=0): return pydt.time(h, m, s, msus).strftime(fmt) elif ms != 0: return pydt.time(h, m, s, msus).strftime(fmt)[:-3] + elif s != 0: + return pydt.time(h, m, s).strftime('%H:%M:%S') - return pydt.time(h, m, s).strftime('%H:%M:%S') + return pydt.time(h, m).strftime('%H:%M') # Period Conversion From 91bf9b2fa14bd2b61f8b82a4fc8743e4ba8a09d7 Mon Sep 17 00:00:00 2001 From: Nis Martensen Date: Thu, 30 Nov 2017 00:28:31 +0100 Subject: [PATCH 04/11] Add some TimeFormatter test comparisons --- pandas/tests/plotting/test_converter.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/pandas/tests/plotting/test_converter.py b/pandas/tests/plotting/test_converter.py index 3818c04649366..9c552575e34c6 100644 --- a/pandas/tests/plotting/test_converter.py +++ b/pandas/tests/plotting/test_converter.py @@ -236,7 +236,24 @@ def test_conversion_outofbounds_datetime(self): assert rs == xp def test_time_formatter(self): - self.tc(90000) + # time2num(datetime.time.min) + rs = self.tc(0) + xp = '00:00' + assert rs == xp + # time2num(datetime.time.max) + rs = self.tc(86399.999999) + xp = '23:59:59.999999' + assert rs == xp + # some other times + rs = self.tc(90000) + xp = '01:00' + assert rs == xp + rs = self.tc(3723) + xp = '01:02:03' + assert rs == xp + rs = self.tc(39723.2) + xp = '11:02:03.200' + assert rs == xp def test_dateindex_conversion(self): decimals = 9 From e7d5b0dd328a97bc5b8f7dacbfeefcabb8402335 Mon Sep 17 00:00:00 2001 From: Nis Martensen Date: Fri, 15 Dec 2017 23:49:10 +0100 Subject: [PATCH 05/11] Add whatsnew entry --- doc/source/whatsnew/v0.22.0.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt index 9dc10a09378f8..624a77b33fd41 100644 --- a/doc/source/whatsnew/v0.22.0.txt +++ b/doc/source/whatsnew/v0.22.0.txt @@ -297,6 +297,7 @@ Plotting ^^^^^^^^ - :func: `DataFrame.plot` now raises a ``ValueError`` when the ``x`` or ``y`` argument is improperly formed (:issue:`18671`) +- Bug in formatting tick labels with datetime.time() and fractional seconds (:issue:`18478`). - - From 1a11153590936fd3f952a30b7f23b5e76bbe8791 Mon Sep 17 00:00:00 2001 From: Nis Martensen Date: Sat, 2 Dec 2017 18:05:42 +0100 Subject: [PATCH 06/11] simplify --- pandas/plotting/_converter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/plotting/_converter.py b/pandas/plotting/_converter.py index 278cb258aa22a..29624bb0cdbf7 100644 --- a/pandas/plotting/_converter.py +++ b/pandas/plotting/_converter.py @@ -193,8 +193,8 @@ def __call__(self, x, pos=0): fmt = '%H:%M:%S.%f' s = int(x) msus = int(round((x - s) * 1e6)) - ms = int(msus / 1e3) - us = int(msus - ms * 1e3) + ms = msus // 1000 + us = msus % 1000 m, s = divmod(s, 60) h, m = divmod(m, 60) _, h = divmod(h, 24) From d7fa51f5d1869e5ade80b291ec83d2eed3d10b4d Mon Sep 17 00:00:00 2001 From: Nis Martensen Date: Sat, 2 Dec 2017 20:30:56 +0100 Subject: [PATCH 07/11] TimeFormatter: Add __call__ docstring --- pandas/plotting/_converter.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pandas/plotting/_converter.py b/pandas/plotting/_converter.py index 29624bb0cdbf7..afbcbc37d203e 100644 --- a/pandas/plotting/_converter.py +++ b/pandas/plotting/_converter.py @@ -190,6 +190,15 @@ def __init__(self, locs): self.locs = locs def __call__(self, x, pos=0): + """ + Return the time of day as a formatted string. + + The time of day is specified by `x` as seconds since 00:00 + (midnight), with upto microsecond precision. + + In the returned HH:MM:SS.mmmuuu string, microseconds, + milliseconds and seconds are only displayed if non-zero. + """ fmt = '%H:%M:%S.%f' s = int(x) msus = int(round((x - s) * 1e6)) From 8157c351c040a517bbbe768d8f38797ea50dd29a Mon Sep 17 00:00:00 2001 From: Nis Martensen Date: Sat, 2 Dec 2017 20:36:43 +0100 Subject: [PATCH 08/11] test_converter::test_time_formatter: comment, formatting --- pandas/tests/plotting/test_converter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pandas/tests/plotting/test_converter.py b/pandas/tests/plotting/test_converter.py index 9c552575e34c6..cc275282436c8 100644 --- a/pandas/tests/plotting/test_converter.py +++ b/pandas/tests/plotting/test_converter.py @@ -236,14 +236,18 @@ def test_conversion_outofbounds_datetime(self): assert rs == xp def test_time_formatter(self): + # issue 18478 + # time2num(datetime.time.min) rs = self.tc(0) xp = '00:00' assert rs == xp + # time2num(datetime.time.max) rs = self.tc(86399.999999) xp = '23:59:59.999999' assert rs == xp + # some other times rs = self.tc(90000) xp = '01:00' From 75ae4f1e212fb656960cfe33a2ca5e53918fa9a7 Mon Sep 17 00:00:00 2001 From: Nis Martensen Date: Sat, 2 Dec 2017 22:16:30 +0100 Subject: [PATCH 09/11] test_datetimelike::test_time*: make testing tick labels actually work --- pandas/tests/plotting/test_datetimelike.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pandas/tests/plotting/test_datetimelike.py b/pandas/tests/plotting/test_datetimelike.py index e87c67a682d46..d971cc0508d30 100644 --- a/pandas/tests/plotting/test_datetimelike.py +++ b/pandas/tests/plotting/test_datetimelike.py @@ -1032,10 +1032,11 @@ def test_time(self): df = DataFrame({'a': np.random.randn(len(ts)), 'b': np.random.randn(len(ts))}, index=ts) - _, ax = self.plt.subplots() + fig, ax = self.plt.subplots() df.plot(ax=ax) # verify tick labels + fig.canvas.draw() ticks = ax.get_xticks() labels = ax.get_xticklabels() for t, l in zip(ticks, labels): @@ -1050,6 +1051,7 @@ def test_time(self): ax.set_xlim('1:30', '5:00') # check tick labels again + fig.canvas.draw() ticks = ax.get_xticks() labels = ax.get_xticklabels() for t, l in zip(ticks, labels): @@ -1069,10 +1071,11 @@ def test_time_musec(self): df = DataFrame({'a': np.random.randn(len(ts)), 'b': np.random.randn(len(ts))}, index=ts) - _, ax = self.plt.subplots() + fig, ax = self.plt.subplots() ax = df.plot(ax=ax) # verify tick labels + fig.canvas.draw() ticks = ax.get_xticks() labels = ax.get_xticklabels() for t, l in zip(ticks, labels): From 9b8bde484e31b02d41e60276bc9510c666427133 Mon Sep 17 00:00:00 2001 From: Nis Martensen Date: Sat, 2 Dec 2017 23:23:26 +0100 Subject: [PATCH 10/11] test_datetimelike::test_time: update expected results --- pandas/tests/plotting/test_datetimelike.py | 34 +++++++++++++++------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/pandas/tests/plotting/test_datetimelike.py b/pandas/tests/plotting/test_datetimelike.py index d971cc0508d30..aa27cb0f4ee1c 100644 --- a/pandas/tests/plotting/test_datetimelike.py +++ b/pandas/tests/plotting/test_datetimelike.py @@ -1042,9 +1042,12 @@ def test_time(self): for t, l in zip(ticks, labels): m, s = divmod(int(t), 60) h, m = divmod(m, 60) - xp = l.get_text() - if len(xp) > 0: - rs = time(h, m, s).strftime('%H:%M:%S') + rs = l.get_text() + if len(rs) > 0: + if s != 0: + xp = time(h, m, s).strftime('%H:%M:%S') + else: + xp = time(h, m, s).strftime('%H:%M') assert xp == rs # change xlim @@ -1057,9 +1060,12 @@ def test_time(self): for t, l in zip(ticks, labels): m, s = divmod(int(t), 60) h, m = divmod(m, 60) - xp = l.get_text() - if len(xp) > 0: - rs = time(h, m, s).strftime('%H:%M:%S') + rs = l.get_text() + if len(rs) > 0: + if s != 0: + xp = time(h, m, s).strftime('%H:%M:%S') + else: + xp = time(h, m, s).strftime('%H:%M') assert xp == rs @pytest.mark.slow @@ -1081,13 +1087,19 @@ def test_time_musec(self): for t, l in zip(ticks, labels): m, s = divmod(int(t), 60) - # TODO: unused? - # us = int((t - int(t)) * 1e6) + us = int(round((t - int(t)) * 1e6)) h, m = divmod(m, 60) - xp = l.get_text() - if len(xp) > 0: - rs = time(h, m, s).strftime('%H:%M:%S.%f') + rs = l.get_text() + if len(rs) > 0: + if (us % 1000) != 0: + xp = time(h, m, s, us).strftime('%H:%M:%S.%f') + elif (us // 1000) != 0: + xp = time(h, m, s, us).strftime('%H:%M:%S.%f')[:-3] + elif s != 0: + xp = time(h, m, s, us).strftime('%H:%M:%S') + else: + xp = time(h, m, s, us).strftime('%H:%M') assert xp == rs @pytest.mark.slow From c1ace3881de4c14728a1930cc6fc471b6fbac0af Mon Sep 17 00:00:00 2001 From: Nis Martensen Date: Sat, 9 Dec 2017 16:54:40 +0100 Subject: [PATCH 11/11] TimeFormatter: Docstring: add parameters and returns sections --- pandas/plotting/_converter.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/pandas/plotting/_converter.py b/pandas/plotting/_converter.py index afbcbc37d203e..2a45e20dda4cc 100644 --- a/pandas/plotting/_converter.py +++ b/pandas/plotting/_converter.py @@ -193,11 +193,19 @@ def __call__(self, x, pos=0): """ Return the time of day as a formatted string. - The time of day is specified by `x` as seconds since 00:00 - (midnight), with upto microsecond precision. - - In the returned HH:MM:SS.mmmuuu string, microseconds, - milliseconds and seconds are only displayed if non-zero. + Parameters + ---------- + x : float + The time of day specified as seconds since 00:00 (midnight), + with upto microsecond precision. + pos + Unused + + Returns + ------- + str + A string in HH:MM:SS.mmmuuu format. Microseconds, + milliseconds and seconds are only displayed if non-zero. """ fmt = '%H:%M:%S.%f' s = int(x)