Skip to content

Commit 4e1abd8

Browse files
committed
Refactor time series plotting logic for improved clarity
Extract and streamline time series preparation steps into `prepare_ts_data`, replacing redundant logic across methods. Simplifies axis frequency handling and improves code readability while maintaining functionality.
1 parent dc8401a commit 4e1abd8

File tree

2 files changed

+47
-35
lines changed

2 files changed

+47
-35
lines changed

pandas/plotting/_matplotlib/core.py

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
)
5555
from pandas.core.dtypes.missing import isna
5656

57+
from pandas import Series
5758
import pandas.core.common as com
5859
from pandas.util.version import Version
5960

@@ -64,10 +65,9 @@
6465
from pandas.plotting._matplotlib.misc import unpack_single_str_list
6566
from pandas.plotting._matplotlib.style import get_standard_colors
6667
from pandas.plotting._matplotlib.timeseries import (
67-
decorate_axes,
6868
format_dateaxis,
6969
maybe_convert_index,
70-
maybe_resample,
70+
prepare_ts_data,
7171
use_dynamic_x,
7272
)
7373
from pandas.plotting._matplotlib.tools import (
@@ -95,7 +95,6 @@
9595
from pandas import (
9696
DataFrame,
9797
Index,
98-
Series,
9998
)
10099

101100

@@ -288,6 +287,21 @@ def __init__(
288287

289288
self.data = self._ensure_frame(self.data)
290289

290+
from pandas.plotting import plot_params
291+
292+
self.x_compat = plot_params["x_compat"]
293+
if "x_compat" in self.kwds:
294+
self.x_compat = bool(self.kwds.pop("x_compat"))
295+
296+
@final
297+
def _is_ts_plot(self) -> bool:
298+
# this is slightly deceptive
299+
return not self.x_compat and self.use_index and self._use_dynamic_x()
300+
301+
@final
302+
def _use_dynamic_x(self) -> bool:
303+
return use_dynamic_x(self._get_ax(0), self.data.index)
304+
291305
@final
292306
@staticmethod
293307
def _validate_sharex(sharex: bool | None, ax, by) -> bool:
@@ -1324,10 +1338,17 @@ def __init__(
13241338
c = self.data.columns[c]
13251339
self.c = c
13261340

1341+
@register_pandas_matplotlib_converters
13271342
def _make_plot(self, fig: Figure) -> None:
13281343
x, y, c, data = self.x, self.y, self.c, self.data
13291344
ax = self.axes[0]
13301345

1346+
x_data = Series(index=data[x])
1347+
if use_dynamic_x(ax, x_data.index):
1348+
x_data = maybe_convert_index(ax, x_data)
1349+
freq, x_data = prepare_ts_data(x_data, ax, self.kwds)
1350+
x_data = x_data.index
1351+
13311352
c_is_column = is_hashable(c) and c in self.data.columns
13321353

13331354
color_by_categorical = c_is_column and isinstance(
@@ -1344,7 +1365,7 @@ def _make_plot(self, fig: Figure) -> None:
13441365
else:
13451366
label = None
13461367

1347-
# if a list of non color strings is passed in as c, color points
1368+
# if a list of non-color strings is passed in as c, color points
13481369
# by uniqueness of the strings, such same strings get same color
13491370
create_colors = not self._are_valid_colors(c_values)
13501371
if create_colors:
@@ -1360,7 +1381,7 @@ def _make_plot(self, fig: Figure) -> None:
13601381
)
13611382

13621383
scatter = ax.scatter(
1363-
data[x].values,
1384+
x_data.values,
13641385
data[y].values,
13651386
c=c_values,
13661387
label=label,
@@ -1520,23 +1541,9 @@ def _kind(self) -> Literal["line", "area", "hist", "kde", "box"]:
15201541
return "line"
15211542

15221543
def __init__(self, data, **kwargs) -> None:
1523-
from pandas.plotting import plot_params
1524-
15251544
MPLPlot.__init__(self, data, **kwargs)
15261545
if self.stacked:
15271546
self.data = self.data.fillna(value=0)
1528-
self.x_compat = plot_params["x_compat"]
1529-
if "x_compat" in self.kwds:
1530-
self.x_compat = bool(self.kwds.pop("x_compat"))
1531-
1532-
@final
1533-
def _is_ts_plot(self) -> bool:
1534-
# this is slightly deceptive
1535-
return not self.x_compat and self.use_index and self._use_dynamic_x()
1536-
1537-
@final
1538-
def _use_dynamic_x(self) -> bool:
1539-
return use_dynamic_x(self._get_ax(0), self.data)
15401547

15411548
def _make_plot(self, fig: Figure) -> None:
15421549
if self._is_ts_plot():
@@ -1626,15 +1633,8 @@ def _ts_plot(self, ax: Axes, x, data: Series, style=None, **kwds):
16261633
# accept x to be consistent with normal plot func,
16271634
# x is not passed to tsplot as it uses data.index as x coordinate
16281635
# column_num must be in kwds for stacking purpose
1629-
freq, data = maybe_resample(data, ax, kwds)
1636+
freq, data = prepare_ts_data(data, ax, kwds)
16301637

1631-
# Set ax with freq info
1632-
decorate_axes(ax, freq)
1633-
# digging deeper
1634-
if hasattr(ax, "left_ax"):
1635-
decorate_axes(ax.left_ax, freq)
1636-
if hasattr(ax, "right_ax"):
1637-
decorate_axes(ax.right_ax, freq)
16381638
# TODO #54485
16391639
ax._plot_data.append((data, self._kind, kwds)) # type: ignore[attr-defined]
16401640

pandas/plotting/_matplotlib/timeseries.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
from pandas._typing import NDFrameT
4949

5050
from pandas import (
51-
DataFrame,
5251
DatetimeIndex,
5352
Index,
5453
PeriodIndex,
@@ -231,8 +230,8 @@ def _get_freq(ax: Axes, series: Series):
231230
return freq, ax_freq
232231

233232

234-
def use_dynamic_x(ax: Axes, data: DataFrame | Series) -> bool:
235-
freq = _get_index_freq(data.index)
233+
def use_dynamic_x(ax: Axes, index: Index) -> bool:
234+
freq = _get_index_freq(index)
236235
ax_freq = _get_ax_freq(ax)
237236

238237
if freq is None: # convert irregular if axes has freq info
@@ -250,16 +249,15 @@ def use_dynamic_x(ax: Axes, data: DataFrame | Series) -> bool:
250249
return False
251250

252251
# FIXME: hack this for 0.10.1, creating more technical debt...sigh
253-
if isinstance(data.index, ABCDatetimeIndex):
252+
if isinstance(index, ABCDatetimeIndex):
254253
# error: "BaseOffset" has no attribute "_period_dtype_code"
255254
freq_str = OFFSET_TO_PERIOD_FREQSTR.get(freq_str, freq_str)
256255
base = to_offset(freq_str, is_period=True)._period_dtype_code # type: ignore[attr-defined]
257-
x = data.index
258256
if base <= FreqGroup.FR_DAY.value:
259-
return x[:1].is_normalized
260-
period = Period(x[0], freq_str)
257+
return index[:1].is_normalized
258+
period = Period(index[0], freq_str)
261259
assert isinstance(period, Period)
262-
return period.to_timestamp().tz_localize(x.tz) == x[0]
260+
return period.to_timestamp().tz_localize(index.tz) == index[0]
263261
return True
264262

265263

@@ -366,3 +364,17 @@ def format_dateaxis(
366364
raise TypeError("index type not supported")
367365

368366
plt.draw_if_interactive()
367+
368+
369+
def prepare_ts_data(data, ax, kwds):
370+
freq, data = maybe_resample(data, ax, kwds)
371+
372+
# Set ax with freq info
373+
decorate_axes(ax, freq)
374+
# digging deeper
375+
if hasattr(ax, "left_ax"):
376+
decorate_axes(ax.left_ax, freq)
377+
if hasattr(ax, "right_ax"):
378+
decorate_axes(ax.right_ax, freq)
379+
380+
return freq, data

0 commit comments

Comments
 (0)