Skip to content
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v0.21.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ I/O
Plotting
^^^^^^^^
- Bug in plotting methods using ``secondary_y`` and ``fontsize`` not setting secondary axis font size (:issue:`12565`)

- Bug when plotting ``timedelta`` and ``datetime`` dtypes on y-axis (:issue:`16953`)

Groupby/Resample/Rolling
^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
8 changes: 7 additions & 1 deletion pandas/plotting/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,13 @@ def _compute_plot_data(self):
label = 'None'
data = data.to_frame(name=label)

numeric_data = data._convert(datetime=True)._get_numeric_data()
# GH16953, _convert is needed as fallback, for ``Series``
# with ``dtype == object``
data = data._convert(datetime=True, timedelta=True)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need the _convert at all here. this is for object types that need to be coerced. almost all routines in pandas already assume this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The _convert is needed for a test, where a Series with numeric values and dtype object is used (tests.plotting.test_series.test_valid_object_plot).
If _convert isn't used, that test will fail in circleci.
To prevent the test from failing, _convert can either be used only on Series with dtype==object and after the to_frame (line 344), or on data in general after the Series specific part (line 346 as is now).

Copy link
Contributor

Choose a reason for hiding this comment

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

did you take out the _convert and see, on what input does it fail?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yap i did, circleci fails at tests.plotting.test_series.test_valid_object_plot, since the Series is of dtype == object (s = Series(lrange(10), dtype=object), also see comment on your last requested changes).
Also i think doing a general data._convert is more sturdy, than just calling in if the Seriesis of type object (which would be the minimal change for the test to pass).

Copy link
Contributor

Choose a reason for hiding this comment

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

ok I guess leave it in is ok. In general we don't automatically coerce object types (even if they are numbers) in other functions / places; we happen to do it in plotting for compat I think. Can you open a new issue for this to discuss (deprecating this).

numeric_data = data.select_dtypes(include=[np.number,
"datetime",
"datetimetz",
"timedelta"])

try:
is_empty = numeric_data.empty
Expand Down
75 changes: 75 additions & 0 deletions pandas/tests/plotting/test_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,81 @@ def test_subplots_timeseries(self):
self._check_ticks_props(ax, xlabelsize=7, xrot=45,
ylabelsize=7)

def test_subplots_timeseries_y_axis(self):
# GH16953
data = {"numeric": np.array([1, 2, 5]),
"timedelta": [pd.Timedelta(-10, unit="s"),
pd.Timedelta(10, unit="m"),
pd.Timedelta(10, unit="h")],
"datetime_no_tz": [pd.to_datetime("2017-08-01 00:00:00"),
pd.to_datetime("2017-08-01 02:00:00"),
pd.to_datetime("2017-08-02 00:00:00")],
"datetime_all_tz": [pd.to_datetime("2017-08-01 00:00:00",
utc=True),
pd.to_datetime("2017-08-01 02:00:00",
utc=True),
pd.to_datetime("2017-08-02 00:00:00",
utc=True)],
"text": ["This", "should", "fail"]}
testdata = DataFrame(data)

ax_numeric = testdata.plot(y="numeric")
assert (ax_numeric.get_lines()[0].get_data()[1] ==
testdata["numeric"].values).all()
ax_timedelta = testdata.plot(y="timedelta")
assert (ax_timedelta.get_lines()[0].get_data()[1] ==
testdata["timedelta"].values).all()
ax_datetime_no_tz = testdata.plot(y="datetime_no_tz")
assert (ax_datetime_no_tz.get_lines()[0].get_data()[1] ==
testdata["datetime_no_tz"].values).all()
ax_datetime_all_tz = testdata.plot(y="datetime_all_tz")
assert (ax_datetime_all_tz.get_lines()[0].get_data()[1] ==
testdata["datetime_all_tz"].values).all()
with pytest.raises(TypeError):
testdata.plot(y="text")

@pytest.mark.xfail
Copy link
Contributor

Choose a reason for hiding this comment

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

you need to have a reason='not support for period, categorical, datetime_mixed_tz') here

def test_subplots_timeseries_y_axis_not_supported(self):
"""
This test will fail for:
period:
since period isn't yet implemented in ``select_dtypes``
and because it will need a custom value converter +
tick formater (as was done for x-axis plots)

categorical:
because it will need a custom value converter +
tick formater (also doesn't work for x-axis, as of now)

datetime_mixed_tz:
because of the way how pandas handels ``Series`` of
``datetime`` objects with different timezone,
generally converting ``datetime`` objects in a tz-aware
form could help with this problem
"""
data = {"numeric": np.array([1, 2, 5]),
"period": [pd.Period('2017-08-01 00:00:00', freq='H'),
pd.Period('2017-08-01 02:00', freq='H'),
pd.Period('2017-08-02 00:00:00', freq='H')],
"categorical": pd.Categorical(["c", "b", "a"],
categories=["a", "b", "c"],
ordered=False),
"datetime_mixed_tz": [pd.to_datetime("2017-08-01 00:00:00",
utc=True),
pd.to_datetime("2017-08-01 02:00:00"),
pd.to_datetime("2017-08-02 00:00:00")]}
testdata = pd.DataFrame(data)
ax_period = testdata.plot(x="numeric", y="period")
assert (ax_period.get_lines()[0].get_data()[1] ==
testdata["period"].values).all()
ax_categorical = testdata.plot(x="numeric", y="categorical")
assert (ax_categorical.get_lines()[0].get_data()[1] ==
testdata["categorical"].values).all()
ax_datetime_mixed_tz = testdata.plot(x="numeric",
y="datetime_mixed_tz")
assert (ax_datetime_mixed_tz.get_lines()[0].get_data()[1] ==
testdata["datetime_mixed_tz"].values).all()

@pytest.mark.slow
def test_subplots_layout(self):
# GH 6667
Expand Down