Skip to content

Commit

Permalink
BUG: Handle iterable of arrays in convert (#16026)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
TomAugspurger authored and jreback committed Apr 18, 2017
1 parent a65492f commit cd1031f
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 2 deletions.
1 change: 1 addition & 0 deletions doc/source/whatsnew/v0.20.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1566,6 +1566,7 @@ Plotting

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

Groupby/Resample/Rolling
Expand Down
44 changes: 44 additions & 0 deletions pandas/core/dtypes/inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,50 @@ def is_list_like(obj):
not isinstance(obj, string_and_binary_types))


def is_nested_list_like(obj):
"""
Check if the object is list-like, and that all of its elements
are also list-like.
.. versionadded:: 0.20.0
Parameters
----------
obj : The object to check.
Returns
-------
is_list_like : bool
Whether `obj` has list-like properties.
Examples
--------
>>> is_nested_list_like([[1, 2, 3]])
True
>>> is_nested_list_like([{1, 2, 3}, {1, 2, 3}])
True
>>> is_nested_list_like(["foo"])
False
>>> is_nested_list_like([])
False
>>> is_nested_list_like([[1, 2, 3], 1])
False
Notes
-----
This won't reliably detect whether a consumable iterator (e. g.
a generator) is a nested-list-like without consuming the iterator.
To avoid consuming it, we always return False if the outer container
doesn't define `__len__`.
See Also
--------
is_list_like
"""
return (is_list_like(obj) and hasattr(obj, '__len__') and
len(obj) > 0 and all(is_list_like(item) for item in obj))


def is_dict_like(obj):
"""
Check if the object is dict-like.
Expand Down
24 changes: 22 additions & 2 deletions pandas/plotting/_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
from matplotlib.ticker import Formatter, AutoLocator, Locator
from matplotlib.transforms import nonsingular


from pandas.core.dtypes.common import (
is_float, is_integer,
is_integer_dtype,
is_float_dtype,
is_datetime64_ns_dtype,
is_period_arraylike)
is_period_arraylike,
is_nested_list_like
)

from pandas.compat import lrange
import pandas.compat as compat
Expand Down Expand Up @@ -127,6 +128,15 @@ class PeriodConverter(dates.DateConverter):

@staticmethod
def convert(values, units, axis):
if is_nested_list_like(values):
values = [PeriodConverter._convert_1d(v, units, axis)
for v in values]
else:
values = PeriodConverter._convert_1d(values, units, axis)
return values

@staticmethod
def _convert_1d(values, units, axis):
if not hasattr(axis, 'freq'):
raise TypeError('Axis must have `freq` set to convert to Periods')
valid_types = (compat.string_types, datetime,
Expand Down Expand Up @@ -178,6 +188,16 @@ class DatetimeConverter(dates.DateConverter):

@staticmethod
def convert(values, unit, axis):
# values might be a 1-d array, or a list-like of arrays.
if is_nested_list_like(values):
values = [DatetimeConverter._convert_1d(v, unit, axis)
for v in values]
else:
values = DatetimeConverter._convert_1d(values, unit, axis)
return values

@staticmethod
def _convert_1d(values, unit, axis):
def try_parse(values):
try:
return _dt_to_float_ordinal(tools.to_datetime(values))
Expand Down
22 changes: 22 additions & 0 deletions pandas/tests/dtypes/test_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from datetime import datetime, date, timedelta, time
import numpy as np
import pytz
import pytest

import pandas as pd
from pandas._libs import tslib, lib
Expand Down Expand Up @@ -66,6 +67,27 @@ def test_is_list_like():
assert not inference.is_list_like(f)


@pytest.mark.parametrize('inner', [
[], [1], (1, ), (1, 2), {'a': 1}, set([1, 'a']), Series([1]),
Series([]), Series(['a']).str, (x for x in range(5))
])
@pytest.mark.parametrize('outer', [
list, Series, np.array, tuple
])
def test_is_nested_list_like_passes(inner, outer):
result = outer([inner for _ in range(5)])
assert inference.is_list_like(result)


@pytest.mark.parametrize('obj', [
'abc', [], [1], (1,), ['a'], 'a', {'a'},
[1, 2, 3], Series([1]), DataFrame({"A": [1]}),
([1, 2] for _ in range(5)),
])
def test_is_nested_list_like_fails(obj):
assert not inference.is_nested_list_like(obj)


def test_is_dict_like():
passes = [{}, {'A': 1}, Series([1])]
fails = ['1', 1, [1, 2], (1, 2), range(2), Index([1])]
Expand Down
13 changes: 13 additions & 0 deletions pandas/tests/plotting/test_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ def _assert_less(ts1, ts2):
_assert_less(ts, ts + Milli())
_assert_less(ts, ts + Micro(50))

def test_convert_nested(self):
inner = [Timestamp('2017-01-01', Timestamp('2017-01-02'))]
data = [inner, inner]
result = self.dtc.convert(data, None, None)
expected = [self.dtc.convert(x, None, None) for x in data]
assert result == expected


class TestPeriodConverter(tm.TestCase):

Expand Down Expand Up @@ -196,3 +203,9 @@ def test_integer_passthrough(self):
rs = self.pc.convert([0, 1], None, self.axis)
xp = [0, 1]
self.assertEqual(rs, xp)

def test_convert_nested(self):
data = ['2012-1-1', '2012-1-2']
r1 = self.pc.convert([data, data], None, self.axis)
r2 = [self.pc.convert(data, None, self.axis) for _ in range(2)]
assert r1 == r2
8 changes: 8 additions & 0 deletions pandas/tests/plotting/test_datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -1334,6 +1334,14 @@ def test_timedelta_plot(self):
s = Series(np.random.randn(len(index)), index)
_check_plot_works(s.plot)

def test_hist(self):
# https://github.com/matplotlib/matplotlib/issues/8459
rng = date_range('1/1/2011', periods=10, freq='H')
x = rng
w1 = np.arange(0, 1, .1)
w2 = np.arange(0, 1, .1)[::-1]
self.plt.hist([x, x], weights=[w1, w2])


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

0 comments on commit cd1031f

Please sign in to comment.