Skip to content
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
43 changes: 26 additions & 17 deletions pandas/plotting/_matplotlib/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,22 +224,29 @@ def __call__(self, x, pos: int | None = 0) -> str:

class PeriodConverter(mdates.DateConverter):
@staticmethod
def convert(values, units, axis):
def convert(values, unit, axis: Axis):
# Reached via e.g. `ax.set_xlim`

# In tests as of 2025-09-24, unit is always None except for 3 tests
# that directly call this with unit="";
# axis is always specifically a matplotlib.axis.XAxis

if not hasattr(axis, "freq"):
raise TypeError("Axis must have `freq` set to convert to Periods")
return PeriodConverter.convert_from_freq(values, axis.freq)
freq = to_offset(axis.freq, is_period=True)
return PeriodConverter.convert_from_freq(values, freq)

@staticmethod
def convert_from_freq(values, freq):
def convert_from_freq(values, freq: BaseOffset):
if is_nested_list_like(values):
values = [PeriodConverter._convert_1d(v, freq) for v in values]
else:
values = PeriodConverter._convert_1d(values, freq)
return values

@staticmethod
def _convert_1d(values, freq):
valid_types = (str, datetime, Period, pydt.date, pydt.time, np.datetime64)
def _convert_1d(values, freq: BaseOffset):
valid_types = (str, datetime, Period, pydt.date, np.datetime64)
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", "Period with BDay freq is deprecated", category=FutureWarning
Expand All @@ -252,30 +259,26 @@ def _convert_1d(values, freq):
or is_integer(values)
or is_float(values)
):
return get_datevalue(values, freq)
return _get_datevalue(values, freq)
elif isinstance(values, PeriodIndex):
return values.asfreq(freq).asi8
elif isinstance(values, Index):
return values.map(lambda x: get_datevalue(x, freq))
return values.map(lambda x: _get_datevalue(x, freq))
elif lib.infer_dtype(values, skipna=False) == "period":
# https://github.com/pandas-dev/pandas/issues/24304
# convert ndarray[period] -> PeriodIndex
return PeriodIndex(values, freq=freq).asi8
elif isinstance(values, (list, tuple, np.ndarray, Index)):
return [get_datevalue(x, freq) for x in values]
elif isinstance(values, (list, tuple, np.ndarray)):
return [_get_datevalue(x, freq) for x in values]
return values


def get_datevalue(date, freq):
def _get_datevalue(date, freq: BaseOffset):
if isinstance(date, Period):
return date.asfreq(freq).ordinal
elif isinstance(date, (str, datetime, pydt.date, pydt.time, np.datetime64)):
elif isinstance(date, (str, datetime, pydt.date, np.datetime64)):
return Period(date, freq).ordinal
elif (
is_integer(date)
or is_float(date)
or (isinstance(date, (np.ndarray, Index)) and (date.size == 1))
):
elif is_integer(date) or is_float(date):
return date
elif date is None:
return None
Expand All @@ -285,7 +288,13 @@ def get_datevalue(date, freq):
# Datetime Conversion
class DatetimeConverter(mdates.DateConverter):
@staticmethod
def convert(values, unit, axis):
def convert(values, unit, axis: Axis):
# Reached via e.g. `ax.set_xlim`

# In tests as of 2025-09-24, unit is always None except for 3 tests
# that directly call this with unit="";
# axis is always specifically a matplotlib.axis.XAxis

# 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]
Expand Down
21 changes: 3 additions & 18 deletions pandas/plotting/_matplotlib/timeseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@
from typing import (
TYPE_CHECKING,
Any,
cast,
)
import warnings

import numpy as np

from pandas._libs.tslibs import (
BaseOffset,
Period,
Expand Down Expand Up @@ -265,27 +262,15 @@ def _get_index_freq(index: Index) -> BaseOffset | None:
freq = getattr(index, "freq", None)
if freq is None:
freq = getattr(index, "inferred_freq", None)
if freq == "B":
# error: "Index" has no attribute "dayofweek"
weekdays = np.unique(index.dayofweek) # type: ignore[attr-defined]
if (5 in weekdays) or (6 in weekdays):
freq = None

freq = to_offset(freq)
freq = to_offset(freq)
return freq


def maybe_convert_index(ax: Axes, data: NDFrameT) -> NDFrameT:
# tsplot converts automatically, but don't want to convert index
# over and over for DataFrames
if isinstance(data.index, (ABCDatetimeIndex, ABCPeriodIndex)):
freq: str | BaseOffset | None = data.index.freq

if freq is None:
# We only get here for DatetimeIndex
data.index = cast("DatetimeIndex", data.index)
freq = data.index.inferred_freq
freq = to_offset(freq)
freq = _get_index_freq(data.index)

if freq is None:
freq = _get_ax_freq(ax)
Expand Down Expand Up @@ -315,7 +300,7 @@ def maybe_convert_index(ax: Axes, data: NDFrameT) -> NDFrameT:
# Patch methods for subplot.


def _format_coord(freq, t, y) -> str:
def _format_coord(freq: BaseOffset, t, y) -> str:
time_period = Period(ordinal=int(t), freq=freq)
return f"t = {time_period} y = {y:8f}"

Expand Down
9 changes: 5 additions & 4 deletions pandas/tests/plotting/test_datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,13 @@ def test_high_freq(self, freq):
_check_plot_works(ser.plot, ax=ax)

def test_get_datevalue(self):
assert conv.get_datevalue(None, "D") is None
assert conv.get_datevalue(1987, "Y") == 1987
assert conv._get_datevalue(None, "D") is None
assert conv._get_datevalue(1987, "Y") == 1987
assert (
conv.get_datevalue(Period(1987, "Y"), "M") == Period("1987-12", "M").ordinal
conv._get_datevalue(Period(1987, "Y"), "M")
== Period("1987-12", "M").ordinal
)
assert conv.get_datevalue("1/1/1987", "D") == Period("1987-1-1", "D").ordinal
assert conv._get_datevalue("1/1/1987", "D") == Period("1987-1-1", "D").ordinal

@pytest.mark.parametrize(
"freq, expected_string",
Expand Down
Loading