Skip to content

Commit 1ffb348

Browse files
committed
feat: enhance convert_index_to_timestamps to infer and set frequency for DatetimeIndex
1 parent 24a3211 commit 1ffb348

2 files changed

Lines changed: 74 additions & 0 deletions

File tree

pydsm/analysis/postpro.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ def convert_index_to_timestamps(df):
3535
if isinstance(df.index, pd.core.indexes.datetimes.DatetimeIndex):
3636
if df.index.dtype != "datetime64[ns]":
3737
df.index = df.index.astype("datetime64[ns]")
38+
if df.index.freq is None:
39+
inferred = pd.infer_freq(df.index)
40+
if inferred is not None:
41+
df.index.freq = pd.tseries.frequencies.to_offset(inferred)
3842
return
3943
df.index = df.index.astype("datetime64[ns]")
4044
df.index.freq = pd.infer_freq(df.index) # if possible

tests/test_postpro.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import pytest
33
import numpy as np
44
import pandas as pd
5+
from pydsm.analysis.postpro import convert_index_to_timestamps, merge
56

67

78
def test_postprocessor():
@@ -23,3 +24,72 @@ def test_postprocessor():
2324
assert p2.df.index[0] == pd.to_datetime("03OCT2016 0000")
2425
assert p2.df.index[-1] == pd.to_datetime("05OCT2016 0000")
2526
# assert_frame_equal(p.df, p2.df,check_names=False, check_column_type=False, check_like=False)
27+
28+
29+
# ---------------------------------------------------------------------------
30+
# convert_index_to_timestamps — datetime resolution normalisation
31+
# ---------------------------------------------------------------------------
32+
33+
class TestConvertIndexToTimestamps:
34+
"""Regression: convert_index_to_timestamps must coerce datetime64[us] to
35+
datetime64[ns] — previously it returned early without coercing because the
36+
index was already a DatetimeIndex."""
37+
38+
def _make(self, dtype, n=10):
39+
idx = pd.date_range("2020-01-01", periods=n, freq="15min").astype(dtype)
40+
return pd.DataFrame({"v": range(n)}, index=idx)
41+
42+
def test_ns_index_unchanged(self):
43+
df = self._make("datetime64[ns]")
44+
convert_index_to_timestamps(df)
45+
assert df.index.dtype == np.dtype("datetime64[ns]")
46+
47+
def test_us_index_coerced_to_ns(self):
48+
"""Before the fix this was a no-op; the index stayed as datetime64[us]."""
49+
df = self._make("datetime64[us]")
50+
convert_index_to_timestamps(df)
51+
assert df.index.dtype == np.dtype("datetime64[ns]"), (
52+
f"Expected datetime64[ns], got {df.index.dtype}"
53+
)
54+
55+
def test_period_index_converted(self):
56+
idx = pd.period_range("2020-01-01", periods=10, freq="15min")
57+
df = pd.DataFrame({"v": range(10)}, index=idx)
58+
convert_index_to_timestamps(df)
59+
assert isinstance(df.index, pd.DatetimeIndex)
60+
61+
62+
# ---------------------------------------------------------------------------
63+
# merge() — mixed datetime resolution (ns vs us)
64+
# ---------------------------------------------------------------------------
65+
66+
class TestMergeMixedResolution:
67+
"""merge() calls combine_first() which requires aligned indices.
68+
Both inputs must be normalised to the same resolution first."""
69+
70+
def _make(self, dtype, start="2020-01-01", n=20, col="v"):
71+
idx = pd.date_range(start, periods=n, freq="15min").astype(dtype)
72+
return pd.DataFrame({col: np.random.default_rng(0).random(n)}, index=idx)
73+
74+
def test_ns_plus_us_does_not_raise(self):
75+
df_ns = self._make("datetime64[ns]", start="2020-01-01")
76+
df_us = self._make("datetime64[us]", start="2020-01-01 05:00") # overlapping
77+
result = merge([df_ns, df_us])
78+
assert result is not None
79+
assert isinstance(result.index, pd.DatetimeIndex)
80+
assert result.index.dtype == np.dtype("datetime64[ns]")
81+
82+
def test_both_us_does_not_raise(self):
83+
df1 = self._make("datetime64[us]", start="2020-01-01")
84+
df2 = self._make("datetime64[us]", start="2020-01-01 05:00")
85+
result = merge([df1, df2])
86+
assert result is not None
87+
assert result.index.dtype == np.dtype("datetime64[ns]")
88+
89+
def test_result_spans_both_inputs(self):
90+
df_ns = self._make("datetime64[ns]", start="2020-01-01", n=4)
91+
df_us = self._make("datetime64[us]", start="2020-01-01 01:00", n=4)
92+
result = merge([df_ns, df_us])
93+
# Result should cover the full range of both inputs
94+
assert result.index.min() <= pd.Timestamp("2020-01-01")
95+
assert result.index.max() >= pd.Timestamp("2020-01-01 01:45")

0 commit comments

Comments
 (0)