diff --git a/.gitignore b/.gitignore index 54a044db..68c468a8 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ docs/text/_generated/ # Virtual environment venv* +.venv/ activate # IPython notebooks diff --git a/tests/units/utilities/test_dataframe_functions.py b/tests/units/utilities/test_dataframe_functions.py index f1c4f7b4..dc77d71c 100644 --- a/tests/units/utilities/test_dataframe_functions.py +++ b/tests/units/utilities/test_dataframe_functions.py @@ -1469,6 +1469,43 @@ def test_make_forecasting_frame_feature_extraction(self): default_fc_parameters=MinimalFCParameters(), ) + def test_make_forecasting_frame_min_timeshift(self): + x = pd.Series([1, 2, 3, 4, 5]) + # Default behavior + df_default, y_default = dataframe_functions.make_forecasting_frame( + x=x, + kind="test", + max_timeshift=3, + rolling_direction=1, + ) + # With min_timeshift + df_filtered, y_filtered = dataframe_functions.make_forecasting_frame( + x=x, + kind="test", + max_timeshift=3, + rolling_direction=1, + min_timeshift=2, + ) + # check actual filtered df length + assert len(df_filtered) == len(df_default) - 1 + # Check target alignment + assert len(y_filtered) == df_filtered["id"].nunique() + # check df index matching + df_common_ids = set(df_filtered["id"]) + df_default_intersection = ( + df_default[df_default["id"].isin(df_common_ids)] + .sort_values(["id", "time"]) + .reset_index(drop=True) + ) + df_filtered = df_filtered.sort_values(["id", "time"]).reset_index(drop=True) + pd.testing.assert_frame_equal(df_default_intersection, df_filtered) + # check target index matching + y_common_index = y_filtered.index + y_default_intersection = y_default.loc[y_common_index] + pd.testing.assert_series_equal( + y_default_intersection.sort_index(), y_filtered.sort_index() + ) + class GetIDsTestCase(TestCase): def test_get_id__correct_DataFrame(self): diff --git a/tsfresh/utilities/dataframe_functions.py b/tsfresh/utilities/dataframe_functions.py index b0377180..f5b58d4b 100644 --- a/tsfresh/utilities/dataframe_functions.py +++ b/tsfresh/utilities/dataframe_functions.py @@ -603,7 +603,7 @@ def roll_time_series( return df_shift.sort_values(by=["id", column_sort or "sort"]) -def make_forecasting_frame(x, kind, max_timeshift, rolling_direction): +def make_forecasting_frame(x, kind, max_timeshift, rolling_direction, min_timeshift=0): """ Takes a singular time series x and constructs a DataFrame df and target vector y that can be used for a time series forecasting task. @@ -627,6 +627,8 @@ def make_forecasting_frame(x, kind, max_timeshift, rolling_direction): :type rolling_direction: int :param max_timeshift: If not None, shift only up to max_timeshift. If None, shift as often as possible. :type max_timeshift: int + :param min_timeshift: Minimum shift size to begin creating windows. Smaller windows are discarded. Default is 0. + :type min_timeshift: int :return: time series container df, target vector y :rtype: (pd.DataFrame, pd.Series) @@ -647,6 +649,7 @@ def make_forecasting_frame(x, kind, max_timeshift, rolling_direction): column_kind="kind", rolling_direction=rolling_direction, max_timeshift=max_timeshift, + min_timeshift=min_timeshift, ) # drop the rows which should actually be predicted @@ -670,6 +673,10 @@ def mask_first(x): # df_shift index y.index = map(lambda x: ("id", x), y.index) + # align y with df_shift ids + valid_ids = set(df_shift["id"].unique()) + y = y[y.index.isin(valid_ids)] + return df_shift, y