diff --git a/DashAI/back/converters/category/feature_selection.py b/DashAI/back/converters/category/feature_selection.py index 9b7171433..f090ae69a 100644 --- a/DashAI/back/converters/category/feature_selection.py +++ b/DashAI/back/converters/category/feature_selection.py @@ -1,8 +1,12 @@ -from typing import Final +from typing import TYPE_CHECKING, Final, Union from DashAI.back.converters.base_converter import BaseConverter from DashAI.back.core.utils import MultilingualString from DashAI.back.static.icons import Icon +from DashAI.back.types.dashai_data_type import DashAIDataType + +if TYPE_CHECKING: + from DashAI.back.dataloaders.classes.dashai_dataset import DashAIDataset class FeatureSelectionConverter(BaseConverter): @@ -15,6 +19,10 @@ class FeatureSelectionConverter(BaseConverter): Use these converters to reduce overfitting, speed up training, and improve model interpretability by retaining only the most informative features. + + These converters only drop columns; the retained columns keep their + original values untouched, so their data types must be preserved instead of + being coerced to float. """ CATEGORY = MultilingualString( @@ -26,3 +34,57 @@ class FeatureSelectionConverter(BaseConverter): ) ICON: Final[str] = Icon.FilterList.value COLOR: Final[str] = "rgb(255, 206, 86)" + + def fit( + self, x: "DashAIDataset", y: Union["DashAIDataset", None] = None + ) -> "FeatureSelectionConverter": + """Fit the selector while remembering the input column types. + + Feature selection only keeps a subset of the input columns without + modifying their values, so the original types are captured here to be + returned later by ``get_output_type``. Types are recorded during ``fit`` + (rather than ``transform``) because scikit-learn auto-wraps ``transform`` + on subclasses and would coerce its output back to a pandas DataFrame. + + Parameters + ---------- + x : DashAIDataset + The input dataset to fit the selector on. + y : DashAIDataset, optional + Target values for the supervised selectors. Defaults to None. + + Returns + ------- + FeatureSelectionConverter + The fitted selector instance (self). + """ + if hasattr(x, "types") and x.types is not None: + self._input_types = dict(x.types) + return super().fit(x, y) + + def get_output_type(self, column_name: str = None) -> DashAIDataType: + """Return the original DashAI data type of a retained column. + + Since feature selection leaves the retained columns' values unchanged, + the output type matches the input type of that column. + + Parameters + ---------- + column_name : str, optional + The name of the retained column. Defaults to None. + + Returns + ------- + DashAIDataType + The original type of the column. Falls back to ``float64`` when the + input type is unknown (feature selectors only operate on numbers). + """ + input_types = getattr(self, "_input_types", None) + if input_types is not None and column_name in input_types: + return input_types[column_name] + + import pyarrow as pa + + from DashAI.back.types.value_types import Float + + return Float(arrow_type=pa.float64()) diff --git a/DashAI/back/converters/scikit_learn/generic_univariate_select.py b/DashAI/back/converters/scikit_learn/generic_univariate_select.py index 7799d344e..4ce7f2324 100644 --- a/DashAI/back/converters/scikit_learn/generic_univariate_select.py +++ b/DashAI/back/converters/scikit_learn/generic_univariate_select.py @@ -14,7 +14,6 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString -from DashAI.back.types.dashai_data_type import DashAIDataType from DashAI.back.types.value_types import Float, Integer @@ -83,21 +82,3 @@ class GenericUnivariateSelect( ) IMAGE_PREVIEW = "generic_univariate_select.png" metadata = {"allowed_types": [Float, Integer], "allowed_dtypes": []} - - def get_output_type(self, column_name: str = None) -> DashAIDataType: - """Return the DashAI data type produced by this converter for a column. - - Parameters - ---------- - column_name : str, optional - Not used; all output columns share the - same type. Defaults to None. - - Returns - ------- - DashAIDataType - A Float type backed by ``pyarrow.float64()``. - """ - import pyarrow as pa - - return Float(arrow_type=pa.float64()) diff --git a/DashAI/back/converters/scikit_learn/select_fdr.py b/DashAI/back/converters/scikit_learn/select_fdr.py index a37762be2..0dc935952 100644 --- a/DashAI/back/converters/scikit_learn/select_fdr.py +++ b/DashAI/back/converters/scikit_learn/select_fdr.py @@ -5,7 +5,6 @@ from DashAI.back.core.schema_fields import float_field, schema_field from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString -from DashAI.back.types.dashai_data_type import DashAIDataType from DashAI.back.types.value_types import Float, Integer @@ -104,21 +103,3 @@ def __init__(self, **kwargs): schema fields. Forwarded to the underlying scikit-learn class. """ super().__init__(**kwargs) - - def get_output_type(self, column_name: str = None) -> DashAIDataType: - """Return the DashAI data type produced by this converter for a column. - - Parameters - ---------- - column_name : str, optional - Not used; all output columns share the - same type. Defaults to None. - - Returns - ------- - DashAIDataType - A Float type backed by ``pyarrow.float64()``. - """ - import pyarrow as pa - - return Float(arrow_type=pa.float64()) diff --git a/DashAI/back/converters/scikit_learn/select_fpr.py b/DashAI/back/converters/scikit_learn/select_fpr.py index 84d966955..8097b8d52 100644 --- a/DashAI/back/converters/scikit_learn/select_fpr.py +++ b/DashAI/back/converters/scikit_learn/select_fpr.py @@ -5,7 +5,6 @@ from DashAI.back.core.schema_fields import float_field, schema_field from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString -from DashAI.back.types.dashai_data_type import DashAIDataType from DashAI.back.types.value_types import Float, Integer @@ -95,21 +94,3 @@ def __init__(self, **kwargs): schema fields. Forwarded to the underlying scikit-learn class. """ super().__init__(**kwargs) - - def get_output_type(self, column_name: str = None) -> DashAIDataType: - """Return the DashAI data type produced by this converter for a column. - - Parameters - ---------- - column_name : str, optional - Not used; all output columns share the - same type. Defaults to None. - - Returns - ------- - DashAIDataType - A Float type backed by ``pyarrow.float64()``. - """ - import pyarrow as pa - - return Float(arrow_type=pa.float64()) diff --git a/DashAI/back/converters/scikit_learn/select_fwe.py b/DashAI/back/converters/scikit_learn/select_fwe.py index 5f187b516..4efd800eb 100644 --- a/DashAI/back/converters/scikit_learn/select_fwe.py +++ b/DashAI/back/converters/scikit_learn/select_fwe.py @@ -5,7 +5,6 @@ from DashAI.back.core.schema_fields import float_field, schema_field from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString -from DashAI.back.types.dashai_data_type import DashAIDataType from DashAI.back.types.value_types import Float, Integer @@ -96,24 +95,6 @@ class SelectFwe(FeatureSelectionConverter, SklearnWrapper, SelectFweOperation): IMAGE_PREVIEW = "select_fwe.png" metadata = {"allowed_types": [Float, Integer], "allowed_dtypes": []} - def get_output_type(self, column_name: str = None) -> DashAIDataType: - """Return the DashAI data type produced by this converter for a column. - - Parameters - ---------- - column_name : str, optional - Not used; all output columns share the - same type. Defaults to None. - - Returns - ------- - DashAIDataType - A Float type backed by ``pyarrow.float64()``. - """ - import pyarrow as pa - - return Float(arrow_type=pa.float64()) - def __init__(self, **kwargs): """Initialize the SelectFwe converter. diff --git a/DashAI/back/converters/scikit_learn/select_k_best.py b/DashAI/back/converters/scikit_learn/select_k_best.py index dc78135a8..1bffc79f7 100644 --- a/DashAI/back/converters/scikit_learn/select_k_best.py +++ b/DashAI/back/converters/scikit_learn/select_k_best.py @@ -10,7 +10,6 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString -from DashAI.back.types.dashai_data_type import DashAIDataType from DashAI.back.types.value_types import Float, Integer @@ -83,24 +82,6 @@ class SelectKBest(FeatureSelectionConverter, SklearnWrapper, SelectKBestOperatio IMAGE_PREVIEW = "select_k_best.png" metadata = {"allowed_types": [Float, Integer], "allowed_dtypes": []} - def get_output_type(self, column_name: str = None) -> DashAIDataType: - """Return the DashAI data type produced by this converter for a column. - - Parameters - ---------- - column_name : str, optional - Not used; all output columns share the - same type. Defaults to None. - - Returns - ------- - DashAIDataType - A Float type backed by ``pyarrow.float64()``. - """ - import pyarrow as pa - - return Float(arrow_type=pa.float64()) - def __init__(self, **kwargs): """Initialize the SelectKBest converter. diff --git a/DashAI/back/converters/scikit_learn/select_percentile.py b/DashAI/back/converters/scikit_learn/select_percentile.py index c5346129d..7054e237e 100644 --- a/DashAI/back/converters/scikit_learn/select_percentile.py +++ b/DashAI/back/converters/scikit_learn/select_percentile.py @@ -5,7 +5,6 @@ from DashAI.back.core.schema_fields import int_field, schema_field from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString -from DashAI.back.types.dashai_data_type import DashAIDataType from DashAI.back.types.value_types import Float, Integer @@ -87,24 +86,6 @@ class SelectPercentile( IMAGE_PREVIEW = "select_percentile.png" metadata = {"allowed_types": [Float, Integer], "allowed_dtypes": []} - def get_output_type(self, column_name: str = None) -> DashAIDataType: - """Return the DashAI data type produced by this converter for a column. - - Parameters - ---------- - column_name : str, optional - Not used; all output columns share the - same type. Defaults to None. - - Returns - ------- - DashAIDataType - A Float type backed by ``pyarrow.float64()``. - """ - import pyarrow as pa - - return Float(arrow_type=pa.float64()) - def __init__(self, **kwargs): """Initialize the SelectPercentile converter. diff --git a/DashAI/back/converters/scikit_learn/variance_threshold.py b/DashAI/back/converters/scikit_learn/variance_threshold.py index 98aff4e01..216e755d7 100644 --- a/DashAI/back/converters/scikit_learn/variance_threshold.py +++ b/DashAI/back/converters/scikit_learn/variance_threshold.py @@ -96,25 +96,35 @@ class VarianceThreshold( def fit( self, x: "DashAIDataset", y: Union["DashAIDataset", None] = None ) -> "VarianceThreshold": - """Fit the transformer, allowing all features to be removed if none pass. + """Fit the selector, remembering input types and tolerating empty output. - sklearn raises a ValueError when no feature meets the threshold; we catch - it and return self instead so that ``transform`` can legitimately return a - dataset with zero columns. ``self.variances_`` is already populated by - sklearn before it raises, so the internal state is correct. + VarianceThreshold only drops low-variance columns without modifying the + retained columns' values, so their original types are captured here to be + returned later by ``get_output_type`` instead of coercing to float. Types + are recorded during ``fit`` (rather than ``transform``) because + scikit-learn auto-wraps ``transform`` on subclasses and would coerce its + output back to a pandas DataFrame. + + Additionally, sklearn raises a ValueError when no feature meets the + threshold; we catch it and return self instead so that ``transform`` can + legitimately return a dataset with zero columns. ``self.variances_`` is + already populated by sklearn before it raises, so the internal state is + correct. Parameters ---------- x : DashAIDataset - Input dataset. + The input dataset to fit the selector on. y : DashAIDataset, optional Ignored; present for API consistency. Returns ------- VarianceThreshold - The fitted instance. + The fitted selector instance (self). """ + if hasattr(x, "types") and x.types is not None: + self._input_types = dict(x.types) try: return super().fit(x, y) except ValueError as e: @@ -125,19 +135,26 @@ def fit( return self def get_output_type(self, column_name: str = None) -> DashAIDataType: - """Return the DashAI data type produced by this converter for a column. + """Return the original DashAI data type of a retained column. + + Since the selection leaves the retained columns' values unchanged, the + output type matches the input type of that column. Parameters ---------- column_name : str, optional - Not used; all output columns share the - same type. Defaults to None. + The name of the retained column. Defaults to None. Returns ------- DashAIDataType - A Float type backed by ``pyarrow.float64()``. + The original type of the column. Falls back to ``float64`` when the + input type is unknown (the selector only operates on numbers). """ + input_types = getattr(self, "_input_types", None) + if input_types is not None and column_name in input_types: + return input_types[column_name] + import pyarrow as pa return Float(arrow_type=pa.float64())