Running DFS¶
deep_feature_synthesis() walks the database
from a target table, stacking primitives up to max_depth, and returns a
(feature_matrix, features) tuple.
feature_matrix, features = tusk.deep_feature_synthesis(
database=db,
target_table="customers",
agg_primitives=["mean", "count"],
trans_primitives=["month", "weekday"],
max_depth=2,
)
The matrix has one row per visible row of the target table, keyed by its
primary key. The target must have a primary_key; without one, compilation
raises SchemaError.
Lazy out, always¶
tusk builds one query plan and never collects. Whatever you feed it — a
pl.DataFrame, a pl.LazyFrame, a pandas frame, a duckdb relation — the
matrix comes back uncomputed, in your backend's native type, and you decide
when to compute:
On a backend with no separate lazy type, such as pandas or pyarrow, there is
nothing to collect and you already have your frame. The only collect() calls
in tusk are the ones validation makes when you ask
for a check by name.
Definitions without computation¶
features_only=True returns just the FeatureList of
Feature definitions, skipping compilation entirely.
Synthesis raises SchemaError instead if the
walk generates no features at all. Feed the definitions back later:
features = tusk.deep_feature_synthesis(db, "customers", features_only=True)
matrix = features.apply(db_new)
This is how you apply a feature set fitted on training data to new data. All
features in a FeatureList — and so all features passed to
apply_features() — must share one target table.
Cutoff times¶
cutoff_time is one global value, not a per-row frame. Only rows whose
row_creation_time is at or before it are visible.
It must be a datetime, not just a date. A date has no time of day, and
therefore risks differing behaviors across dataframe backends.
Its time zone awareness must match the database's row creation times. A
comparison between a tz-aware timestamp and a naive one has no defined
ordering, so a mismatch raises
ValidationError.
It filters the target table too, so the matrix can have fewer rows than the target — a row that did not exist yet at the cutoff has no features to compute.
Tables with no row_creation_time are timeless and pass through unfiltered, so
a cutoff on a database that declares none is silently a no-op.
With features_only=True the cutoff is ignored entirely, since nothing is
computed and feature definitions do not record it.
Feature names are SQL identifiers¶
Featuretools writes MEAN(transactions.amount); tusk writes
MEAN__transactions__amount. On a backend that generates SQL, dots and
parentheses parse as table qualifiers and function calls rather than as part of
a name, so the conventional form is unusable there. Every construct joins with
__:
| meaning | column name |
|---|---|
| a parent's column | customers__age |
| an aggregation | MEAN__transactions__amount |
| a zero-arity aggregation | COUNT__transactions |
| stacked to depth 2 | MEAN__sessions__MEAN__transactions__amount |
| a grouped transform | CUM_SUM__amount__by__session_id |
| one output of a multi-output primitive | QUANTILES__amount__0 |
The conventional form is kept on
Feature.display_name for logs, docs and
error messages. If a source column name collides with a generated one,
apply_features raises rather than silently dropping a column.
Warnings¶
DFS reports what it quietly skipped rather than failing:
UnmatchedPrimitiveWarning— a requested primitive matched no column of its input dtypes anywhere in the walk. Skipping is correct, since raising would break a zero-configurationdeep_feature_synthesis()on any schema that happens to lack a dtype family; skipping silently is not.CategoricalDtypeWarning— a Categorical or Enum column was skipped because the primitive requires a string input.
Both have their own class so you can filter them independently.