gators.feature_generation package#
Module contents#
- class gators.feature_generation.IsNull[source]#
Bases:
gators.transformer._base_transformer._BaseTransformerCreates boolean features indicating whether values are null for specified columns.
- Parameters:
subset (list[str], default=None) – List of column names to check for null values. If None, all columns in the DataFrame are used.
Examples
>>> from is_null import IsNull >>> import polars as pl
>>> X ={'A': [1, None, 3, 4], ... 'B': [4, 3, None, 1], ... 'C': [1, 2, 1, 2]} >>> X = pl.DataFrame(X)
>>> transformer = IsNull(subset=['A', 'B']) >>> transformer.fit(X) IsNull(subset=['A', 'B']) >>> result = transformer.transform(X) >>> result shape: (4, 5) ┌──────┬──────┬─────┬──────────────┬──────────────┐ │ A │ B │ C │ A__is_null │ B__is_null │ │ i64 │ i64 │ i64 │ bool │ bool │ ├──────┼──────┼─────┼──────────────┼──────────────┤ │ 1 │ 4 │ 1 │ false │ false │ │ null │ 3 │ 2 │ true │ false │ │ 3 │ null │ 1 │ false │ true │ │ 4 │ 1 │ 2 │ false │ false │ └──────┴──────┴─────┴──────────────┴──────────────┘
- fit(X: polars.DataFrame, y: polars.Series | None = None) gators.feature_generation.is_null.IsNull[source]#
Fit the transformer by generating column name mappings.
- Parameters:
X (pl.DataFrame) – Input DataFrame.
y (pl.Series, default=None) – Target variable. Not used, present here for compatibility.
- Returns:
Fitted transformer instance.
- Return type:
- class gators.feature_generation.PolynomialFeatures[source]#
Bases:
gators.transformer._base_transformer._BaseTransformerGenerates polynomial and interaction features.
- Parameters:
subset (list[str], default=None) – Subset of columns to transform. If None, all columns except strings and booleans.
degree (int, default=2) – The degree of the polynomial features.
interaction_only (bool, default=False) – If True, only interaction features are produced.
include_bias (bool, default=True) – If True, include a bias column (column of ones).
Examples
Example 1: Degree 2 polynomial with bias term
>>> from gators.discretizers import PolynomialFeatures >>> import polars as pl >>> X = pl.DataFrame({'A': [1, 2], 'B': [3, 4]}) >>> transformer = PolynomialFeatures(degree=2, include_bias=True) >>> transformer.fit(X) >>> transformer.transform(X) shape: (2, 5) ┌─────┬─────┬─────┬─────┬─────┐─────┐ │ A │ B │ A__A│ A__B│ B__B│ bias| ├─────┼─────┼─────┼─────┼─────┤─────┤ │ 1 │ 3 │ 1 │ 3 │ 9 │ 1 │ │ 2 │ 4 │ 4 │ 8 │ 16 │ 1 │ └─────┴─────┴─────┴─────┴─────┴─────┘
Example 2: Polynomial on subset of columns
>>> transformer = PolynomialFeatures(subset=['A'], degree=2) >>> transformer.fit(X) >>> transformer.transform(X) shape: (2, 3) ┌─────┬─────┬─────┐ │ A │ B │ A__A│ ├─────┼─────┼─────┤ │ 1 │ 3 │ 1 │ │ 2 │ 4 │ 4 │ └─────┴─────┴─────┘
Example 3: Interaction features only
>>> transformer = PolynomialFeatures(degree=2, interaction_only=True) >>> transformer.fit(X) >>> transformer.transform(X) shape: (2, 4) ┌─────┬─────┬─────┐ │ A │ B │ A__B│ ├─────┼─────┼─────┼ │ 1 │ 3 │ 3 │ │ 2 │ 4 │ 8 │ └─────┴─────┴─────┴
- fit(X: polars.DataFrame, y: polars.Series | None = None) gators.feature_generation.polynomial_features.PolynomialFeatures[source]#
Fit the transformer by identifying columns to transform.
- Parameters:
X (pl.DataFrame) – Input DataFrame.
y (pl.Series, default=None) – Target variable. Not used, present here for compatibility.
- Returns:
Fitted transformer instance.
- Return type:
- class gators.feature_generation.PlanRotationFeatures[source]#
Bases:
gators.transformer._base_transformer._BaseTransformerCreate new columns based on the plan rotation mapping.
The data should be composed of numerical columns only. Use gators.encoders to replace the categorical columns by numerical ones before using PlanRotationFeatures.
- Parameters:
subset (list[list[str]]) – List of pair-wise columns.
angles (list[float]) – List of rotation angles.
Examples
Basic usage with plan rotation
Imports and initialization:
>>> from gators.feature_generation import PlanRotationFeatures >>> obj = PlanRotationFeatures( ... subset=[['X', 'Y'], ['X', 'Z']] , angles=[45.0, 60.0])
The fit, transform, and fit_transform methods accept polars dataframes:
>>> import polars as pl >>> X = pl.DataFrame( ... {'X': [200.0, 210.0], 'Y': [140.0, 160.0], 'Z': [100.0, 125.0]})
The result is a transformed polars dataframe.
>>> obj.fit_transform(X) shape: (2, 9) ┌───────┬───────┬───────┬────────────┬───┬────────────┬────────────┬────────────┐ │ X ┆ Y ┆ Z ┆ XY_x_45.0… ┆ … ┆ XZ_y_45.0… ┆ XZ_x_60.0… ┆ XZ_y_60.0… │ │ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- │ │ f64 ┆ f64 ┆ f64 ┆ f64 ┆ ┆ f64 ┆ f64 ┆ f64 │ ╞═══════╪═══════╪═══════╪════════════╪═══╪════════════╪════════════╪════════════╡ │ 200.0 ┆ 140.0 ┆ 100.0 ┆ 42.426407 ┆ … ┆ 212.132034 ┆ 13.397460 ┆ 223.205081 │ │ 210.0 ┆ 160.0 ┆ 125.0 ┆ 35.355339 ┆ … ┆ 236.880772 ┆ -3.253175 ┆ 244.365335 │ └───────┴───────┴───────┴────────────┴───┴────────────┴────────────┴────────────┘
- fit(X: polars.DataFrame, y: polars.Series | None = None) gators.feature_generation.plan_rotation_features.PlanRotationFeatures[source]#
Fit the transformer by identifying columns to flatten.
- Parameters:
X (pl.DataFrame) – Input dataframe.
y (pl.Series, default=None) – Target variable. Not used, present here for compatibility.
- Returns:
Fitted transformer instance.
- Return type:
- class gators.feature_generation.MathFeatures[source]#
Bases:
gators.transformer._base_transformer._BaseTransformerGenerates new features by applying mathematical operations to groups of columns.
- Parameters:
groups (list[list[str]]) – List of groups of column names to apply operations on.
func (list[str]) –
List of operations to apply to each group of columns. Available operations:
’sum’: Sum of all columns
’mean’: Mean of all columns
’minus’: Subtraction (reduces columns left to right)
’mul’: Product of all columns
’div’: Division (reduces columns left to right)
’min’: Minimum value across columns
’max’: Maximum value across columns
’std’: Standard deviation across columns
’var’: Variance across columns
’median’: Median across columns
’range’: Range (max - min)
’abs_diff’: Absolute difference (reduces columns left to right)
’count_null’: Count of null values
’count_zero’: Count of zero values
’count_nonzero’: Count of non-zero values
Note: For division operations, consider using RatioFeatures instead, which provides safer division with automatic handling of division by zero and null values.
drop_columns (bool, optional) – Whether to drop the original columns after creating the new features, by default False.
new_column_names (list[str]], optional) – List of new column names for the created features, by default None.
Examples
>>> from math_features import MathFeatures >>> import polars as pl
>>> X ={'A': [1, 2, 3, 4], ... 'B': [4, 3, 2, 1], ... 'C': [1, 2, 1, 2]} >>> X = pl.DataFrame(X)
Example 1: drop_columns=False
>>> transformer = MathFeatures(groups=[['A', 'B'], ['B', 'C']], func=['sum', 'mean']) >>> transformer.fit(X) MathFeatures(groups=[['A', 'B'], ['B', 'C']], func=['sum', 'mean']) >>> result = transformer.transform(X) >>> result shape: (4, 6) ┌─────┬─────┬─────┬────────┬─────-───┬────────┐ │ A │ B │ C │ A_B_sum│ A_B_mean│ B_C_sum│ │ i64 │ i64 │ i64 │ f64 │ f64 │ f64 │ ├─────┼─────┼─────┼────────┼──────-──┼────────┤ │ 1 │ 4 │ 1 │ 5.0 │ 2.5 │ 5.0 │ │ 2 │ 3 │ 2 │ 5.0 │ 2.5 │ 5.0 │ │ 3 │ 2 │ 1 │ 5.0 │ 2.5 │ 3.0 │ │ 4 │ 1 │ 2 │ 5.0 │ 2.5 │ 3.0 │ └─────┴─────┴─────┴────────┴───────-─┴────────┘
Example 2: drop_columns=True
>>> transformer = MathFeatures(groups=[['A', 'B'], ['B', 'C']], func=['sum'], drop_columns=True) >>> transformer.fit(X) MathFeatures(groups=[['A', 'B'], ['B', 'C']], func=['sum'], drop_columns=True) >>> result = transformer.transform(X) >>> result shape: (4, 2) ┌────────┬────────┐ │ A_B_sum│ B_C_sum│ │ f64 │ f64 │ ├────────┼────────┤ │ 5.0 │ 5.0 │ │ 5.0 │ 5.0 │ │ 5.0 │ 3.0 │ │ 5.0 │ 3.0 │ └────────┴────────┘
- fit(X: polars.DataFrame, y: polars.Series | None = None) gators.feature_generation.math_features.MathFeatures[source]#
Fit the transformer by generating column name mappings.
- Parameters:
X (pl.DataFrame) – Input DataFrame.
y (pl.Series, default=None) – Target variable. Not used, present here for compatibility.
- Returns:
Fitted transformer instance.
- Return type:
- class gators.feature_generation.GroupStatisticsFeatures[source]#
Bases:
gators.transformer._base_transformer._BaseTransformerGenerates group-level feature columns for numerical columns.
Two categories of output are supported:
Absolute statistics — the raw group aggregate is appended as a new column:
'mean': group mean'std': group standard deviation'median': group median'min': group minimum'max': group maximum'sum': group sum'count': group count (nulls excluded)'range': group range (max − min)
Relative statistics — each row is scaled by its group aggregate:
'mean_ratio': value / group_mean'median_ratio': value / group_median'zscore': (value − group_mean) / group_std'minmax': (value − group_min) / (group_max − group_min)
Both categories can be combined freely in a single transformer call. All generated columns follow the naming pattern
'{func}_{num_col}__per_{groupby_col}'.Importance for Fraud Detection#
Group features are particularly valuable in fraud detection because they capture how a transaction compares to the typical behaviour of its segment (merchant, customer, time-of-day, geography, etc.).
Absolute stats provide context: “the average transaction for merchant A is $183”.
Relative stats expose anomalies: “this transaction is 10× the group average” (
mean_ratio), “3σ above the group mean” (zscore), or “at the very top of the observed range” (minmax).
- param subset:
List of numerical column names to aggregate.
- type subset:
list[str]
- param by:
List of column names to use for groupby operations. Each column is used for a separate groupby (e.g.,
['cat1', 'cat2']creates features grouped bycat1and separate features grouped bycat2).- type by:
list[str]
- param func:
List of functions to apply. Any mix of absolute and relative functions listed above is valid.
- type func:
list[str]
- param fill_value:
Value used when the denominator is zero or null for relative statistics (
'mean_ratio','median_ratio','zscore','minmax'). Has no effect on absolute statistics.- type fill_value:
float, default=0.0
- param drop_columns:
Whether to drop the original numerical columns after creating features.
- type drop_columns:
bool, default=False
- param new_column_names:
Custom names for the generated columns. If
None, names are auto-generated as'{func}_{num_col}__per_{groupby_col}'. Must have the same length assubset × by × func.- type new_column_names:
list[str], default=None
Examples
>>> from gators.feature_generation import GroupStatisticsFeatures >>> import polars as pl
>>> X = pl.DataFrame({ ... 'amount': [100, 200, 150, 300, 250], ... 'cat1': ['A', 'A', 'B', 'B', 'A'], ... 'cat2': ['X', 'Y', 'X', 'X', 'X'], ... })
Example 1: Absolute statistics
>>> transformer = GroupStatisticsFeatures( ... subset=['amount'], ... by=['cat1'], ... func=['mean', 'count'], ... ) >>> result = transformer.fit_transform(X) >>> result shape: (5, 5) ┌────────┬──────┬──────┬───────────────────────┬────────────────────────┐ │ amount ┆ cat1 ┆ cat2 ┆ mean_amount__per_cat1 ┆ count_amount__per_cat1 │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ str ┆ str ┆ f64 ┆ u32 │ ╞════════╪══════╪══════╪═══════════════════════╪════════════════════════╡ │ 100 ┆ A ┆ X ┆ 183.333333 ┆ 3 │ │ 200 ┆ A ┆ Y ┆ 183.333333 ┆ 3 │ │ 150 ┆ B ┆ X ┆ 225.0 ┆ 2 │ │ 300 ┆ B ┆ X ┆ 225.0 ┆ 2 │ │ 250 ┆ A ┆ X ┆ 183.333333 ┆ 3 │ └────────┴──────┴──────┴───────────────────────┴────────────────────────┘
Example 2: Relative statistics
>>> transformer = GroupStatisticsFeatures( ... subset=['amount'], ... by=['cat1'], ... func=['mean_ratio', 'zscore'], ... ) >>> result = transformer.fit_transform(X) >>> result.columns ['amount', 'cat1', 'cat2', 'mean_ratio_amount__per_cat1', 'zscore_amount__per_cat1']
Example 3: Mixing absolute and relative
>>> transformer = GroupStatisticsFeatures( ... subset=['amount'], ... by=['cat1'], ... func=['mean', 'zscore', 'minmax'], ... ) >>> result = transformer.fit_transform(X) >>> result.columns ['amount', 'cat1', 'cat2', 'mean_amount__per_cat1', 'zscore_amount__per_cat1', 'minmax_amount__per_cat1']
Example 4: Multiple groupby columns
>>> transformer = GroupStatisticsFeatures( ... subset=['amount'], ... by=['cat1', 'cat2'], ... func=['mean'], ... ) >>> result = transformer.fit_transform(X) >>> result.columns ['amount', 'cat1', 'cat2', 'mean_amount__per_cat1', 'mean_amount__per_cat2']
Example 5: Zero denominator handling (relative statistics)
>>> X_zero = pl.DataFrame({'v': [0, 0, 5, 10], 'g': ['A', 'A', 'B', 'B']}) >>> transformer = GroupStatisticsFeatures( ... subset=['v'], ... by=['g'], ... func=['mean_ratio'], ... fill_value=-1.0, ... ) >>> result = transformer.fit_transform(X_zero) >>> result['mean_ratio_v__per_g'][0] # group A mean=0 -> fill_value -1.0
- fit(X: polars.DataFrame, y: polars.Series | None = None) gators.feature_generation.group_statistics_features.GroupStatisticsFeatures[source]#
Fit the transformer by generating column name mappings.
- Parameters:
X (pl.DataFrame) – Input DataFrame.
y (pl.Series, default=None) – Target variable. Not used, present here for compatibility.
- Returns:
Fitted transformer instance.
- Return type:
- class gators.feature_generation.GroupLagFeatures[source]#
Bases:
gators.transformer._base_transformer._BaseTransformerGenerates lag (previous values) and lead (next values) features within groups.
This transformer creates features like:
Value N periods ago within the same group
Historical behavior patterns within groups
Sequential dependencies in grouped data
Useful for time-series analysis and detecting changes in behavior patterns. Lag features are suitable for real-time scoring; lead features are only useful for historical analysis and backtesting.
- Parameters:
subset (list[str]) – List of numerical column names to create lag/lead features for.
by (list[str]) – List of columns to group by. Lags/leads are computed within each group.
lags (list[int]) – List of lag periods. Positive integers create lag features (previous values). Example: [1, 2, 3] creates lag_1, lag_2, lag_3
leads (list[int], default=[]) – List of lead periods. Positive integers create lead features (next values). Example: [1, 2] creates lead_1, lead_2
fill_value (float, default=None) – Value to use for missing lag/lead values. If None, uses null.
drop_columns (bool, default=False) – Whether to drop the original numerical columns after creating lag features.
new_column_names (list[str], default=None) – List of custom names for the lag/lead columns. If None, uses default naming pattern ‘{num_col}_lag{n}_{groupby_cols}’ or ‘{num_col}_lead{n}_{groupby_cols}’. Must have same length as the total number of features created.
Examples
>>> from gators.feature_generation import GroupLagFeatures >>> import polars as pl
>>> X ={ ... 'amount': [100, 200, 150, 300, 250, 180], ... 'cat1': ['A', 'A', 'B', 'B', 'A', 'B'], ... 'time': [1, 2, 1, 2, 3, 3] ... } >>> X = pl.DataFrame(X).sort(['cat1', 'time'])
Example 1: Basic lag features
>>> transformer = GroupLagFeatures( ... subset=['amount'], ... by=['cat1'], ... lags=[1, 2] ... ) >>> result = transformer.fit_transform(X) >>> result shape: (6, 5) ┌────────┬───────┬──────┬─────────────────────┬─────────────────────┐ │ amount ┆ cat1 ┆ time ┆ amount_lag1_cat1 ┆ amount_lag2_cat1 │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ str ┆ i64 ┆ i64 ┆ i64 │ ╞════════╪═══════╪══════╪═════════════════════╪═════════════════════╡ │ 100 ┆ A ┆ 1 ┆ null ┆ null │ │ 200 ┆ A ┆ 2 ┆ 100 ┆ null │ │ 250 ┆ A ┆ 3 ┆ 200 ┆ 100 │ │ 150 ┆ B ┆ 1 ┆ null ┆ null │ │ 300 ┆ B ┆ 2 ┆ 150 ┆ null │ │ 180 ┆ B ┆ 3 ┆ 300 ┆ 150 │ └────────┴───────┴──────┴─────────────────────┴─────────────────────┘
Example 2: Lag and lead features
>>> transformer = GroupLagFeatures( ... subset=['amount'], ... by=['cat1'], ... lags=[1], ... leads=[1] ... ) >>> result = transformer.fit_transform(X) >>> result shape: (6, 5) ┌────────┬───────┬──────┬───────────────────┬────────────────────┐ │ amount ┆ cat1 ┆ time ┆ amount_lag1_cat1 ┆ amount_lead1_cat1 │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ str ┆ i64 ┆ i64 ┆ i64 │ ╞════════╪═══════╪══════╪═══════════════════╪════════════════════╡ │ 100 ┆ A ┆ 1 ┆ null ┆ 200 │ │ 200 ┆ A ┆ 2 ┆ 100 ┆ 250 │ │ 250 ┆ A ┆ 3 ┆ 200 ┆ null │ │ 150 ┆ B ┆ 1 ┆ null ┆ 300 │ │ 300 ┆ B ┆ 2 ┆ 150 ┆ 180 │ │ 180 ┆ B ┆ 3 ┆ 300 ┆ null │ └────────┴───────┴──────┴───────────────────┴────────────────────┘
Example 3: With fill_value
>>> transformer = GroupLagFeatures( ... subset=['amount'], ... by=['cat1'], ... lags=[1], ... fill_value=0.0 ... ) >>> result = transformer.fit_transform(X) >>> result['amount_lag1_cat1'][0] # First row, no previous value 0.0
Notes
Data should be sorted by by and time before transformation
Lag features look backwards: lag_1 is the previous row within the group. These are suitable for real-time scoring.
Lead features look forwards: lead_1 is the next row within the group. These are useful only for historical analysis and backtesting, not real-time scoring.
First rows in each group will have null (or fill_value) for lag features
Last rows in each group will have null (or fill_value) for lead features
- fit(X: polars.DataFrame, y: polars.Series | None = None) gators.feature_generation.group_lag_features.GroupLagFeatures[source]#
Fit the transformer by generating column name mappings.
- Parameters:
X (pl.DataFrame) – Input DataFrame.
y (pl.Series, default=None) – Target variable. Not used, present here for compatibility.
- Returns:
Fitted transformer instance.
- Return type:
- class gators.feature_generation.ComparisonFeatures[source]#
Bases:
gators.transformer._base_transformer._BaseTransformerGenerates binary comparison features between pairs of columns, or unary null checks.
- Parameters:
subset_a (list[str]) – List of column names for the left side of comparisons (or the only column for unary operators).
subset_b (list[str]) – List of column names for the right side of comparisons. For unary operators (‘is_null’, ‘is_not_null’), these values are ignored.
operators (list[Literal[">", "<", ">=", "<=", "==", "!=", "is_null", "is_not_null"]]) – List of comparison operators to apply. Must match length of columns. Unary operators: ‘is_null’, ‘is_not_null’ (only use subset_a) Binary operators: ‘>’, ‘<’, ‘>=’, ‘<=’, ‘==’, ‘!=’ (use both subset_a and subset_b)
drop_columns (bool, default=False) – Whether to drop the original columns after creating comparisons.
Examples
>>> from gators.feature_generation import ComparisonFeatures >>> import polars as pl
>>> X ={'A': [10, 20, 30, 40], ... 'B': [15, 10, 30, 35], ... 'C': [5, 25, 20, 50]} >>> X = pl.DataFrame(X)
Example 1: Single comparison
>>> transformer = ComparisonFeatures( ... subset_a=['A'], ... subset_b=['B'], ... operators=['>'] ... ) >>> transformer.fit(X) ComparisonFeatures(subset_a=['A'], subset_b=['B'], operators=['>']) >>> result = transformer.transform(X) >>> result shape: (4, 4) ┌──────┬──────┬──────┬─────────┐ │ A │ B │ C │ A_gt_B │ │ i64 │ i64 │ i64 │ bool │ ├──────┼──────┼──────┼─────────┤ │ 10 │ 15 │ 5 │ false │ │ 20 │ 10 │ 25 │ true │ │ 30 │ 30 │ 20 │ false │ │ 40 │ 35 │ 50 │ true │ └──────┴──────┴──────┴─────────┘
Example 2: Multiple comparisons with different operators
>>> transformer = ComparisonFeatures( ... subset_a=['A', 'B', 'A'], ... subset_b=['B', 'C', 'C'], ... operators=['>', '<', '>='] ... ) >>> result = transformer.fit_transform(X) >>> result shape: (4, 6) ┌──────┬──────┬──────┬─────────┬─────────┬─────────┐ │ A │ B │ C │ A_gt_B │ B_lt_C │ A_gte_C │ │ i64 │ i64 │ i64 │ bool │ bool │ bool │ ├──────┼──────┼──────┼─────────┼─────────┼─────────┤ │ 10 │ 15 │ 5 │ false │ false │ true │ │ 20 │ 10 │ 25 │ true │ true │ false │ │ 30 │ 30 │ 20 │ false │ false │ true │ │ 40 │ 35 │ 50 │ true │ true │ false │ └──────┴──────┴──────┴─────────┴─────────┴─────────┘
Example 3: Null checks (unary operators)
>>> data_with_nulls = pl.DataFrame({ ... 'A': [10, None, 30, None], ... 'B': [15, 10, None, 35] ... }) >>> transformer = ComparisonFeatures( ... subset_a=['A', 'B'], ... subset_b=['', ''], # Ignored for unary operators ... operators=['is_null', 'is_not_null'] ... ) >>> result = transformer.fit_transform(data_with_nulls) >>> result shape: (4, 4) ┌──────┬──────┬────────────┬────────────────┐ │ A │ B │ A__is_null │ B__is_not_null │ │ i64 │ i64 │ bool │ bool │ ├──────┼──────┼────────────┼────────────────┤ │ 10 │ 15 │ false │ true │ │ null │ 10 │ true │ true │ │ 30 │ null │ false │ false │ │ null │ 35 │ true │ true │ └──────┴──────┴────────────┴────────────────┘
Example 4: With drop_columns=True
>>> transformer = ComparisonFeatures( ... subset_a=['A'], ... subset_b=['B'], ... operators=['>'], ... drop_columns=True ... ) >>> result = transformer.fit_transform(X) >>> result shape: (4, 2) ┌──────┬─────────┐ │ C │ A_gt_B │ │ i64 │ bool │ ├──────┼─────────┤ │ 5 │ false │ │ 25 │ true │ │ 20 │ false │ │ 50 │ true │ └──────┴─────────┘
- fit(X: polars.DataFrame, y: polars.Series | None = None) gators.feature_generation.comparison_features.ComparisonFeatures[source]#
Fit the transformer (no-op, but required for sklearn compatibility).
- Parameters:
X (pl.DataFrame) – Input DataFrame.
y (pl.Series, default=None) – Target variable. Not used, present here for compatibility.
- Returns:
Fitted transformer instance.
- Return type:
- class gators.feature_generation.ConditionFeatures[source]#
Bases:
gators.transformer._base_transformer._BaseTransformerCreates multiple independent boolean features, one for each condition.
This transformer is designed for creating simple boolean flags without combination logic. Each condition produces exactly one boolean output column. For combining multiple conditions with AND/OR logic, use RuleFeatures instead.
Use Cases:
Create simple boolean flags (is_adult, is_weekend, is_premium, etc.)
Materialize threshold-based features (is_high_value, is_frequent_user)
Feature engineering: Generate independent indicator variables
Fraud detection: Create simple risk flags before combining them
When to Use:
Need multiple independent boolean columns
Each condition stands alone (no AND/OR combination needed)
Want cleaner API than RuleFeatures for simple cases
Building feature sets for downstream transformers
When NOT to Use:
Need to combine conditions with AND/OR (use RuleFeatures)
One-off exploratory analysis (use Polars native expressions)
Very simple cases with 1-2 conditions (just use .with_columns())
- Parameters:
conditions (list[dict[str, Any]]) –
List of condition dictionaries. Each condition creates one boolean output column.
Each condition dictionary must contain:
’column’: str - Name of the column to evaluate
’op’: str - Comparison operator. Supported:
Binary: ‘>’, ‘<’, ‘>=’, ‘<=’, ‘==’, ‘!=’ (require ‘value’ or ‘other_column’)
Unary: ‘is_null’, ‘is_not_null’ (no ‘value’ or ‘other_column’ needed)
’value’: Any (optional) - Scalar value to compare the column against
’other_column’: str (optional) - Name of another column to compare against
For binary operators: Either ‘value’ or ‘other_column’ must be specified, but not both. For unary operators: Neither ‘value’ nor ‘other_column’ should be specified.
Examples:
# Simple conditions: [ {'column': 'age', 'op': '>=', 'value': 18}, {'column': 'amount', 'op': '>', 'value': 1000} ] # Column comparison: [ {'column': 'velocity_24h', 'op': '>', 'other_column': 'velocity_7d'} ] # Null checks: [ {'column': 'age', 'op': 'is_null'}, {'column': 'email', 'op': 'is_not_null'} ]
new_column_names (list[str], default=None) –
Names for the resulting boolean feature columns. If provided, must have the same length as
conditions. If None, column names are auto-generated in the format:Scalar comparison:
{column}_{op_name}_{value}(e.g., ‘age_gte_18’)Column comparison:
{column}_{op_name}_{other_column}(e.g., ‘velocity_24h_gt_velocity_7d’)Unary operation:
{column}__{op_name}(e.g., ‘age__is_null’)
Operator name mapping:
’>’ -> ‘gt’
’<’ -> ‘lt’
’>=’ -> ‘gte’
’<=’ -> ‘lte’
’==’ -> ‘eq’
’!=’ -> ‘ne’
’is_null’ -> ‘is_null’
’is_not_null’ -> ‘is_not_null’
Examples
>>> import polars as pl >>> from gators.feature_generation import ConditionFeatures
>>> X ={ ... 'age': [15, 25, 30, 17, 45], ... 'amount': [100, 1500, 500, 200, 2000], ... 'family_size': [1, 3, 1, 4, 2], ... 'fare': [50, 75, 30, 100, 80] ... } >>> X = pl.DataFrame(X)
Example 1: Create simple boolean flags
>>> transformer = ConditionFeatures( ... conditions=[ ... {'column': 'age', 'op': '>=', 'value': 18}, ... {'column': 'amount', 'op': '>', 'value': 1000}, ... {'column': 'family_size', 'op': '==', 'value': 1} ... ], ... new_column_names=['is_adult', 'is_high_amount', 'is_alone'] ... ) >>> result = transformer.fit_transform(X) >>> result.select(['age', 'amount', 'family_size', 'is_adult', 'is_high_amount', 'is_alone']) shape: (5, 6) ┌─────┬────────┬─────────────┬──────────┬─────────────────┬──────────┐ │ age ┆ amount ┆ family_size ┆ is_adult ┆ is_high_amount ┆ is_alone │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 ┆ bool ┆ bool ┆ bool │ ╞═════╪════════╪═════════════╪══════════╪═════════════════╪══════════╡ │ 15 ┆ 100 ┆ 1 ┆ false ┆ false ┆ true │ │ 25 ┆ 1500 ┆ 3 ┆ true ┆ true ┆ false │ │ 30 ┆ 500 ┆ 1 ┆ true ┆ false ┆ true │ │ 17 ┆ 200 ┆ 4 ┆ false ┆ false ┆ false │ │ 45 ┆ 2000 ┆ 2 ┆ true ┆ true ┆ false │ └─────┴────────┴─────────────┴──────────┴─────────────────┴──────────┘
Example 2: Column-to-column comparison
>>> fare_X ={ ... 'fare': [50.0, 100.0, 30.0, 200.0, 80.0], ... 'fare_per_person': [50.0, 33.3, 30.0, 50.0, 40.0] ... } >>> fare_X = pl.DataFrame(fare_data) >>> fare_BaseTransformer = ConditionFeatures( ... conditions=[ ... {'column': 'fare', 'op': '>', 'value': 100}, ... {'column': 'fare_per_person', 'op': '>', 'other_column': 'fare'} ... ], ... new_column_names=['is_expensive', 'paid_more_per_person'] ... ) >>> result = fare_BaseTransformer.fit_transform(fare_X) >>> result shape: (5, 4) ┌───────┬──────────────────┬──────────────┬──────────────────────┐ │ fare ┆ fare_per_person ┆ is_expensive ┆ paid_more_per_person │ │ --- ┆ --- ┆ --- ┆ --- │ │ f64 ┆ f64 ┆ bool ┆ bool │ ╞═══════╪══════════════════╪══════════════╪══════════════════════╡ │ 50.0 ┆ 50.0 ┆ false ┆ false │ │ 100.0 ┆ 33.3 ┆ false ┆ false │ │ 30.0 ┆ 30.0 ┆ false ┆ false │ │ 200.0 ┆ 50.0 ┆ true ┆ false │ │ 80.0 ┆ 40.0 ┆ false ┆ false │ └───────┴──────────────────┴──────────────┴──────────────────────┘
Example 3: Titanic-style feature engineering
>>> titanic_X ={ ... 'Age': [22.0, 38.0, 26.0, 35.0, 12.0], ... 'Pclass': [3, 1, 3, 1, 3], ... 'SibSp': [1, 1, 0, 1, 0], ... 'Parch': [0, 0, 0, 0, 1] ... } >>> titanic_X = pl.DataFrame(titanic_data) >>> # First add family_size >>> titanic_X = titanic_X.with_columns( ... (pl.col('SibSp') + pl.col('Parch')).alias('family_size') ... ) >>> titanic_BaseTransformer = ConditionFeatures( ... conditions=[ ... {'column': 'Age', 'op': '<', 'value': 18}, ... {'column': 'Pclass', 'op': '==', 'value': 1}, ... {'column': 'family_size', 'op': '==', 'value': 0} ... ], ... new_column_names=['is_child', 'is_first_class', 'is_alone'] ... ) >>> result = titanic_BaseTransformer.fit_transform(titanic_X) >>> result.select(['Age', 'Pclass', 'family_size', 'is_child', 'is_first_class', 'is_alone']) shape: (5, 6) ┌──────┬────────┬─────────────┬──────────┬────────────────┬──────────┐ │ Age ┆ Pclass ┆ family_size ┆ is_child ┆ is_first_class ┆ is_alone │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ f64 ┆ i64 ┆ i64 ┆ bool ┆ bool ┆ bool │ ╞══════╪════════╪═════════════╪══════════╪════════════════╪══════════╡ │ 22.0 ┆ 3 ┆ 1 ┆ false ┆ false ┆ false │ │ 38.0 ┆ 1 ┆ 1 ┆ false ┆ true ┆ false │ │ 26.0 ┆ 3 ┆ 0 ┆ false ┆ false ┆ true │ │ 35.0 ┆ 1 ┆ 1 ┆ false ┆ true ┆ false │ │ 12.0 ┆ 3 ┆ 1 ┆ true ┆ false ┆ false │ └──────┴────────┴─────────────┴──────────┴────────────────┴──────────┘
Example 4: Auto-generated column names
>>> auto_BaseTransformer = ConditionFeatures( ... conditions=[ ... {'column': 'age', 'op': '>=', 'value': 18}, ... {'column': 'amount', 'op': '>', 'value': 1000}, ... {'column': 'family_size', 'op': '==', 'value': 1} ... ] ... # new_column_names not specified - will be auto-generated ... ) >>> result = auto_BaseTransformer.fit_transform(X) >>> result.select(['age', 'amount', 'family_size', 'age_gte_18', 'amount_gt_1000', 'family_size_eq_1']) shape: (5, 6) ┌─────┬────────┬─────────────┬────────────┬────────────────┬──────────────────┐ │ age ┆ amount ┆ family_size ┆ age_gte_18 ┆ amount_gt_1000 ┆ family_size_eq_1 │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 ┆ bool ┆ bool ┆ bool │ ╞═════╪════════╪═════════════╪════════════╪════════════════╪══════════════════╡ │ 15 ┆ 100 ┆ 1 ┆ false ┆ false ┆ true │ │ 25 ┆ 1500 ┆ 3 ┆ true ┆ true ┆ false │ │ 30 ┆ 500 ┆ 1 ┆ true ┆ false ┆ true │ │ 17 ┆ 200 ┆ 4 ┆ false ┆ false ┆ false │ │ 45 ┆ 2000 ┆ 2 ┆ true ┆ true ┆ false │ └─────┴────────┴─────────────┴────────────┴────────────────┴──────────────────┘
Example 5: Null checks (unary operators)
>>> data_with_nulls = { ... 'age': [25, None, 30, 17, None], ... 'email': ['a@test.com', 'b@test.com', None, 'd@test.com', None], ... 'amount': [100, 1500, 500, 200, 2000] ... } >>> X_nulls = pl.DataFrame(data_with_nulls) >>> null_BaseTransformer = ConditionFeatures( ... conditions=[ ... {'column': 'age', 'op': 'is_null'}, ... {'column': 'email', 'op': 'is_not_null'}, ... {'column': 'amount', 'op': '>', 'value': 1000} ... ], ... new_column_names=['age_missing', 'has_email', 'is_high_amount'] ... ) >>> result = null_BaseTransformer.fit_transform(X_nulls) >>> result shape: (5, 6) ┌──────┬─────────────┬────────┬─────────────┬───────────┬─────────────────┐ │ age ┆ email ┆ amount ┆ age_missing ┆ has_email ┆ is_high_amount │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ str ┆ i64 ┆ bool ┆ bool ┆ bool │ ╞══════╪═════════════╪════════╪═════════════╪═══════════╪═════════════════╡ │ 25 ┆ a@test.com ┆ 100 ┆ false ┆ true ┆ false │ │ null ┆ b@test.com ┆ 1500 ┆ true ┆ true ┆ true │ │ 30 ┆ null ┆ 500 ┆ false ┆ false ┆ false │ │ 17 ┆ d@test.com ┆ 200 ┆ false ┆ true ┆ false │ │ null ┆ null ┆ 2000 ┆ true ┆ false ┆ true │ └──────┴─────────────┴────────┴─────────────┴───────────┴─────────────────┘
Notes
Each condition produces exactly one independent boolean column
Auto-naming: If new_column_names is None, names are auto-generated as: * Scalar: {column}_{op_name}_{value} (e.g., ‘age_gte_18’) * Column-to-column: {column}_{op_name}_{other_column} (e.g., ‘velocity_24h_gt_velocity_7d’) * Unary: {column}__{op_name} (e.g., ‘age__is_null’)
No combination logic - use RuleFeatures if you need AND/OR
Simpler API than RuleFeatures for common use cases
Missing values (null) in comparisons typically result in null/false
Unary operators ‘is_null’ and ‘is_not_null’ explicitly check for null values
Can be used as preprocessing step before RuleFeatures for complex logic
See also
- RuleFeatures
For combining multiple conditions with AND/OR logic
- fit(X: polars.DataFrame, y: polars.Series | None = None) gators.feature_generation.condition_features.ConditionFeatures[source]#
Fit the transformer by generating column names if not provided.
- Parameters:
X (pl.DataFrame) – Input DataFrame.
y (pl.Series, default=None) – Target variable. Not used, present here for compatibility.
- Returns:
Fitted transformer instance.
- Return type:
- transform(X: polars.DataFrame) polars.DataFrame[source]#
Transform the input DataFrame by creating boolean features for each condition.
- Parameters:
X (pl.DataFrame) – Input DataFrame to transform.
- Returns:
Transformed DataFrame with new boolean features (one per condition).
- Return type:
pl.DataFrame
- class gators.feature_generation.DistanceFeatures[source]#
Bases:
gators.transformer._base_transformer._BaseTransformerCalculates distances between geographic coordinate pairs.
This transformer computes distances between consecutive pairs of latitude/longitude coordinates using different distance metrics (euclidean, manhattan, haversine) and units (km, miles, meters, feet).
For fraud detection, distance features are valuable for:
Detecting location anomalies (billing vs shipping address distance)
Identifying suspicious IP geolocation patterns
Flagging transactions far from customer’s typical location
Calculating travel feasibility (transaction velocity checks)
- Parameters:
lats (list[str]) – List of latitude column names. Must have at least 2 elements. Coordinates are paired sequentially: (lats[0], longs[0]) to (lats[1], longs[1]), etc.
longs (list[str]) – List of longitude column names. Must have same length as lats.
unit (Literal["km", "miles", "meters", "feet"], default="km") – Unit for distance output.
method (Literal["euclidean", "manhattan", "haversine"], default="haversine") – Distance calculation method: - ‘haversine’: Great-circle distance on a sphere (recommended for lat/long) - ‘euclidean’: Straight-line distance - ‘manhattan’: Sum of absolute differences (taxicab distance)
drop_columns (bool, default=True) – Whether to drop the original coordinate columns.
new_column_names (list[str], default=None) – Custom names for distance columns. If None, uses pattern: ‘distance__{lat1}_to_{lat2}__{method}_{unit}’
Examples
>>> from gators.feature_generation import DistanceFeatures >>> import polars as pl
Example 1: Haversine distance between two locations
>>> X = pl.DataFrame({ ... 'billing_lat': [40.7128, 34.0522, 41.8781], ... 'billing_long': [-74.0060, -118.2437, -87.6298], ... 'shipping_lat': [40.7580, 34.0522, 42.3601], ... 'shipping_long': [-73.9855, -118.2437, -71.0589] ... }) >>> transformer = DistanceFeatures( ... lats=['billing_lat', 'shipping_lat'], ... longs=['billing_long', 'shipping_long'], ... method='haversine', ... unit='km' ... ) >>> result = transformer.fit_transform(X) >>> result.columns ['distance__billing_lat_to_shipping_lat__haversine_km'] >>> result['distance__billing_lat_to_shipping_lat__haversine_km'][0] 5.376...
Example 2: Multiple distance pairs
>>> X = pl.DataFrame({ ... 'home_lat': [40.7128, 34.0522], ... 'home_long': [-74.0060, -118.2437], ... 'work_lat': [40.7580, 34.0700], ... 'work_long': [-73.9855, -118.3000], ... 'shop_lat': [40.7489, 34.0800], ... 'shop_long': [-73.9680, -118.3500] ... }) >>> transformer = DistanceFeatures( ... lats=['home_lat', 'work_lat', 'shop_lat'], ... longs=['home_long', 'work_long', 'shop_long'], ... method='haversine', ... unit='miles', ... drop_columns=False ... ) >>> result = transformer.fit_transform(X) >>> result.columns ['home_lat', 'home_long', 'work_lat', 'work_long', 'shop_lat', 'shop_long', 'distance__home_lat_to_work_lat__haversine_miles', 'distance__work_lat_to_shop_lat__haversine_miles']
Example 3: Euclidean distance
>>> X = pl.DataFrame({ ... 'x1': [0.0, 1.0, 2.0], ... 'y1': [0.0, 1.0, 2.0], ... 'x2': [3.0, 4.0, 5.0], ... 'y2': [4.0, 5.0, 6.0] ... }) >>> transformer = DistanceFeatures( ... lats=['x1', 'x2'], ... longs=['y1', 'y2'], ... method='euclidean', ... unit='meters' ... ) >>> result = transformer.fit_transform(X)
- fit(X: polars.DataFrame, y: polars.Series | None = None) gators.feature_generation.distance_features.DistanceFeatures[source]#
Fit the transformer by generating column name mappings.
- Parameters:
X (pl.DataFrame) – Input DataFrame.
y (pl.Series, default=None) – Target variable. Not used, present here for compatibility.
- Returns:
Fitted transformer instance.
- Return type:
- class gators.feature_generation.ScalarMathFeatures[source]#
Bases:
gators.transformer._base_transformer._BaseTransformerGenerates new features by applying mathematical operations between columns and scalar values.
This transformer performs element-wise operations between a column and a scalar constant. Each operation creates one new feature column. For operations between multiple columns, use MathFeatures instead.
Use Cases:
Unit conversions (days to years, meters to feet, Celsius to Fahrenheit)
Normalization (divide by constant, multiply by scaling factor)
Feature scaling (percentage calculation, ratio computation)
Offset adjustments (add/subtract baseline values)
When to Use:
Need to apply arithmetic operations with fixed scalar values
Creating interpretable transformations (e.g., Age/365 for age_in_years)
Scaling features by known constants
Building feature sets for downstream models
When NOT to Use:
Operations between multiple columns (use MathFeatures)
Need learned scaling (use StandardScaler, MinMaxScaler)
Complex mathematical functions (use DataFrame.with_columns directly)
- Parameters:
operations –
List of operation dictionaries. Each operation creates one new feature column.
Each operation dictionary must contain:
- new_column_nameslist[str], default=None
Names for the resulting feature columns. If provided, must have the same length as
operations. If None, column names are auto-generated in the format:{column}_{op_name}_{scalar}(e.g., ‘Age_div_365’, ‘Price_mul_1.1’)Operator name mapping: ‘+’ -> ‘plus’, ‘-’ -> ‘minus’, ‘*’ -> ‘mul’, ‘/’ -> ‘div’, ‘**’ -> ‘pow’, ‘//’ -> ‘floordiv’, ‘%’ -> ‘mod’
Examples
>>> import polars as pl >>> from gators.feature_generation import ScalarMathFeatures
>>> X ={ ... 'Age': [25, 30, 45, 12, 65], ... 'Price': [100.0, 150.0, 200.0, 75.0, 300.0], ... 'Temperature': [20.0, 25.0, 15.0, 30.0, 22.0] ... } >>> X = pl.DataFrame(X)
Example 1: Unit conversions with custom names
>>> transformer = ScalarMathFeatures( ... operations=[ ... {'column': 'Age', 'op': '/', 'scalar': 365}, ... {'column': 'Temperature', 'op': '+', 'scalar': 273.15} ... ], ... new_column_names=['Age_years', 'Temperature_kelvin'] ... ) >>> result = transformer.fit_transform(X) >>> result.select(['Age', 'Age_years', 'Temperature', 'Temperature_kelvin']) shape: (5, 4) ┌─────┬───────────┬─────────────┬───────────────────┐ │ Age ┆ Age_years ┆ Temperature ┆ Temperature_kelvin│ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ f64 ┆ f64 ┆ f64 │ ╞═════╪═══════════╪═════════════╪═══════════════════╡ │ 25 ┆ 0.068493 ┆ 20.0 ┆ 293.15 │ │ 30 ┆ 0.082192 ┆ 25.0 ┆ 298.15 │ │ 45 ┆ 0.123288 ┆ 15.0 ┆ 288.15 │ │ 12 ┆ 0.032877 ┆ 30.0 ┆ 303.15 │ │ 65 ┆ 0.178082 ┆ 22.0 ┆ 295.15 │ └─────┴───────────┴─────────────┴───────────────────┘
Example 2: Auto-generated column names
>>> auto_BaseTransformer = ScalarMathFeatures( ... operations=[ ... {'column': 'Price', 'op': '*', 'scalar': 1.1}, ... {'column': 'Price', 'op': '/', 'scalar': 100} ... ] ... # new_column_names not specified - will be auto-generated ... ) >>> result = auto_BaseTransformer.fit_transform(X) >>> result.select(['Price', 'Price_mul_1.1', 'Price_div_100']) shape: (5, 3) ┌───────┬──────────────┬───────────────┐ │ Price ┆ Price_mul_1.1┆ Price_div_100 │ │ --- ┆ --- ┆ --- │ │ f64 ┆ f64 ┆ f64 │ ╞═══════╪══════════════╪═══════════════╡ │ 100.0 ┆ 110.0 ┆ 1.0 │ │ 150.0 ┆ 165.0 ┆ 1.5 │ │ 200.0 ┆ 220.0 ┆ 2.0 │ │ 75.0 ┆ 82.5 ┆ 0.75 │ │ 300.0 ┆ 330.0 ┆ 3.0 │ └───────┴──────────────┴───────────────┘
Example 3: Multiple operations (scaling, percentage, tax)
>>> multi_ops = ScalarMathFeatures( ... operations=[ ... {'column': 'Price', 'op': '*', 'scalar': 1.2}, # 20% markup ... {'column': 'Price', 'op': '/', 'scalar': 100}, # as percentage of 100 ... {'column': 'Age', 'op': '%', 'scalar': 10} # age modulo 10 ... ], ... new_column_names=['Price_with_tax', 'Price_pct', 'Age_decade_offset'] ... ) >>> result = multi_ops.fit_transform(X) >>> result.select(['Price', 'Price_with_tax', 'Price_pct', 'Age', 'Age_decade_offset']) shape: (5, 5) ┌───────┬────────────────┬───────────┬─────┬───────────────────┐ │ Price ┆ Price_with_tax ┆ Price_pct ┆ Age ┆ Age_decade_offset │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ f64 ┆ f64 ┆ f64 ┆ i64 ┆ i64 │ ╞═══════╪════════════════╪═══════════╪═════╪═══════════════════╡ │ 100.0 ┆ 120.0 ┆ 1.0 ┆ 25 ┆ 5 │ │ 150.0 ┆ 180.0 ┆ 1.5 ┆ 30 ┆ 0 │ │ 200.0 ┆ 240.0 ┆ 2.0 ┆ 45 ┆ 5 │ │ 75.0 ┆ 90.0 ┆ 0.75 ┆ 12 ┆ 2 │ │ 300.0 ┆ 360.0 ┆ 3.0 ┆ 65 ┆ 5 │ └───────┴────────────────┴───────────┴─────┴───────────────────┘
Example 4: Power and floor division
>>> power_ops = ScalarMathFeatures( ... operations=[ ... {'column': 'Age', 'op': '**', 'scalar': 2}, ... {'column': 'Age', 'op': '//', 'scalar': 10} ... ], ... new_column_names=['Age_squared', 'Age_decade'] ... ) >>> result = power_ops.fit_transform(X) >>> result.select(['Age', 'Age_squared', 'Age_decade']) shape: (5, 3) ┌─────┬─────────────┬────────────┐ │ Age ┆ Age_squared ┆ Age_decade │ │ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 │ ╞═════╪═════════════╪════════════╡ │ 25 ┆ 625 ┆ 2 │ │ 30 ┆ 900 ┆ 3 │ │ 45 ┆ 2025 ┆ 4 │ │ 12 ┆ 144 ┆ 1 │ │ 65 ┆ 4225 ┆ 6 │ └─────┴─────────────┴────────────┘
Notes
Each operation produces exactly one new feature column
Auto-naming: If new_column_names is None, names are auto-generated as: {column}_{op_name}_{scalar} (e.g., ‘Age_div_365’)
Operations are applied element-wise to each row
Division by zero will result in inf or null values (Polars default behavior)
Can chain multiple ScalarMathFeatures transformers in a pipeline
For learned transformations, consider sklearn scalers instead
See also
- MathFeatures
For operations between multiple columns
- ConditionFeatures
For creating boolean features from conditions
- fit(X: polars.DataFrame, y: polars.Series | None = None) gators.feature_generation.scalar_math_features.ScalarMathFeatures[source]#
Fit the transformer by generating column names if not provided.
- Parameters:
X (pl.DataFrame) – Input DataFrame.
y (pl.Series , default=None) – Target variable. Not used, present here for compatibility.
- Returns:
Fitted transformer instance.
- Return type:
- transform(X: polars.DataFrame) polars.DataFrame[source]#
Transform the input DataFrame by creating new features from scalar operations.
- Parameters:
X (pl.DataFrame) – Input DataFrame to transform.
- Returns:
Transformed DataFrame with new computed features (one per operation).
- Return type:
pl.DataFrame
- class gators.feature_generation.RuleFeatures[source]#
Bases:
gators.transformer._base_transformer._BaseTransformerCreates multiple boolean features, each from a group of conditions combined with logical operators.
This transformer is useful for creating multiple rule-based features simultaneously, where each rule represents a distinct business logic or fraud detection pattern. Each rule group produces its own boolean output column.
Use Cases:
Fraud detection: Create multiple risk indicators (velocity spike, amount anomaly, etc.)
Business rules: Generate several eligibility/qualification flags at once
Feature engineering: Build a family of related boolean features
Production pipelines: Encapsulate multiple rule definitions in one transformer
When to Use:
Building production ML pipelines that need serialization
Creating reusable feature engineering templates
Working with sklearn-based systems that expect transformers
Need version control of feature logic (can serialize to JSON/YAML)
Want to create multiple related boolean features efficiently
When NOT to Use:
One-off exploratory analysis (use Polars native expressions)
Very complex nested logic within a single rule (consider Polars native)
Performance-critical scenarios where every microsecond counts
- Parameters:
rules (list[list[dict[str, Any]]]) –
List of rule groups. Each rule group contains condition dictionaries that will be combined to create one boolean output column.
Each condition dictionary must contain:
’column’: str - Name of the column to evaluate
’op’: str - Comparison operator. Supported: ‘>’, ‘<’, ‘>=’, ‘<=’, ‘==’, ‘!=’
’value’: Any (optional) - Scalar value to compare the column against
’other_column’: str (optional) - Name of another column to compare against
Either ‘value’ or ‘other_column’ must be specified, but not both.
Examples:
# Two rules: [ [{'column': 'age', 'op': '>', 'value': 18}], [{'column': 'amount', 'op': '>', 'value': 1000}] ] # Rule with multiple conditions: [ [{'column': 'age', 'op': '>', 'value': 18}, {'column': 'amount', 'op': '>', 'value': 1000}] ]
rule_logic (Literal['and', 'or'], default='and') –
How to combine conditions within each rule group:
’and’: All conditions in a group must be True
’or’: At least one condition in a group must be True
new_column_names (list[str]) – Names for the resulting boolean feature columns. Must have the same length as rules. Each rule group will produce a column with the corresponding name.
drop_conditions (bool, default=False) – Whether to drop intermediate condition columns after combining. Recommended: True for cleaner output.
Examples
>>> import polars as pl >>> from gators.feature_generation import RuleFeatures
>>> X ={ ... 'amount': [100, 500, 1200, 50, 2000], ... 'velocity_24h': [1, 3, 5, 0, 10], ... 'velocity_7d': [5, 8, 10, 2, 15], ... 'is_new_user': [True, False, False, True, False] ... } >>> X = pl.DataFrame(X)
Example 1: Create two risk indicators in one pass
>>> multi_risk_BaseTransformer = RuleFeatures( ... rules=[ ... # Rule 1: Activity spike (24h > 0 AND 7d == 24h) ... [ ... {'column': 'velocity_24h', 'op': '>', 'value': 0}, ... {'column': 'velocity_7d', 'op': '==', 'other_column': 'velocity_24h'} ... ], ... # Rule 2: High amount (amount > 1000) ... [ ... {'column': 'amount', 'op': '>', 'value': 1000} ... ] ... ], ... rule_logic='and', ... new_column_names=['is_activity_spike', 'is_high_amount'], ... drop_conditions=True ... ) >>> result = multi_risk_BaseTransformer.fit_transform(X) >>> result.select(['velocity_24h', 'velocity_7d', 'amount', ... 'is_activity_spike', 'is_high_amount']) shape: (5, 5) ┌──────────────┬─────────────┬────────┬────────────────────┬─────────────────┐ │ velocity_24h ┆ velocity_7d ┆ amount ┆ is_activity_spike ┆ is_high_amount │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 ┆ bool ┆ bool │ ╞══════════════╪═════════════╪════════╪════════════════════╪═════════════════╡ │ 1 ┆ 5 ┆ 100 ┆ false ┆ false │ │ 3 ┆ 8 ┆ 500 ┆ false ┆ false │ │ 5 ┆ 10 ┆ 1200 ┆ false ┆ true │ │ 0 ┆ 2 ┆ 50 ┆ false ┆ false │ │ 10 ┆ 15 ┆ 2000 ┆ false ┆ true │ └──────────────┴─────────────┴────────┴────────────────────┴─────────────────┘
Example 2: OR logic within a rule (high amount OR high velocity)
>>> or_BaseTransformer = RuleFeatures( ... rules=[ ... [ ... {'column': 'amount', 'op': '>', 'value': 1000}, ... {'column': 'velocity_24h', 'op': '>=', 'value': 5} ... ] ... ], ... rule_logic='or', ... new_column_names=['is_high_risk'], ... drop_conditions=True ... ) >>> result = or_BaseTransformer.fit_transform(X) >>> result.select(['amount', 'velocity_24h', 'is_high_risk']) shape: (5, 3) ┌────────┬──────────────┬──────────────┐ │ amount ┆ velocity_24h ┆ is_high_risk │ │ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ bool │ ╞════════╪══════════════╪══════════════╡ │ 100 ┆ 1 ┆ false │ │ 500 ┆ 3 ┆ false │ │ 1200 ┆ 5 ┆ true │ │ 50 ┆ 0 ┆ false │ │ 2000 ┆ 10 ┆ true │ └────────┴──────────────┴──────────────┘
Example 3: Multiple rules with different logic patterns
>>> complex_BaseTransformer = RuleFeatures( ... rules=[ ... # New user AND high amount AND high velocity ... [ ... {'column': 'is_new_user', 'op': '==', 'value': True}, ... {'column': 'amount', 'op': '>', 'value': 1000}, ... {'column': 'velocity_24h', 'op': '>', 'value': 3} ... ], ... # Very high velocity (simple rule) ... [ ... {'column': 'velocity_24h', 'op': '>=', 'value': 10} ... ] ... ], ... rule_logic='and', ... new_column_names=['is_suspicious_new_user', 'is_extreme_velocity'] ... ) >>> result = complex_BaseTransformer.fit_transform(X) >>> result.select(['is_new_user', 'amount', 'velocity_24h', ... 'is_suspicious_new_user', 'is_extreme_velocity']) shape: (5, 5) ┌─────────────┬────────┬──────────────┬─────────────────────────┬──────────────────────┐ │ is_new_user ┆ amount ┆ velocity_24h ┆ is_suspicious_new_user ┆ is_extreme_velocity │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ bool ┆ i64 ┆ i64 ┆ bool ┆ bool │ ╞═════════════╪════════╪══════════════╪═════════════════════════╪══════════════════════╡ │ true ┆ 100 ┆ 1 ┆ false ┆ false │ │ false ┆ 500 ┆ 3 ┆ false ┆ false │ │ false ┆ 1200 ┆ 5 ┆ false ┆ false │ │ true ┆ 50 ┆ 0 ┆ false ┆ false │ │ false ┆ 2000 ┆ 10 ┆ false ┆ true │ └─────────────┴────────┴──────────────┴─────────────────────────┴──────────────────────┘
Notes
Each rule group produces one boolean output column
All conditions within a rule are evaluated independently before combining
Missing values (null) in comparisons typically result in null/false
Creates intermediate boolean columns, so use drop_conditions=True for cleaner output
To create a single column from multiple rules with complex logic (AND of ORs), use this transformer to create intermediate columns, then combine them manually
- fit(X: polars.DataFrame, y: polars.Series | None = None) gators.feature_generation.rule_features.RuleFeatures[source]#
Fit the transformer (no-op, but required for sklearn compatibility).
- Parameters:
X (pl.DataFrame) – Input DataFrame.
y (pl.Series , default=None) – Target variable. Not used, present here for compatibility.
- Returns:
Fitted transformer instance.
- Return type:
- class gators.feature_generation.RowStatisticsFeatures[source]#
Bases:
gators.transformer._base_transformer._BaseTransformerGenerates row-level aggregation features across groups of columns.
This transformer computes statistics (min, max, mean, median, std, range, sum, count) horizontally across specified column groups for each row. Unlike GroupStatisticsFeatures which aggregates vertically (across rows within groups), this computes statistics across columns within each row.
Auto-generated column names follow the pattern
'{group_name}__{func}'.- Parameters:
column_groups (dict[str, list[str]]) – Dictionary mapping group names to lists of column names. Each group defines a set of columns over which to compute row-level statistics. Every list must contain at least 2 columns. Example:
{'card_fields': ['card1', 'card2', 'card3']}func (list[str]) –
Aggregation functions to apply to every group. Available options:
'min': Row-wise minimum'max': Row-wise maximum'mean': Row-wise mean'median': Row-wise median'std': Row-wise standard deviation'range': Row-wise range (max − min)'sum': Row-wise sum'count': Row-wise count of non-null values
drop_columns (bool, default=False) – Whether to drop the original columns after creating aggregation features.
new_column_names (list[str], default=None) – Custom names for the generated columns. If
None, names are auto-generated as'{group_name}__{func}'. Must have the same length aslen(column_groups) × len(func).
Examples
>>> from gators.feature_generation import RowStatisticsFeatures >>> import polars as pl
Example 1: Single group with multiple aggregations
>>> X = pl.DataFrame({'A': [9, 9, 7], 'B': [3, 4, 5], 'C': [6, 7, 8]}) >>> transformer = RowStatisticsFeatures( ... column_groups={'cluster_1': ['A', 'B']}, ... func=['mean', 'std'], ... ) >>> result = transformer.fit_transform(X) >>> result.select(['cluster_1__mean', 'cluster_1__std']) shape: (3, 2) ┌─────────────────┬────────────────┐ │ cluster_1__mean ┆ cluster_1__std │ │ --- ┆ --- │ │ f64 ┆ f64 │ ╞═════════════════╪════════════════╡ │ 6.0 ┆ 4.242641 │ │ 6.5 ┆ 3.535534 │ │ 6.0 ┆ 1.414214 │ └─────────────────┴────────────────┘
Example 2: Multiple groups
>>> X = pl.DataFrame({'A': [9, 9, 7], 'B': [3, 4, 5], 'C': [6, 7, 8], 'D': [1, 2, 3]}) >>> transformer = RowStatisticsFeatures( ... column_groups={'cluster_1': ['A', 'B'], 'cluster_2': ['C', 'D']}, ... func=['min', 'max'], ... ) >>> result = transformer.fit_transform(X) >>> result.columns ['A', 'B', 'C', 'D', 'cluster_1__min', 'cluster_1__max', 'cluster_2__min', 'cluster_2__max']
Example 3: Custom column names
>>> X = pl.DataFrame({'amount1': [100, 200, 150], 'amount2': [50, 100, 75], 'amount3': [25, 50, 30]}) >>> transformer = RowStatisticsFeatures( ... column_groups={'amounts': ['amount1', 'amount2', 'amount3']}, ... func=['mean', 'std'], ... new_column_names=['avg_amount', 'std_amount'], ... ) >>> result = transformer.fit_transform(X) >>> 'avg_amount' in result.columns True
Example 4: Fraud detection — card verification fields
>>> X = pl.DataFrame({ ... 'card_cvv_match': [1, 0, 1, 1], ... 'card_addr_match': [1, 1, 0, 1], ... 'card_zip_match': [1, 1, 1, 0], ... 'is_fraud': [0, 1, 1, 1], ... }) >>> transformer = RowStatisticsFeatures( ... column_groups={'verification': ['card_cvv_match', 'card_addr_match', 'card_zip_match']}, ... func=['mean', 'std'], ... new_column_names=['verif__mean', 'verif__std'], ... ) >>> result = transformer.fit_transform(X) >>> result['verif__mean'][0] # legitimate: all checks pass 1.0 >>> result['verif__std'][0] # legitimate: no variance 0.0
- fit(X: polars.DataFrame, y: polars.Series | None = None) gators.feature_generation.row_statistics_features.RowStatisticsFeatures[source]#
Fit the transformer by generating column name mappings.
- Parameters:
X (pl.DataFrame) – Input DataFrame.
y (pl.Series, default=None) – Target variable. Not used, present here for compatibility.
- Returns:
Fitted transformer instance.
- Return type:
- class gators.feature_generation.RatioFeatures[source]#
Bases:
gators.transformer._base_transformer._BaseTransformerGenerates ratio features by dividing numerator columns by denominator columns with Laplace smoothing applied to the denominator.
Each feature is computed as:
numerator / (denominator + 1)Adding
1to the denominator (Laplace smoothing) prevents division-by-zero and avoids extreme values when the denominator is small or zero. This is particularly useful for count-based features such as event/trial ratios in fraud detection or click-through rates.- Parameters:
numerator_columns (list[str]) – List of column names to use as numerators.
denominator_columns (list[str]) – List of column names to use as denominators. Must have the same length as
numerator_columns.new_column_names (list[str], optional) – List of custom names for the ratio features. If
None, names will be automatically generated as'{numerator}__div__{denominator}', by defaultNone.drop_columns (bool, optional) – Whether to drop the original numerator and denominator columns after creating ratios, by default
False.
Examples
>>> from gators.feature_generation import RatioFeatures >>> import polars as pl
>>> X = pl.DataFrame({ ... 'events': [10, 20, 0, 5], ... 'trials': [1, 3, 0, 9], ... })
Example 1: Basic ratio with Laplace smoothing
Zero trials are smoothed to 1, producing
events / 1instead of null.>>> transformer = RatioFeatures( ... numerator_columns=['events'], ... denominator_columns=['trials'] ... ) >>> transformer.fit(X) RatioFeatures(numerator_columns=['events'], denominator_columns=['trials']) >>> result = transformer.transform(X) >>> result shape: (4, 3) ┌────────┬────────┬─────────────────────┐ │ events │ trials │ events__div__trials │ │ i64 │ i64 │ f64 │ ├────────┼────────┼─────────────────────┤ │ 10 │ 1 │ 5.0 │ │ 20 │ 3 │ 5.0 │ │ 0 │ 0 │ 0.0 │ │ 5 │ 9 │ 0.5 │ └────────┴────────┴─────────────────────┘
Example 2: Multiple ratio features
>>> X2 = pl.DataFrame({ ... 'hits_a': [9, 19, 0], ... 'hits_b': [4, 9, 9], ... 'views_a': [2, 4, 0], ... 'views_b': [1, 4, 9], ... }) >>> transformer = RatioFeatures( ... numerator_columns=['hits_a', 'hits_b'], ... denominator_columns=['views_a', 'views_b'] ... ) >>> result = transformer.fit_transform(X2) >>> result shape: (3, 6) ┌────────┬────────┬─────────┬─────────┬──────────────────────┬──────────────────────┐ │ hits_a │ hits_b │ views_a │ views_b │ hits_a__div__views_a │ hits_b__div__views_b │ │ i64 │ i64 │ i64 │ i64 │ f64 │ f64 │ ├────────┼────────┼─────────┼─────────┼──────────────────────┼──────────────────────┤ │ 9 │ 4 │ 2 │ 1 │ 3.0 │ 2.0 │ │ 19 │ 9 │ 4 │ 4 │ 3.8 │ 1.8 │ │ 0 │ 9 │ 0 │ 9 │ 0.0 │ 0.9 │ └────────┴────────┴─────────┴─────────┴──────────────────────┴──────────────────────┘
Example 3: Custom column names
>>> transformer = RatioFeatures( ... numerator_columns=['events'], ... denominator_columns=['trials'], ... new_column_names=['smoothed_rate'] ... ) >>> result = transformer.fit_transform(X) >>> result shape: (4, 3) ┌────────┬────────┬───────────────┐ │ events │ trials │ smoothed_rate │ │ i64 │ i64 │ f64 │ ├────────┼────────┼───────────────┤ │ 10 │ 1 │ 5.0 │ │ 20 │ 3 │ 5.0 │ │ 0 │ 0 │ 0.0 │ │ 5 │ 9 │ 0.5 │ └────────┴────────┴───────────────┘
Example 4: With drop_columns=True
>>> transformer = RatioFeatures( ... numerator_columns=['events'], ... denominator_columns=['trials'], ... drop_columns=True ... ) >>> result = transformer.fit_transform(X) >>> result shape: (4, 1) ┌─────────────────────┐ │ events__div__trials │ │ f64 │ ├─────────────────────┤ │ 5.0 │ │ 5.0 │ │ 0.0 │ │ 0.5 │ └─────────────────────┘
Example 5: Null propagation
Input nulls still propagate through the ratio; only zero denominators are smoothed, not missing ones.
>>> X_nulls = pl.DataFrame({ ... 'A': [10, None, 30], ... 'B': [1, 3, None] ... }) >>> transformer = RatioFeatures( ... numerator_columns=['A'], ... denominator_columns=['B'] ... ) >>> result = transformer.fit_transform(X_nulls) >>> result shape: (3, 3) ┌──────┬──────┬────────────┐ │ A │ B │ A__div__B │ │ i64 │ i64 │ f64 │ ├──────┼──────┼────────────┤ │ 10 │ 1 │ 5.0 │ │ null │ 3 │ null │ │ 30 │ null │ null │ └──────┴──────┴────────────┘
- fit(X: polars.DataFrame, y: polars.Series | None = None) gators.feature_generation.ratio_features.RatioFeatures[source]#
Fit the transformer by generating column name mappings.
- Parameters:
X (pl.DataFrame) – Input DataFrame.
y (pl.Series, default=None) – Target variable. Not used, present here for compatibility.
- Returns:
Fitted transformer instance.
- Return type:
- class gators.feature_generation.ConcentrationIndexFeatures[source]#
Bases:
gators.transformer._base_transformer._BaseTransformerGenerates concentration index features by dividing a numerator column by the row-wise sum of a group of denominator columns.
Each feature is computed as:
numerator / (denom_1 + denom_2 + ... + denom_n [+ 1])The optional
+1(Laplace smoothing, enabled viasmoothing=True) prevents division-by-zero when all denominator columns are zero for a given row. This is useful for computing market share, click-through concentration, or any ratio of one quantity against the total of several others.- Parameters:
numerator_columns (list[str]) – List of column names to use as numerators. One concentration index is created per entry.
denominator_columns (list[list[str]]) – List of denominator column groups. Each inner list corresponds to the denominator columns for the matching numerator. Must have the same length as
numerator_columns, and each inner list must be non-empty.smoothing (bool, optional) – Whether to add
1to each denominator sum to prevent division by zero, by defaultTrue.new_column_names (list[str], optional) – Custom output column names. If
None, names are auto-generated as'{numerator}__conc__{denom1}__{denom2}__...', by defaultNone.drop_columns (bool, optional) – Whether to drop the original numerator and denominator columns after creating the concentration features, by default
False.
Examples
>>> from gators.feature_generation import ConcentrationIndexFeatures >>> import polars as pl
>>> X = pl.DataFrame({ ... 'brand_a': [10, 20, 0, 5], ... 'brand_b': [30, 10, 0, 15], ... 'brand_c': [60, 70, 0, 80], ... })
Example 1: Single concentration index
Compute the share of
brand_awithin the total ofbrand_bandbrand_c.>>> transformer = ConcentrationIndexFeatures( ... numerator_columns=['brand_a'], ... denominator_columns=[['brand_b', 'brand_c']], ... ) >>> transformer.fit(X) ConcentrationIndexFeatures(numerator_columns=['brand_a'], denominator_columns=[['brand_b', 'brand_c']], smoothing=True) >>> result = transformer.transform(X) >>> result shape: (4, 4) ┌─────────┬─────────┬─────────┬───────────────────────────────┐ │ brand_a │ brand_b │ brand_c │ brand_a__conc__brand_b__brand… │ │ i64 │ i64 │ i64 │ f64 │ ├─────────┼─────────┼─────────┼───────────────────────────────┤ │ 10 │ 30 │ 60 │ 0.109890 │ │ 20 │ 10 │ 70 │ 0.246914 │ │ 0 │ 0 │ 0 │ 0.0 │ │ 5 │ 15 │ 80 │ 0.052083 │ └─────────┴─────────┴─────────┴───────────────────────────────┘
Example 2: Multiple concentration indices
Each numerator has its own denominator group.
>>> X2 = pl.DataFrame({ ... 'clicks_a': [10, 20, 0], ... 'clicks_b': [5, 15, 10], ... 'views_x': [100, 200, 0], ... 'views_y': [50, 100, 0], ... 'impressions_x': [200, 400, 500], ... 'impressions_y': [300, 600, 500], ... }) >>> transformer = ConcentrationIndexFeatures( ... numerator_columns=['clicks_a', 'clicks_b'], ... denominator_columns=[ ... ['views_x', 'views_y'], ... ['impressions_x', 'impressions_y'], ... ], ... ) >>> result = transformer.fit_transform(X2)
Example 3: No smoothing
>>> X3 = pl.DataFrame({ ... 'sales': [10, 20, 0], ... 'total_a': [5, 10, 0], ... 'total_b': [5, 10, 0], ... }) >>> transformer = ConcentrationIndexFeatures( ... numerator_columns=['sales'], ... denominator_columns=[['total_a', 'total_b']], ... smoothing=False, ... ) >>> result = transformer.fit_transform(X3) >>> result shape: (3, 4) ┌───────┬─────────┬─────────┬────────────────────────────────────┐ │ sales │ total_a │ total_b │ sales__conc__total_a__total_b │ │ i64 │ i64 │ i64 │ f64 │ ├───────┼─────────┼─────────┼────────────────────────────────────┤ │ 10 │ 5 │ 5 │ 1.0 │ │ 20 │ 10 │ 10 │ 1.0 │ │ 0 │ 0 │ 0 │ NaN │ └───────┴─────────┴─────────┴────────────────────────────────────┘
Example 4: Custom column names
>>> transformer = ConcentrationIndexFeatures( ... numerator_columns=['brand_a'], ... denominator_columns=[['brand_b', 'brand_c']], ... new_column_names=['brand_a_share'], ... ) >>> result = transformer.fit_transform(X) >>> result shape: (4, 4) ┌─────────┬─────────┬─────────┬───────────────┐ │ brand_a │ brand_b │ brand_c │ brand_a_share │ │ i64 │ i64 │ i64 │ f64 │ ├─────────┼─────────┼─────────┼───────────────┤ │ 10 │ 30 │ 60 │ 0.109890 │ │ 20 │ 10 │ 70 │ 0.246914 │ │ 0 │ 0 │ 0 │ 0.0 │ │ 5 │ 15 │ 80 │ 0.052083 │ └─────────┴─────────┴─────────┴───────────────┘
Example 5: With drop_columns=True
>>> transformer = ConcentrationIndexFeatures( ... numerator_columns=['brand_a'], ... denominator_columns=[['brand_b', 'brand_c']], ... drop_columns=True, ... ) >>> result = transformer.fit_transform(X) >>> result shape: (4, 2) ┌─────────┬───────────────────────────────┐ │ brand_b │ brand_a__conc__brand_b__brand… │ │ i64 │ f64 │ ├─────────┼───────────────────────────────┤ ... └─────────┴───────────────────────────────┘
Example 6: Null behavior
Nulls in the numerator propagate to the result. Nulls in denominator columns are treated as
0bypl.sum_horizontal(they are ignored), so a row where all denominators arenullbehaves as if the denominator sum were0(and becomes1with smoothing enabled).>>> X_nulls = pl.DataFrame({ ... 'A': [10, None, 30], ... 'B': [5, 5, None], ... 'C': [5, 5, None], ... }) >>> transformer = ConcentrationIndexFeatures( ... numerator_columns=['A'], ... denominator_columns=[['B', 'C']], ... ) >>> result = transformer.fit_transform(X_nulls) >>> result shape: (3, 4) ┌──────┬──────┬──────┬───────────────┐ │ A │ B │ C │ A__conc__B__C │ │ i64 │ i64 │ i64 │ f64 │ ├──────┼──────┼──────┼───────────────┤ │ 10 │ 5 │ 5 │ 0.909091 │ │ null │ 5 │ 5 │ null │ │ 30 │ null │ null │ 30.0 │ └──────┴──────┴──────┴───────────────┘
- fit(X: polars.DataFrame, y: polars.Series | None = None) gators.feature_generation.concentration_index_features.ConcentrationIndexFeatures[source]#
Fit the transformer by generating column name mappings.
- Parameters:
X (pl.DataFrame) – Input DataFrame.
y (pl.Series, default=None) – Target variable. Not used, present here for compatibility.
- Returns:
Fitted transformer instance.
- Return type:
- class gators.feature_generation.AsymmetryIndexFeatures[source]#
Bases:
gators.transformer._base_transformer._BaseTransformerGenerates asymmetry index features for pairs of columns.
For each pair (x_i, y_i), computes:
asym(x, y) = (x / y) / (y / x + 1) = x² / (y · (x + y))Properties:
asym = 0.5whenx == y(perfectly symmetric).asym > 0.5whenx > y(x dominates); can be greater than 1.asym < 0.5whenx < y(y dominates).
With
smoothing=True(default),1is added to bothxandybefore computing the ratio, preventing division by zero.- Parameters:
x_columns (list[str]) – List of column names to use as the numerator side. One asymmetry index is created per entry.
y_columns (list[str]) – List of column names to use as the denominator side. Must have the same length as
x_columns.smoothing (bool, optional) – Whether to add
1to bothxandybefore computing the index, by defaultTrue.new_column_names (list[str], optional) – Custom output column names. If
None, names are auto-generated as'{x}__asym__{y}', by defaultNone.drop_columns (bool, optional) – Whether to drop the original
xandycolumns after creating the asymmetry features, by defaultFalse.
Examples
>>> from gators.feature_generation import AsymmetryIndexFeatures >>> import polars as pl
>>> X = pl.DataFrame({ ... 'clicks_a': [10, 0, 50], ... 'clicks_b': [10, 20, 5], ... })
Example 1: Basic usage (with smoothing)
>>> transformer = AsymmetryIndexFeatures( ... x_columns=['clicks_a'], ... y_columns=['clicks_b'], ... ) >>> transformer.fit(X) AsymmetryIndexFeatures(x_columns=['clicks_a'], y_columns=['clicks_b'], smoothing=True) >>> result = transformer.transform(X) >>> result shape: (3, 3) ┌──────────┬──────────┬──────────────────────────┐ │ clicks_a │ clicks_b │ clicks_a__asym__clicks_b │ │ i64 │ i64 │ f64 │ ╞══════════╪══════════╪══════════════════════════╡ │ 10 │ 10 │ 0.5 │ │ 0 │ 20 │ 0.002165 │ │ 50 │ 5 │ 7.605263 │ └──────────┴──────────┴──────────────────────────┘
Example 2: No smoothing
When
smoothing=False, equal-zero rows produceNaN.>>> transformer = AsymmetryIndexFeatures( ... x_columns=['clicks_a'], ... y_columns=['clicks_b'], ... smoothing=False, ... ) >>> result = transformer.fit_transform(X)
Example 3: Multiple pairs
>>> X2 = pl.DataFrame({ ... 'views_a': [100, 200, 0], ... 'views_b': [50, 200, 0], ... 'sales_a': [10, 30, 5], ... 'sales_b': [40, 30, 15], ... }) >>> transformer = AsymmetryIndexFeatures( ... x_columns=['views_a', 'sales_a'], ... y_columns=['views_b', 'sales_b'], ... ) >>> result = transformer.fit_transform(X2)
Example 4: Custom column names
>>> transformer = AsymmetryIndexFeatures( ... x_columns=['clicks_a'], ... y_columns=['clicks_b'], ... new_column_names=['click_asym'], ... ) >>> result = transformer.fit_transform(X)
Example 5: drop_columns=True
>>> transformer = AsymmetryIndexFeatures( ... x_columns=['clicks_a'], ... y_columns=['clicks_b'], ... drop_columns=True, ... ) >>> result = transformer.fit_transform(X) >>> result.columns ['clicks_a__asym__clicks_b']
- fit(X: polars.DataFrame, y: polars.Series | None = None) gators.feature_generation.asymmetry_index_features.AsymmetryIndexFeatures[source]#
Fit the transformer by generating column name mappings.
- Parameters:
X (pl.DataFrame) – Input DataFrame.
y (pl.Series, default=None) – Target variable. Not used, present here for compatibility.
- Returns:
Fitted transformer instance.
- Return type:
- class gators.feature_generation.GeneralizedRatioFeatures[source]#
Bases:
gators.transformer._base_transformer._BaseTransformerGenerates ratio features from weighted sums of column groups.
Each feature is computed as:
\[r = \frac{b_0 + \sum_i a_i \cdot x_i}{\sum_j b_j \cdot y_j + \varepsilon}\]where \(a_i\) and \(b_j\) are per-column scalar weights (defaulting to
1.0), \(b_0\) is an optional bias added to the numerator (defaulting to0.0), and \(\varepsilon\) is a small constant added to the denominator to prevent division by zero.When
numerator_columns[i]is an empty list ([]) andnumerator_biases[i]is set, the numerator reduces to the constant \(b_0\) — enabling expressions such as \(1/(f_1 + f_2 + \varepsilon)\).Unlike
ConcentrationIndexFeatures(single numerator column, equal weights) orBurstFeatures(one-to-one columns), this transformer allows different numbers of columns in the numerator and denominator groups and independent coefficients for each column in both groups.- Parameters:
numerator_columns (list[list[str]]) – One inner list of column names per output feature. The weighted sum of each inner group forms the numerator of the corresponding ratio. An empty inner list (
[]) is allowed whennumerator_biasesis provided, making the numerator a pure constant.denominator_columns (list[list[str]]) – One inner list of column names per output feature. The weighted sum of each inner group forms the denominator. Must have the same length as
numerator_columns. Each inner list must be non-empty.numerator_coefficients (list[list[float]], optional) – Scalar weights applied to each numerator column. Must mirror the shape of
numerator_columns. Defaults to1.0for every column whenNone.denominator_coefficients (list[list[float]], optional) – Scalar weights applied to each denominator column. Must mirror the shape of
denominator_columns. Defaults to1.0for every column whenNone.numerator_biases (list[float], optional) – Scalar constant \(b_0\) added to each numerator sum. Must have the same length as
numerator_columns. Defaults to0.0for every feature whenNone. Set to a non-zero value (and pass[]as the corresponding inner list innumerator_columns) to create a pure-constant numerator.epsilon (float, optional) – Small positive constant added to every denominator sum for numerical stability, by default
1.0.new_column_names (list[str], optional) – Custom output column names. If
None, names are auto-generated as'{num1}__{num2}__...____gratio__{denom1}__{denom2}__...'.drop_columns (bool, optional) – Whether to drop all source columns after creating the ratio features, by default
False.
Examples
>>> from gators.feature_generation import GeneralizedRatioFeatures >>> import polars as pl
>>> X = pl.DataFrame({ ... 'f1': [5.0, 20.0, 0.0], ... 'f2': [500.0, 200.0, 0.0], ... 'f3': [60.0, 100.0, 10.0], ... 'f4': [6000.0, 4000.0, 0.0], ... })
Example 1: Single ratio, equal weights
>>> transformer = GeneralizedRatioFeatures( ... numerator_columns=[['f1', 'f2']], ... denominator_columns=[['f3', 'f4']], ... ) >>> transformer.fit(X) GeneralizedRatioFeatures(numerator_columns=[['f1', 'f2']], denominator_columns=[['f3', 'f4']]) >>> result = transformer.transform(X) >>> 'f1__f2__gratio__f3__f4' in result.columns True
Example 2: Custom coefficients (different group sizes)
Weight the amount column twice as much as the count column in the numerator, and use a single-column denominator with a rescaling coefficient.
>>> transformer = GeneralizedRatioFeatures( ... numerator_columns=[['f1', 'f2']], ... denominator_columns=[['f3']], ... numerator_coefficients=[[1.0, 2.0]], ... denominator_coefficients=[[0.5]], ... ) >>> result = transformer.fit_transform(X)
Example 3: Multiple ratios
>>> transformer = GeneralizedRatioFeatures( ... numerator_columns=[['f1'], ['f2']], ... denominator_columns=[['f3'], ['f4']], ... ) >>> result = transformer.fit_transform(X) >>> 'f1__gratio__f3' in result.columns True >>> 'f2__gratio__f4' in result.columns True
Example 4: Custom column names
>>> transformer = GeneralizedRatioFeatures( ... numerator_columns=[['f1', 'f2']], ... denominator_columns=[['f3', 'f4']], ... new_column_names=['composite_ratio'], ... ) >>> result = transformer.fit_transform(X) >>> 'composite_ratio' in result.columns True
Example 5: With drop_columns=True
>>> transformer = GeneralizedRatioFeatures( ... numerator_columns=[['f1']], ... denominator_columns=[['f3']], ... drop_columns=True, ... ) >>> result = transformer.fit_transform(X) >>> 'f1' in result.columns False >>> 'f3' in result.columns False
Example 6: Constant numerator — 1 / (f3 + f4 + epsilon)
Pass an empty inner list for
numerator_columnsand setnumerator_biasesto the desired constant.>>> transformer = GeneralizedRatioFeatures( ... numerator_columns=[[]], ... denominator_columns=[['f3', 'f4']], ... numerator_biases=[1.0], ... ) >>> result = transformer.fit_transform(X) >>> 'const__gratio__f3__f4' in result.columns True
- fit(X: polars.DataFrame, y: polars.Series | None = None) gators.feature_generation.generalized_ratio_features.GeneralizedRatioFeatures[source]#
Fit the transformer by resolving coefficients and column name mappings.
- Parameters:
X (pl.DataFrame) – Input DataFrame.
y (pl.Series, default=None) – Target variable. Not used, present here for compatibility.
- Returns:
Fitted transformer instance.
- Return type:
- class gators.feature_generation.HHIFeatures[source]#
Bases:
gators.transformer._base_transformer._BaseTransformerGenerates Herfindahl–Hirschman Index (HHI) features from groups of columns.
For each group of columns, the HHI is computed as:
\[HHI = \sum_{i=1}^{n} s_i^2, \quad s_i = \frac{x_i}{\sum_j x_j + \varepsilon}\]where \(s_i\) is the row-wise market share of column \(x_i\) within its group, and \(\varepsilon\) is a small constant added to the total for numerical stability.
The HHI ranges from \(1/n\) (perfect equality, minimum concentration) to
1.0(total concentration, one column dominates). A value near1.0signals that a single component accounts for virtually all of the group total, whereas a value near \(1/n\) indicates a uniform distribution across the \(n\) columns.- Parameters:
column_groups (list[list[str]]) – One inner list of column names per output feature. Each inner list must contain at least two column names. One HHI value is produced per group.
epsilon (float, optional) – Small positive constant added to the total sum to prevent division by zero, by default
1e-8.new_column_names (list[str], optional) – Custom output column names. If
None, names are auto-generated as'{col1}__{col2}__...__hhi'.drop_columns (bool, optional) – Whether to drop all source columns after creating the HHI features, by default
False.
Examples
>>> from gators.feature_generation import HHIFeatures >>> import polars as pl
>>> X = pl.DataFrame({ ... 'brand_a': [10.0, 20.0, 0.0], ... 'brand_b': [30.0, 10.0, 0.0], ... 'brand_c': [60.0, 70.0, 0.0], ... })
Example 1: Single group
>>> transformer = HHIFeatures( ... column_groups=[['brand_a', 'brand_b', 'brand_c']], ... ) >>> transformer.fit(X) HHIFeatures(column_groups=[['brand_a', 'brand_b', 'brand_c']]) >>> result = transformer.transform(X) >>> 'brand_a__brand_b__brand_c__hhi' in result.columns True
Example 2: Multiple groups
Compute separate HHI values for two independent column groups.
>>> X2 = pl.DataFrame({ ... 'a1': [10.0, 50.0], ... 'a2': [90.0, 50.0], ... 'b1': [25.0, 25.0], ... 'b2': [25.0, 75.0], ... }) >>> transformer = HHIFeatures( ... column_groups=[['a1', 'a2'], ['b1', 'b2']], ... ) >>> result = transformer.fit_transform(X2) >>> 'a1__a2__hhi' in result.columns True >>> 'b1__b2__hhi' in result.columns True
Example 3: Custom column names
>>> transformer = HHIFeatures( ... column_groups=[['brand_a', 'brand_b', 'brand_c']], ... new_column_names=['market_hhi'], ... ) >>> result = transformer.fit_transform(X) >>> 'market_hhi' in result.columns True
Example 4: With drop_columns=True
>>> transformer = HHIFeatures( ... column_groups=[['brand_a', 'brand_b']], ... drop_columns=True, ... ) >>> result = transformer.fit_transform(X) >>> 'brand_a' in result.columns False >>> 'brand_b' in result.columns False >>> 'brand_c' in result.columns True
- fit(X: polars.DataFrame, y: polars.Series | None = None) gators.feature_generation.hhi_features.HHIFeatures[source]#
Fit the transformer by resolving column name mappings.
- Parameters:
X (pl.DataFrame) – Input DataFrame.
y (pl.Series, default=None) – Target variable. Not used, present here for compatibility.
- Returns:
Fitted transformer instance.
- Return type:
- class gators.feature_generation.WeightedSumFeatures[source]#
Bases:
gators.transformer._base_transformer._BaseTransformerGenerates weighted sum features from groups of columns.
Each feature is computed as:
\[f = b_0 + \sum_i a_i \cdot x_i\]where \(a_i\) are per-column scalar weights (defaulting to
1.0) and \(b_0\) is an optional bias term (defaulting to0.0).This is the numerator component of
GeneralizedRatioFeaturesexposed as a standalone transformer. It is useful for applying pre-computed regression coefficients, domain-defined scoring weights, or any fixed linear projection to a group of columns without dividing by a denominator.- Parameters:
column_groups (list[list[str]]) – One inner list of column names per output feature. The weighted sum of each inner group becomes the corresponding output column. Each inner list must be non-empty.
coefficients (list[list[float]], optional) – Scalar weights applied to each column within its group. Must mirror the shape of
column_groups. Defaults to1.0for every column whenNone.biases (list[float], optional) – Scalar intercept added to each weighted sum after all column terms are combined. Must have the same length as
column_groups. Defaults to0.0for every feature whenNone.new_column_names (list[str], optional) – Custom output column names. If
None, names are auto-generated as'{col1}__{col2}__...____wsum'.drop_columns (bool, optional) – Whether to drop all source columns after creating the features, by default
False.
Examples
>>> from gators.feature_generation import WeightedSumFeatures >>> import polars as pl
>>> X = pl.DataFrame({ ... 'f1': [5.0, 20.0, 0.0], ... 'f3': [500.0, 200.0, 0.0], ... 'f2': [60.0, 100.0, 10.0], ... 'amt_1h': [6000.0, 4000.0, 0.0], ... })
Example 1: Simple sum of a group (equal weights)
>>> transformer = WeightedSumFeatures( ... column_groups=[['f1', 'f3']], ... ) >>> transformer.fit(X) WeightedSumFeatures(column_groups=[['f1', 'f3']]) >>> result = transformer.transform(X) >>> 'f1__f3__wsum' in result.columns True
Example 2: Custom coefficients
Apply regression-style weights: weight amount twice as much as count.
>>> transformer = WeightedSumFeatures( ... column_groups=[['f1', 'f3']], ... coefficients=[[1.0, 2.0]], ... ) >>> result = transformer.fit_transform(X)
Example 3: With bias term
>>> transformer = WeightedSumFeatures( ... column_groups=[['f1', 'f3']], ... coefficients=[[0.5, 0.1]], ... biases=[-10.0], ... ) >>> result = transformer.fit_transform(X)
Example 4: Multiple output features
>>> transformer = WeightedSumFeatures( ... column_groups=[['f1', 'f2'], ['f3', 'amt_1h']], ... coefficients=[[1.0, -1.0], [1.0, -1.0]], ... ) >>> result = transformer.fit_transform(X) >>> 'f1__f2__wsum' in result.columns True >>> 'f3__amt_1h__wsum' in result.columns True
Example 5: Custom column names
>>> transformer = WeightedSumFeatures( ... column_groups=[['f1', 'f3']], ... new_column_names=['combined_score'], ... ) >>> result = transformer.fit_transform(X) >>> 'combined_score' in result.columns True
Example 6: With drop_columns=True
>>> transformer = WeightedSumFeatures( ... column_groups=[['f1', 'f2']], ... drop_columns=True, ... ) >>> result = transformer.fit_transform(X) >>> 'f1' in result.columns False >>> 'f2' in result.columns False
- fit(X: polars.DataFrame, y: polars.Series | None = None) gators.feature_generation.weighted_sum_features.WeightedSumFeatures[source]#
Fit the transformer by resolving coefficients, biases, and column name mappings.
- Parameters:
X (pl.DataFrame) – Input DataFrame.
y (pl.Series, default=None) – Target variable. Not used, present here for compatibility.
- Returns:
Fitted transformer instance.
- Return type: