Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion datasieve/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def __init__(self, steps: List[Tuple] = [],
self.fitparams: Dict[str, dict] = self._validate_fitparams(fitparams, steps)
self.pandas_types: bool = False
self.feature_list: list = []
self.features_in: list = []
self.label_list: list = []
self.step_strings: list = []

Expand Down Expand Up @@ -164,7 +165,14 @@ def _validate_arguments(self, X, y, sample_weight, fit=False, outlier_check=Fals
else:
self.label_list = [0]
elif isinstance(X, pd.DataFrame) and not fit:
if list(X.columns) != list(self.features_in):
# If features_in is empty (pipeline loaded from cached state or
# never fit'd with DataFrame), populate from first call as a
# graceful fallback. Otherwise, validate.
if not list(self.features_in):
self.features_in = X.columns
self.feature_list = X.columns
self.pandas_types = True
elif list(X.columns) != list(self.features_in):
raise Exception(f"Pipeline expected {self.features_in} but got {X.columns}.")
elif not isinstance(X, pd.DataFrame) and not fit and self.pandas_types:
X = pd.DataFrame(X, columns=self.features_in)
Expand Down
34 changes: 34 additions & 0 deletions tests/test_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,37 @@ def test_getitem(dummy_array_without_nans, dummy_array2_without_nans):
di_values = pipeline["di"].di_values

assert di_values.shape[0] == 100


def test_pipeline_features_in_initialized_empty(dummy_df_without_nans):
"""
Pipeline.features_in must be initialized as empty list (not undefined).
Regression test for issue #13167 (freqtrade): predict before fit raised
AttributeError because features_in did not exist.
"""
pipeline = Pipeline([])
# Should not raise AttributeError
assert pipeline.features_in == []
assert pipeline.feature_list == []


def test_pipeline_transform_without_prior_fit_graceful(dummy_df_without_nans):
"""
If pipeline.transform() is called and features_in is empty (e.g. pipeline
loaded from cached state where attribute didn't persist), the validation
should gracefully populate from the first call instead of raising.

Regression test for issue #13167 (freqtrade).
"""
import datasieve.transforms as ts
pipeline = Pipeline([("detect_constants", ts.VarianceThreshold(threshold=0))])

df = dummy_df_without_nans.copy()
Xdf, ydf = extract_features_and_labels(df)

# Simulate "pipeline loaded fresh" state (features_in empty)
assert pipeline.features_in == []

# transform should populate features_in instead of raising
Xdf_t, ydf_t, _ = pipeline.transform(Xdf, ydf)
assert list(pipeline.features_in) == list(Xdf.columns)