Skip to content

API: preprocessing.encoding

skyulf.preprocessing.encoding

Encoding nodes package.

Split from a single 832-LOC module into per-encoder files

_common.py — shared helpers (detect_categorical_columns, _exclude_target_column, …) one_hot.py — OneHotEncoder ordinal.py — OrdinalEncoder label.py — LabelEncoder target.py — TargetEncoder hash.py — HashEncoder dummy.py — DummyEncoder woe.py — WOEEncoder (Weight-of-Evidence / Information Value)

All public names are re-exported here so existing imports such as from skyulf.preprocessing.encoding import OneHotEncoderCalculator continue to work unchanged.

TargetEncoderCalculator

Bases: BaseCalculator

Source code in skyulf-core/skyulf/preprocessing/encoding/target.py
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
@NodeRegistry.register("TargetEncoder", TargetEncoderApplier)
@node_meta(
    id="TargetEncoder",
    name="Target Encoder",
    category="Preprocessing",
    description="Encode categorical features using target statistics.",
    params={"smooth": "auto", "target_type": "auto", "columns": []},
    learns_from_data=True,
)
class TargetEncoderCalculator(BaseCalculator):
    @fit_method
    def fit(self, X: Any, y: Any, config: dict[str, Any]) -> TargetEncoderArtifact:  # pylint: disable=arguments-differ
        if user_picked_no_columns(config):
            return {}
        return cast(
            TargetEncoderArtifact,
            fit_dual_engine(
                (X, y) if y is not None else X,
                config,
                polars_func=_target_fit_polars,
                pandas_func=_target_fit_pandas,
            ),
        )

    def fit_transform_train(
        self, df: pd.DataFrame | SkyulfDataFrame | tuple, config: dict[str, Any]
    ) -> tuple[TargetEncoderArtifact, Any]:
        """Fit sklearn TargetEncoder and cross-fit the pipeline training rows."""
        if user_picked_no_columns(config):
            return {}, df

        artifact, transformed = fit_transform_train_dual_engine(
            df,
            config,
            polars_func=_target_fit_transform_train_polars,
            pandas_func=_target_fit_transform_train_pandas,
        )
        return cast(
            TargetEncoderArtifact,
            artifact,
        ), transformed

    def infer_output_schema(
        self,
        input_schema: SkyulfSchema,
        config: dict[str, Any],
    ) -> SkyulfSchema | None:
        # For binary/regression targets, the encoder replaces values in
        # source columns in place — same column names, dtype becomes float
        # (per-column dtype is best-effort so we don't bother rewriting it).
        #
        # For multiclass targets, the apply logic (see
        # ``_target_apply_polars``/``_target_apply_pandas``) drops the
        # original columns and creates ``{col}_cls{i}`` columns instead — the
        # number of classes is data-dependent and unknown here, so we can't
        # confidently predict the output columns. The default/"auto"
        # target_type is resolved to multiclass at fit time whenever y has
        # more than two classes, so we must also treat "auto" as unknown
        # rather than assuming binary/regression. Only the explicit
        # "binary"/"regression" config values are confidently in-place.
        if config.get("target_type", "auto") not in ("binary", "regression"):
            return None
        return input_schema

fit_transform_train(df, config)

Fit sklearn TargetEncoder and cross-fit the pipeline training rows.

Source code in skyulf-core/skyulf/preprocessing/encoding/target.py
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
def fit_transform_train(
    self, df: pd.DataFrame | SkyulfDataFrame | tuple, config: dict[str, Any]
) -> tuple[TargetEncoderArtifact, Any]:
    """Fit sklearn TargetEncoder and cross-fit the pipeline training rows."""
    if user_picked_no_columns(config):
        return {}, df

    artifact, transformed = fit_transform_train_dual_engine(
        df,
        config,
        polars_func=_target_fit_transform_train_polars,
        pandas_func=_target_fit_transform_train_pandas,
    )
    return cast(
        TargetEncoderArtifact,
        artifact,
    ), transformed

WOEEncoderCalculator

Bases: BaseCalculator

Source code in skyulf-core/skyulf/preprocessing/encoding/woe.py
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
@NodeRegistry.register("WOEEncoder", WOEEncoderApplier)
@node_meta(
    id="WOEEncoder",
    name="WOE / IV Encoder",
    category="Preprocessing",
    description=(
        "Weight-of-Evidence encoder for binary classification. Replaces each "
        "category with its log-odds and records Information Value per column."
    ),
    params={"regularization": 0.5, "columns": []},
    learns_from_data=True,
)
class WOEEncoderCalculator(BaseCalculator):
    @fit_method
    def fit(self, X: Any, y: Any, config: dict[str, Any]) -> Mapping[str, Any]:  # pylint: disable=arguments-differ
        if user_picked_no_columns(config):
            return {}
        return cast(
            Mapping[str, Any],
            fit_dual_engine(
                (X, y) if y is not None else X,
                config,
                polars_func=_woe_fit,
                pandas_func=_woe_fit,
            ),
        )

    def fit_transform_train(
        self, df: pd.DataFrame | SkyulfDataFrame | tuple, config: dict[str, Any]
    ) -> tuple[Mapping[str, Any], Any]:
        """Fit the full-data WOE artifact and cross-fit the training rows."""
        if user_picked_no_columns(config):
            return {}, df

        artifact, transformed = fit_transform_train_dual_engine(
            df,
            config,
            polars_func=_woe_fit_transform_train_polars,
            pandas_func=_woe_fit_transform_train_pandas,
        )
        return artifact, transformed

    def infer_output_schema(
        self,
        input_schema: SkyulfSchema,
        config: dict[str, Any],
    ) -> SkyulfSchema | None:
        # WOE replaces values in source columns in place (now float-valued);
        # column names are unchanged.
        return input_schema

fit_transform_train(df, config)

Fit the full-data WOE artifact and cross-fit the training rows.

Source code in skyulf-core/skyulf/preprocessing/encoding/woe.py
335
336
337
338
339
340
341
342
343
344
345
346
347
348
def fit_transform_train(
    self, df: pd.DataFrame | SkyulfDataFrame | tuple, config: dict[str, Any]
) -> tuple[Mapping[str, Any], Any]:
    """Fit the full-data WOE artifact and cross-fit the training rows."""
    if user_picked_no_columns(config):
        return {}, df

    artifact, transformed = fit_transform_train_dual_engine(
        df,
        config,
        polars_func=_woe_fit_transform_train_polars,
        pandas_func=_woe_fit_transform_train_pandas,
    )
    return artifact, transformed