Skip to content

Commit 67d49a4

Browse files
update is_regular and add test for it
1 parent 91175b2 commit 67d49a4

File tree

2 files changed

+155
-4
lines changed

2 files changed

+155
-4
lines changed

tests/test_timeseries.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
import pandas as pd
5+
import numpy as np
6+
from vtools.data.timeseries import is_regular
7+
8+
# Irregular DatetimeIndex Series
9+
irreg_datetime_series = pd.Series(
10+
data=[1, 2, 3, 4],
11+
index=pd.DatetimeIndex(
12+
[
13+
pd.Timestamp("2025-01-01 00:00"),
14+
pd.Timestamp("2025-01-01 01:00"),
15+
pd.Timestamp("2025-01-01 03:00"), # Gap: should be 02:00
16+
pd.Timestamp("2025-01-01 04:00"),
17+
]
18+
),
19+
)
20+
21+
22+
# Regular DatetimeIndex Series
23+
reg_datetime_series = pd.Series(
24+
data=[1, 2, 3, 4],
25+
index=pd.DatetimeIndex(
26+
[
27+
pd.Timestamp("2025-01-01 00:00"),
28+
pd.Timestamp("2025-01-01 01:00"),
29+
pd.Timestamp("2025-01-01 02:00"),
30+
pd.Timestamp("2025-01-01 03:00"),
31+
]
32+
),
33+
)
34+
35+
# Irregular Float Index Series
36+
irreg_float_series = pd.Series(
37+
data=[1, 2, 3, 4],
38+
index=pd.Index(
39+
[0.0, 90.0, 180.0, 300.0], dtype="float64"
40+
), # Last diff is 120, not 90
41+
)
42+
43+
# Regular Float Index Series
44+
reg_float_series = pd.Series(
45+
data=[1, 2, 3, 4],
46+
index=pd.Index([0.0, 90.0, 180.0, 270.0], dtype="float64"), # All diffs are 90.0
47+
)
48+
49+
50+
# Irregular Int Index Series
51+
irreg_int_series = pd.Series(
52+
data=[1, 2, 3, 4],
53+
index=pd.Index([0, 100, 200, 350], dtype="int64"), # Last diff is 150, not 100
54+
)
55+
56+
# Regular Int Index Series
57+
reg_int_series = pd.Series(
58+
data=[1, 2, 3, 4],
59+
index=pd.Index([0, 100, 200, 300], dtype="int64"), # All diffs are 100
60+
)
61+
62+
63+
# ============================================================================
64+
# Tests
65+
# ============================================================================
66+
def test_irregular_datetime_series():
67+
"""Test that irregular datetime series is identified as not regular"""
68+
assert is_regular(irreg_datetime_series) is False
69+
70+
71+
def test_regular_datetime_series():
72+
"""Test that regular datetime series is identified as regular"""
73+
assert is_regular(reg_datetime_series) is True
74+
75+
76+
def test_irregular_float_series():
77+
"""Test that irregular float index series is identified as not regular"""
78+
assert is_regular(irreg_float_series) is False
79+
80+
81+
def test_regular_float_series():
82+
"""Test that regular float index series is identified as regular"""
83+
assert is_regular(reg_float_series) is True
84+
85+
86+
def test_irregular_int_series():
87+
"""Test that irregular int index series is identified as not regular"""
88+
assert is_regular(irreg_int_series) is False
89+
90+
91+
def test_regular_int_series():
92+
"""Test that regular int index series is identified as regular"""
93+
assert is_regular(reg_int_series) is True
94+
95+
96+
def test_irregular_datetime_raises():
97+
"""Test that raise_exception=True raises for irregular datetime"""
98+
try:
99+
is_regular(irreg_datetime_series, raise_exception=True)
100+
assert False, "Should have raised ValueError"
101+
except ValueError:
102+
pass
103+
104+
105+
def test_regular_datetime_raises():
106+
"""Test that raise_exception=True does not raise for regular datetime"""
107+
result = is_regular(reg_datetime_series, raise_exception=True)
108+
assert result is True
109+
110+
111+
def test_irregular_float_raises():
112+
"""Test that raise_exception=True raises for irregular float"""
113+
try:
114+
is_regular(irreg_float_series, raise_exception=True)
115+
assert False, "Should have raised ValueError"
116+
except ValueError:
117+
pass
118+
119+
120+
def test_regular_float_raises():
121+
"""Test that raise_exception=True does not raise for regular float"""
122+
result = is_regular(reg_float_series, raise_exception=True)
123+
assert result is True
124+
125+
126+
def test_irregular_int_raises():
127+
"""Test that raise_exception=True raises for irregular int"""
128+
try:
129+
is_regular(irreg_int_series, raise_exception=True)
130+
assert False, "Should have raised ValueError"
131+
except ValueError:
132+
pass
133+
134+
135+
def test_regular_int_raises():
136+
"""Test that raise_exception=True does not raise for regular int"""
137+
result = is_regular(reg_int_series, raise_exception=True)
138+
assert result is True

vtools/data/timeseries.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -502,12 +502,12 @@ def elapsed_datetime(index_or_ts, reftime=None, time_unit="s", inplace=False):
502502
def is_regular(ts, raise_exception=False):
503503
"""
504504
Check if a pandas DataFrame, Series, or xarray object with a time axis (axis 0)
505-
has a regular time index.
505+
has a regular index.
506506
507507
Regular means:
508508
- The index is unique.
509-
- The index equals a date_range spanning from the first to the last value with
510-
the inferred frequency.
509+
- For DatetimeIndex: equals a date_range with the inferred frequency.
510+
- For numeric index (int/float): differences between consecutive values are constant.
511511
512512
Parameters:
513513
ts : DataFrame, Series, or xarray object.
@@ -518,7 +518,7 @@ def is_regular(ts, raise_exception=False):
518518
Otherwise, returns False.
519519
520520
Returns:
521-
bool : True if the time index is regular; False otherwise.
521+
bool : True if the index is regular; False otherwise.
522522
"""
523523
# Determine the index from the object
524524
if hasattr(ts, "index"):
@@ -549,6 +549,19 @@ def is_regular(ts, raise_exception=False):
549549
raise ValueError(msg)
550550
return False
551551

552+
# Handle numeric indices (int, float)
553+
if isinstance(idx, (pd.Index, pd.RangeIndex)) and np.issubdtype(idx.dtype, np.number):
554+
# Calculate differences between consecutive values
555+
diffs = np.diff(idx.values)
556+
# Check if all differences are equal (within floating point tolerance)
557+
if np.allclose(diffs, diffs[0]):
558+
return True
559+
else:
560+
msg = "Numeric index has non-constant differences; it is not regular."
561+
if raise_exception:
562+
raise ValueError(msg)
563+
return False
564+
552565
# Ensure we are working with a DatetimeIndex. If not, attempt conversion.
553566
if not isinstance(idx, pd.DatetimeIndex):
554567
try:

0 commit comments

Comments
 (0)