Skip to content

Commit cd1031f

Browse files
TomAugspurgerjreback
authored andcommitted
BUG: Handle iterable of arrays in convert (#16026)
* BUG: Handle iterable of arrays in convert DatetimeConverter.convert can take an array or iterable of arrays. Fixed the converter to detect which case we're in and then re-use the existing logic.
1 parent a65492f commit cd1031f

File tree

6 files changed

+110
-2
lines changed

6 files changed

+110
-2
lines changed

doc/source/whatsnew/v0.20.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1566,6 +1566,7 @@ Plotting
15661566

15671567
- Bug in ``DataFrame.hist`` where ``plt.tight_layout`` caused an ``AttributeError`` (use ``matplotlib >= 2.0.1``) (:issue:`9351`)
15681568
- Bug in ``DataFrame.boxplot`` where ``fontsize`` was not applied to the tick labels on both axes (:issue:`15108`)
1569+
- Bug in the date and time converters pandas registers with matplotlib not handling multiple dimensions (:issue:`16026`)
15691570
- Bug in ``pd.scatter_matrix()`` could accept either ``color`` or ``c``, but not both (:issue:`14855`)
15701571

15711572
Groupby/Resample/Rolling

pandas/core/dtypes/inference.py

+44
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,50 @@ def is_list_like(obj):
273273
not isinstance(obj, string_and_binary_types))
274274

275275

276+
def is_nested_list_like(obj):
277+
"""
278+
Check if the object is list-like, and that all of its elements
279+
are also list-like.
280+
281+
.. versionadded:: 0.20.0
282+
283+
Parameters
284+
----------
285+
obj : The object to check.
286+
287+
Returns
288+
-------
289+
is_list_like : bool
290+
Whether `obj` has list-like properties.
291+
292+
Examples
293+
--------
294+
>>> is_nested_list_like([[1, 2, 3]])
295+
True
296+
>>> is_nested_list_like([{1, 2, 3}, {1, 2, 3}])
297+
True
298+
>>> is_nested_list_like(["foo"])
299+
False
300+
>>> is_nested_list_like([])
301+
False
302+
>>> is_nested_list_like([[1, 2, 3], 1])
303+
False
304+
305+
Notes
306+
-----
307+
This won't reliably detect whether a consumable iterator (e. g.
308+
a generator) is a nested-list-like without consuming the iterator.
309+
To avoid consuming it, we always return False if the outer container
310+
doesn't define `__len__`.
311+
312+
See Also
313+
--------
314+
is_list_like
315+
"""
316+
return (is_list_like(obj) and hasattr(obj, '__len__') and
317+
len(obj) > 0 and all(is_list_like(item) for item in obj))
318+
319+
276320
def is_dict_like(obj):
277321
"""
278322
Check if the object is dict-like.

pandas/plotting/_converter.py

+22-2
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
from matplotlib.ticker import Formatter, AutoLocator, Locator
1111
from matplotlib.transforms import nonsingular
1212

13-
1413
from pandas.core.dtypes.common import (
1514
is_float, is_integer,
1615
is_integer_dtype,
1716
is_float_dtype,
1817
is_datetime64_ns_dtype,
19-
is_period_arraylike)
18+
is_period_arraylike,
19+
is_nested_list_like
20+
)
2021

2122
from pandas.compat import lrange
2223
import pandas.compat as compat
@@ -127,6 +128,15 @@ class PeriodConverter(dates.DateConverter):
127128

128129
@staticmethod
129130
def convert(values, units, axis):
131+
if is_nested_list_like(values):
132+
values = [PeriodConverter._convert_1d(v, units, axis)
133+
for v in values]
134+
else:
135+
values = PeriodConverter._convert_1d(values, units, axis)
136+
return values
137+
138+
@staticmethod
139+
def _convert_1d(values, units, axis):
130140
if not hasattr(axis, 'freq'):
131141
raise TypeError('Axis must have `freq` set to convert to Periods')
132142
valid_types = (compat.string_types, datetime,
@@ -178,6 +188,16 @@ class DatetimeConverter(dates.DateConverter):
178188

179189
@staticmethod
180190
def convert(values, unit, axis):
191+
# values might be a 1-d array, or a list-like of arrays.
192+
if is_nested_list_like(values):
193+
values = [DatetimeConverter._convert_1d(v, unit, axis)
194+
for v in values]
195+
else:
196+
values = DatetimeConverter._convert_1d(values, unit, axis)
197+
return values
198+
199+
@staticmethod
200+
def _convert_1d(values, unit, axis):
181201
def try_parse(values):
182202
try:
183203
return _dt_to_float_ordinal(tools.to_datetime(values))

pandas/tests/dtypes/test_inference.py

+22
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from datetime import datetime, date, timedelta, time
1212
import numpy as np
1313
import pytz
14+
import pytest
1415

1516
import pandas as pd
1617
from pandas._libs import tslib, lib
@@ -66,6 +67,27 @@ def test_is_list_like():
6667
assert not inference.is_list_like(f)
6768

6869

70+
@pytest.mark.parametrize('inner', [
71+
[], [1], (1, ), (1, 2), {'a': 1}, set([1, 'a']), Series([1]),
72+
Series([]), Series(['a']).str, (x for x in range(5))
73+
])
74+
@pytest.mark.parametrize('outer', [
75+
list, Series, np.array, tuple
76+
])
77+
def test_is_nested_list_like_passes(inner, outer):
78+
result = outer([inner for _ in range(5)])
79+
assert inference.is_list_like(result)
80+
81+
82+
@pytest.mark.parametrize('obj', [
83+
'abc', [], [1], (1,), ['a'], 'a', {'a'},
84+
[1, 2, 3], Series([1]), DataFrame({"A": [1]}),
85+
([1, 2] for _ in range(5)),
86+
])
87+
def test_is_nested_list_like_fails(obj):
88+
assert not inference.is_nested_list_like(obj)
89+
90+
6991
def test_is_dict_like():
7092
passes = [{}, {'A': 1}, Series([1])]
7193
fails = ['1', 1, [1, 2], (1, 2), range(2), Index([1])]

pandas/tests/plotting/test_converter.py

+13
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,13 @@ def _assert_less(ts1, ts2):
138138
_assert_less(ts, ts + Milli())
139139
_assert_less(ts, ts + Micro(50))
140140

141+
def test_convert_nested(self):
142+
inner = [Timestamp('2017-01-01', Timestamp('2017-01-02'))]
143+
data = [inner, inner]
144+
result = self.dtc.convert(data, None, None)
145+
expected = [self.dtc.convert(x, None, None) for x in data]
146+
assert result == expected
147+
141148

142149
class TestPeriodConverter(tm.TestCase):
143150

@@ -196,3 +203,9 @@ def test_integer_passthrough(self):
196203
rs = self.pc.convert([0, 1], None, self.axis)
197204
xp = [0, 1]
198205
self.assertEqual(rs, xp)
206+
207+
def test_convert_nested(self):
208+
data = ['2012-1-1', '2012-1-2']
209+
r1 = self.pc.convert([data, data], None, self.axis)
210+
r2 = [self.pc.convert(data, None, self.axis) for _ in range(2)]
211+
assert r1 == r2

pandas/tests/plotting/test_datetimelike.py

+8
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,14 @@ def test_timedelta_plot(self):
13341334
s = Series(np.random.randn(len(index)), index)
13351335
_check_plot_works(s.plot)
13361336

1337+
def test_hist(self):
1338+
# https://github.com/matplotlib/matplotlib/issues/8459
1339+
rng = date_range('1/1/2011', periods=10, freq='H')
1340+
x = rng
1341+
w1 = np.arange(0, 1, .1)
1342+
w2 = np.arange(0, 1, .1)[::-1]
1343+
self.plt.hist([x, x], weights=[w1, w2])
1344+
13371345

13381346
def _check_plot_works(f, freq=None, series=None, *args, **kwargs):
13391347
import matplotlib.pyplot as plt

0 commit comments

Comments
 (0)