From 36053c518f5faa04275a461ca3263bf84c648290 Mon Sep 17 00:00:00 2001 From: acavalerie Date: Tue, 23 Jun 2026 09:47:44 +0200 Subject: [PATCH 1/7] =?UTF-8?q?=E2=9C=A8=20introduce=20apply=5Fcuts=20func?= =?UTF-8?q?tion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- corrai/base/math.py | 22 ++++++++++++++++++ tests/base/test_math.py | 49 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/corrai/base/math.py b/corrai/base/math.py index db8ae7d..75923c4 100644 --- a/corrai/base/math.py +++ b/corrai/base/math.py @@ -20,6 +20,19 @@ } +def apply_cuts(obj: pd.Series | pd.DataFrame, cuts): + if cuts is None: + return obj + if len(obj) == 0: + return ValueError("DataFrame is empty, cannot apply cuts.") + if obj.index.tz is not None: + obj.index = obj.index.tz_localize(None) + mask = pd.Series(False, index=obj.index) + for start, end in cuts: + mask |= (obj.index >= start) & (obj.index <= end) + return obj.loc[mask] + + def aggregate_time_series( results: pd.Series, indicator: str, @@ -28,6 +41,7 @@ def aggregate_time_series( reference_time_series: pd.Series = None, freq: str | pd.Timedelta | dt.timedelta = None, prefix: str = "aggregated", + cuts: list[tuple[str, str]] | None = None, ) -> pd.DataFrame: """ Aggregate time series data using a specified statistical or error metric. @@ -78,6 +92,10 @@ def aggregate_time_series( prefix : str, default="aggregated" Prefix to use for naming the output column when `freq` is not specified. + cuts : list[tuple[str, str]], optional + If provided, cut the time series based on these this list of cuts, before aggregation. + Aggregation is then done over the selected time periods only. + Returns ------- pandas.DataFrame @@ -141,8 +159,12 @@ def aggregate_time_series( check_datetime_index(df) agg_df = pd.concat([df[indicator].rename(i) for i, df in results.items()], axis=1) + agg_df = apply_cuts(agg_df, cuts) + agg_df = apply_cuts(agg_df, cuts) + if reference_time_series is not None: check_datetime_index(reference_time_series) + reference_time_series = apply_cuts(reference_time_series, cuts) if not agg_df.shape[0] == reference_time_series.shape[0]: raise ValueError( "Cannot perform aggregation, Dataframes in results and " diff --git a/tests/base/test_math.py b/tests/base/test_math.py index f37c8de..6242757 100644 --- a/tests/base/test_math.py +++ b/tests/base/test_math.py @@ -1,10 +1,57 @@ import pandas as pd import numpy as np -from corrai.base.math import aggregate_time_series +from corrai.base.math import aggregate_time_series, apply_cuts class TestMath: + def test_apply_cuts_series(self): + cuts = [ + ("2009-01-01 00:00:00", "2009-01-01 2:00:00"), + ("2009-01-01 05:00:00", "2009-01-01 07:00:00"), + ] + + index = pd.date_range("2009-01-01", freq="h", periods=8) + + # Test pd.Series + s = pd.Series(range(len(index)), index=index) + + result = apply_cuts(s, cuts) + + mask = pd.Series(False, index=s.index) + for start, end in cuts: + mask |= (s.index >= start) & (s.index <= end) + + pd.testing.assert_series_equal( + result, + s.loc[mask], + ) + + # Test pd.DataFrame + df = pd.DataFrame( + {"a": range(len(index)), "b": range(100, 100 + len(index))}, + index=index, + ) + + result = apply_cuts(df, cuts) + + mask = pd.Series(False, index=df.index) + for start, end in cuts: + mask |= (df.index >= start) & (df.index <= end) + + pd.testing.assert_frame_equal( + result, + df.loc[mask], + ) + + # Test cuts = None + result = apply_cuts(s, None) + + pd.testing.assert_series_equal( + result, + s, + ) + def test_aggregate_time_series(self): sim_res = pd.Series( [ From a3cff9c24d48d17fb4ffa2553cb9ba2c13a17003 Mon Sep 17 00:00:00 2001 From: acavalerie Date: Tue, 23 Jun 2026 15:11:15 +0200 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=90=9B=20drop=20duplicated=20line?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- corrai/base/math.py | 1 - 1 file changed, 1 deletion(-) diff --git a/corrai/base/math.py b/corrai/base/math.py index 75923c4..f5b50d5 100644 --- a/corrai/base/math.py +++ b/corrai/base/math.py @@ -159,7 +159,6 @@ def aggregate_time_series( check_datetime_index(df) agg_df = pd.concat([df[indicator].rename(i) for i, df in results.items()], axis=1) - agg_df = apply_cuts(agg_df, cuts) agg_df = apply_cuts(agg_df, cuts) if reference_time_series is not None: From a2601aa6c382c582b5cb205a3b3c871a33d3b4f4 Mon Sep 17 00:00:00 2001 From: acavalerie Date: Tue, 23 Jun 2026 16:09:52 +0200 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=93=9D=20update=20cuts=20description?= =?UTF-8?q?=20for=20more=20clarity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- corrai/base/math.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/corrai/base/math.py b/corrai/base/math.py index f5b50d5..22d201b 100644 --- a/corrai/base/math.py +++ b/corrai/base/math.py @@ -93,8 +93,8 @@ def aggregate_time_series( Prefix to use for naming the output column when `freq` is not specified. cuts : list[tuple[str, str]], optional - If provided, cut the time series based on these this list of cuts, before aggregation. - Aggregation is then done over the selected time periods only. + List of (start, end) time intervals (timezone-aware or unaware). + If provided, aggregation is performed only on data within these intervals. Returns ------- From 7ff4347f7c600d425f88bfaa8f3542ac616662b9 Mon Sep 17 00:00:00 2001 From: acavalerie Date: Tue, 23 Jun 2026 16:11:51 +0200 Subject: [PATCH 4/7] =?UTF-8?q?=E2=9C=85=20add=20a=20separated=20test=20fo?= =?UTF-8?q?r=20apply=5Fcuts=20on=20series?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/base/test_math.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/base/test_math.py b/tests/base/test_math.py index 6242757..372f918 100644 --- a/tests/base/test_math.py +++ b/tests/base/test_math.py @@ -13,7 +13,6 @@ def test_apply_cuts_series(self): index = pd.date_range("2009-01-01", freq="h", periods=8) - # Test pd.Series s = pd.Series(range(len(index)), index=index) result = apply_cuts(s, cuts) From 947fc2f157a0d01857030c17c3ed2a31d26ea103 Mon Sep 17 00:00:00 2001 From: acavalerie Date: Tue, 23 Jun 2026 16:12:08 +0200 Subject: [PATCH 5/7] =?UTF-8?q?=E2=9C=85=20add=20a=20separated=20test=20fo?= =?UTF-8?q?r=20apply=5Fcuts=20on=20dataframe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/base/test_math.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/base/test_math.py b/tests/base/test_math.py index 372f918..0a12348 100644 --- a/tests/base/test_math.py +++ b/tests/base/test_math.py @@ -26,7 +26,14 @@ def test_apply_cuts_series(self): s.loc[mask], ) - # Test pd.DataFrame + def test_apply_cuts_frame(self): + cuts = [ + ("2009-01-01 00:00:00", "2009-01-01 2:00:00"), + ("2009-01-01 05:00:00", "2009-01-01 07:00:00"), + ] + + index = pd.date_range("2009-01-01", freq="h", periods=8) + df = pd.DataFrame( {"a": range(len(index)), "b": range(100, 100 + len(index))}, index=index, From 52cb1f7bb143852384e06df45318ebac3b3ae393 Mon Sep 17 00:00:00 2001 From: acavalerie Date: Tue, 23 Jun 2026 16:12:48 +0200 Subject: [PATCH 6/7] =?UTF-8?q?=E2=9C=85=20add=20a=20separated=20test=20fo?= =?UTF-8?q?r=20apply=5Fcuts=20when=20None?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/base/test_math.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/base/test_math.py b/tests/base/test_math.py index 0a12348..6abf5a4 100644 --- a/tests/base/test_math.py +++ b/tests/base/test_math.py @@ -50,14 +50,26 @@ def test_apply_cuts_frame(self): df.loc[mask], ) - # Test cuts = None - result = apply_cuts(s, None) + def test_apply_cuts_none(self): + s = pd.Series(range(len(index)), index=index) + df = pd.DataFrame( + {"a": range(len(index)), "b": range(100, 100 + len(index))}, + index=index, + ) + + result_series = apply_cuts(s, None) + result_frame = apply_cuts(df, None) pd.testing.assert_series_equal( - result, + result_series, s, ) + pd.testing.assert_series_equal( + result_frame, + df, + ) + def test_aggregate_time_series(self): sim_res = pd.Series( [ From 08f570cfbfc30f65c84f9debced9fbdd0cd473dc Mon Sep 17 00:00:00 2001 From: acavalerie Date: Tue, 23 Jun 2026 18:09:58 +0200 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=90=9B=20fix=20test=5Fapply=5Fcuts=5F?= =?UTF-8?q?none?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/base/test_math.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/base/test_math.py b/tests/base/test_math.py index 6abf5a4..c739015 100644 --- a/tests/base/test_math.py +++ b/tests/base/test_math.py @@ -51,6 +51,7 @@ def test_apply_cuts_frame(self): ) def test_apply_cuts_none(self): + index = pd.date_range("2009-01-01", freq="h", periods=8) s = pd.Series(range(len(index)), index=index) df = pd.DataFrame( {"a": range(len(index)), "b": range(100, 100 + len(index))}, @@ -65,7 +66,7 @@ def test_apply_cuts_none(self): s, ) - pd.testing.assert_series_equal( + pd.testing.assert_frame_equal( result_frame, df, )