Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: Handle iterable of arrays in convert #16026

Merged
merged 3 commits into from
Apr 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1565,6 +1565,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)])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

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