Feature engineering library for Rust, inspired by scikit-learn.
Built on Polars — all transformations operate natively on DataFrame and preserve column names.
[dependencies]
featrs = "0.3"use featrs::prelude::*;
let mut scaler = StandardScaler::new();
scaler.fit(data.clone())?;
let scaled = scaler.transform(data)?;| Category | Component | Description |
|---|---|---|
| Scaling | StandardScaler |
Z-score normalization (mean 0, variance 1) |
MinMaxScaler |
Scale to [0, 1] or custom range |
|
RobustScaler |
Scale using median and IQR (outlier-robust) | |
| Normalization | Normalizer |
Row-wise L1, L2, or Max normalization |
Binarizer |
Threshold-based binarization | |
| Encoding | OneHotEncoder |
Create binary dummy columns for categories |
LabelEncoder |
Encode labels as 0..n_classes-1 integers |
|
OrdinalEncoder |
Per-column category → integer encoding | |
CyclicalEncoder |
Sin/cos encoding for cyclical features (hour, month) | |
FeatureHasher |
Hash strings into a fixed number of buckets | |
| Imputation | SimpleImputer |
Fill nulls with mean, median, mode, or constant |
MissingIndicator |
Binary columns marking where values were missing | |
| Feature Generation | PolynomialFeatures |
Generate polynomial and interaction features |
Lagger |
Create lag features for time-series forecasting | |
RollingAggregator |
Rolling window mean, std, min, max, sum | |
Difference |
Differencing (x[t] - x[t-1]) and percentage change |
|
| Pipeline | Pipeline |
Sequentially chain multiple transformers |
ColumnTransformer |
Apply different transformers to different columns | |
| Selection | VarianceThreshold |
Remove low-variance features |
SelectKBest |
Select top-k features by statistical test (ANOVA F) | |
| Auto | AutoTypeDetector |
Auto-detect column types and apply default transforms |
use featrs::prelude::*;
let mut scaler = StandardScaler::new();
scaler.fit(df.clone())?;
let scaled = scaler.transform(df)?;use featrs::prelude::*;
let mut pipeline = Pipeline::new(vec![
("scale".into(), Box::new(StandardScaler::new())),
("poly".into(), Box::new(PolynomialFeatures::new(2)?)),
])?;
pipeline.fit(df.clone())?;
let result = pipeline.transform(df)?;use featrs::prelude::*;
let ct = ColumnTransformer::new(
vec![("scale".into(), Box::new(StandardScaler::new()), vec!["feat_a".into()])],
Remainder::Passthrough,
);use featrs::prelude::*;
let pf = PolynomialFeatures::builder()
.degree(3)
.include_bias(false)
.interaction_only(true)
.build()?;use featrs::prelude::*;
let mut vt = VarianceThreshold::new(0.01);
vt.fit(features.clone())?;
let filtered = vt.transform(features)?;
let mut skb = SelectKBest::new(5, Box::new(FClassif::new()));
skb.fit(features.clone(), target)?;
let selected = skb.transform(features)?;use featrs::prelude::*;
let mut lagger = Lagger::new(&["sales", "revenue"], &[1, 7, 30]);
lagger.fit(df.clone())?;
let lagged = lagger.transform(df)?; // adds sales_lag_1, sales_lag_7, ...use featrs::prelude::*;
use featrs::time_series::rolling::RollingFn;
let mut rolling = RollingAggregator::new(&["price"], 7, RollingFn::Mean);
rolling.fit(df.clone())?;
let result = rolling.transform(df)?; // adds price_mean_7use featrs::prelude::*;
let mut diff = Difference::diff(&["sales"], 1);
diff.fit(df.clone())?;
let result = diff.transform(df)?; // adds sales_diff_1
let mut pct = Difference::pct_change(&["price"], 1);
pct.fit(df.clone())?;
let result = pct.transform(df)?; // adds price_pct_1use featrs::prelude::*;
let mut enc = CyclicalEncoder::new(&["hour"], 24);
enc.fit(df.clone())?;
let result = enc.transform(df)?; // adds hour_sin, hour_cosuse featrs::prelude::*;
let mut fh = FeatureHasher::new(&["user_id", "category"], 100);
fh.fit(df.clone())?;
let hashed = fh.transform(df)?; // 100 hashed columnsuse featrs::prelude::*;
let mut ind = MissingIndicator::all();
ind.fit(df.clone())?;
let marked = ind.transform(df)?; // adds {col}_missing where nulls existuse featrs::prelude::*;
let mut atd = AutoTypeDetector::new()
.cat_threshold(30) // one-hot if < 30 unique values
.hash_buckets(200); // hash to 200 buckets otherwise
atd.fit(df.clone())?;
let result = atd.transform(df)?;MIT