Source code for modeva.data.feature_engineering.bivector

import copy
from typing import Tuple, Union

import numpy as np
import pandas as pd

from ...utils.constants import NUMERICAL
from ...utils.results import ValidationResult


[docs] class FEBivector: def __init__(self, dataset): self.dataset = dataset self.key = "data_fe_bivector" self.fitted_ = False
[docs] def transform(self, data): if isinstance(data, pd.DataFrame): X = data[self.feature_names_in_].values.astype(np.float32) else: data = pd.DataFrame(data, columns=self.all_feature_names_in_) X = data[self.feature_names_in_].values.astype(np.float32) result = self.gaml_transformer_.transform(X) return pd.DataFrame(result, columns=self.feature_names_out_)
[docs] def run(self, features: Union[str, Tuple] = None, dataset: str = "main", max_features: int = 300, selection: str = "variance"): """Generates grade-2 bivector features (pairwise feature products). For each pair (i, j) with i < j, computes B_ij = X_i * X_j. This captures pairwise nonlinear interactions based on Geometric Algebra wedge products. Parameters ---------- features : str or tuple, default=None Features to use for bivector computation. If None, all numerical features are used. dataset : {"main", "train", "test"}, default="main" Dataset used to fit the transformer. max_features : int, default=300 Maximum number of bivector features to generate. selection : {"all", "random", "variance"}, default="variance" How to select pairs if max_features < C(n,2): - "all": use all pairs (ignore max_features) - "random": random selection - "variance": highest variance pairs """ from .gaml.bivector import BivectorFeatures inputs = locals() inputs.pop('self', None) if features is None: features = [fn for fn, ft in zip(self.dataset.all_feature_names, self.dataset.all_feature_types) if ft == NUMERICAL] if isinstance(features, str): features = [features] self.all_feature_names_in_ = copy.copy(self.dataset.all_feature_names) self.feature_names_in_ = list(features) data = self.dataset.get_data(dataset=dataset) feature_indices = [self.dataset.all_feature_names.index(fn) for fn in features] X = data[:, feature_indices].astype(np.float32) self.gaml_transformer_ = BivectorFeatures(max_features=max_features, selection=selection) self.gaml_transformer_.fit(X) gaml_names = self.gaml_transformer_.get_feature_names(input_features=features) self.feature_names_out_ = [f"bv_{name}" for name in gaml_names] self.fitted_ = True result = ValidationResult(key=self.key, data=self.dataset.name, inputs=inputs, value={"n_features_out": len(self.feature_names_out_), "feature_names_out": self.feature_names_out_}) return result
[docs] class FEWedgeBivector: def __init__(self, dataset): self.dataset = dataset self.key = "data_fe_wedge_bivector" self.fitted_ = False
[docs] def transform(self, data): if isinstance(data, pd.DataFrame): X = data[self.feature_names_in_].values.astype(np.float32) else: data = pd.DataFrame(data, columns=self.all_feature_names_in_) X = data[self.feature_names_in_].values.astype(np.float32) result = self.gaml_transformer_.transform(X) return pd.DataFrame(result, columns=self.feature_names_out_)
[docs] def run(self, features: Union[str, Tuple] = None, dataset: str = "main", max_features: int = 300, selection: str = "variance"): """Generates wedge-weighted pairwise feature products. Suppresses nearly-collinear pairs and amplifies orthogonal ones by weighting each product X_i * X_j by the column-level sin(angle) between features i and j. Parameters ---------- features : str or tuple, default=None Features to use. If None, all numerical features are used. dataset : {"main", "train", "test"}, default="main" Dataset used to fit the transformer. max_features : int, default=300 Maximum number of pairs to generate. selection : {"variance", "random"}, default="variance" Pair selection method if max_features < C(d,2). """ from .gaml.bivector import WedgeBivector inputs = locals() inputs.pop('self', None) if features is None: features = [fn for fn, ft in zip(self.dataset.all_feature_names, self.dataset.all_feature_types) if ft == NUMERICAL] if isinstance(features, str): features = [features] self.all_feature_names_in_ = copy.copy(self.dataset.all_feature_names) self.feature_names_in_ = list(features) data = self.dataset.get_data(dataset=dataset) feature_indices = [self.dataset.all_feature_names.index(fn) for fn in features] X = data[:, feature_indices].astype(np.float32) self.gaml_transformer_ = WedgeBivector(max_features=max_features, selection=selection) self.gaml_transformer_.fit(X) gaml_names = self.gaml_transformer_.get_feature_names(input_features=features) self.feature_names_out_ = [f"wbv_{name}" for name in gaml_names] self.fitted_ = True result = ValidationResult(key=self.key, data=self.dataset.name, inputs=inputs, value={"n_features_out": len(self.feature_names_out_), "feature_names_out": self.feature_names_out_}) return result