Source code for pysatl_cpd.data.providers.transformers.columns
# -*- coding: ascii -*-
"""Column-oriented transformers for pandas providers."""
__author__ = "Mikhail Mikhailov"
__copyright__ = "Copyright (c) 2026 PySATL project"
__license__ = "SPDX-License-Identifier: MIT"
from collections.abc import Sequence
from typing import Any, TypeVar
import pandas as pd
from pysatl_cpd.data.providers.labeled import PandasLabeledData
from pysatl_cpd.data.providers.plain.pd_provider import PandasDataProvider
from pysatl_cpd.data.providers.transformers.base import IDataTransformer
type ColumnSelector = str | Sequence[str]
PandasProviderT = TypeVar("PandasProviderT", PandasDataProvider[Any], PandasLabeledData[Any])
[docs]
class ColumnsSelectorTransformer(IDataTransformer[PandasProviderT, PandasProviderT]):
"""
Transformer that selects specific columns from labeled data.
Parameters
----------
columns
Column name or sequence of column names to select.
rename_provider
Whether to update the provider annotation name.
"""
[docs]
def __init__(self, columns: ColumnSelector, *, rename_provider: bool = False) -> None:
self._columns = columns
self._rename_provider = rename_provider
@property
def _selected_columns(self) -> list[str]:
"""
Get list of selected column names.
Returns
-------
columns
List of column names to select.
Raises
------
ValueError
If the column list is empty or contains non-string entries.
"""
if isinstance(self._columns, str):
return [self._columns]
columns = list(self._columns)
if not columns:
raise ValueError("At least one column must be selected")
if not all(isinstance(column, str) for column in columns):
raise ValueError("ColumnsSelectorTransformer only supports column names")
return columns
@property
def annotation(self) -> str:
"""
Transformer annotation string.
Returns
-------
annotation
Annotation describing selected columns.
"""
selected = self._selected_columns
return f"column[{selected[0]}]" if len(selected) == 1 else "column[" + ";".join(selected) + "]"
[docs]
def transform(self, provider: PandasProviderT) -> PandasProviderT:
"""
Apply column selection to provider.
Parameters
----------
provider
Input provider to transform.
Returns
-------
result
Provider with selected columns.
Raises
------
TypeError
If provider is not a PandasLabeledData.
"""
if not isinstance(provider, PandasLabeledData):
raise TypeError("ColumnsSelectorTransformer only supports PandasLabeledData providers")
return provider.select_columns(feature_columns=self._selected_columns, rename_provider=self._rename_provider)
[docs]
class ColumnFeatureCreator(IDataTransformer[PandasProviderT, PandasProviderT]):
"""Transformer that appends a derived feature column.
Parameters
----------
name
Name of the derived feature column.
mapping
Callable applied to each pandas row.
rename_provider
Whether to update the provider annotation name.
"""
[docs]
def __init__(self, name: str, mapping: Any, *, rename_provider: bool = False) -> None:
self._name = name
self._mapping = mapping
self._rename_provider = rename_provider
@property
def annotation(self) -> str:
"""Transformer annotation string."""
return f"feature[{self._name}]"
[docs]
def transform(self, provider: PandasProviderT) -> PandasProviderT:
"""
Append a derived feature column to a pandas provider.
Parameters
----------
provider
Input pandas provider to transform.
Returns
-------
result
Provider with the appended feature column.
Raises
------
TypeError
If provider is not a PandasDataProvider or PandasLabeledData,
or if mapping is not callable.
ValueError
If the feature column name is empty.
"""
if not isinstance(provider, PandasDataProvider | PandasLabeledData):
raise TypeError("ColumnFeatureCreator only supports PandasDataProvider and PandasLabeledData providers")
if not self._name:
raise ValueError("Feature column name must be non-empty")
if not callable(self._mapping):
raise TypeError("ColumnFeatureCreator mapping must be callable")
return provider.create_feature_column(
name=self._name,
mapping=self._row_mapping,
rename_provider=self._rename_provider,
)
def _row_mapping(self, row: pd.Series) -> object:
"""Apply the user-supplied mapping to a single row."""
return self._mapping(row)