Matlab Engineer Tabular Features
MathWorksOfficialCreated by MathWorks
- Skill
- Workflow
- Data and AI
- BSD-2-Clause
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a rigorous, data-driven workflow for feature engineering and selection in MATLAB. It manages the entire process from initial data intake and screening to feature pool generation, consensus-based selection, and diagnostic assessment. Designed for single-response supervised learning, it handles both tabular and non-tabular data by routing complex extractions to domain-specific skills. The workflow emphasizes train-test discipline, ensuring that feature generation and selection are performed on training data while maintaining a held-out set for unbiased evaluation. It concludes by delivering a self-contained inference script that allows users to reproduce the selected feature set on new data.
What it does
- Screens raw data to remove degenerate or constant predictors.
- Generates a candidate feature pool using either SMLT or domain-specific extractors.
- Performs consensus-based feature selection using a ranker panel and elbow-cut logic.
- Assesses feature set quality and stability through performance metrics and resampling.
- Produces a standalone MATLAB inference script for applying the selected features to new data.
- Generates comprehensive reports on the selection process and model-free feature quality.
When to use it
- When you need to engineer and select the best predictors for a single-response classification or regression task.
- When you require a structured, reproducible selection process rather than ad-hoc feature picking.
- When working with non-tabular data that requires specialized extraction before feature selection.
- When you need a reliable, self-contained script to apply your feature engineering pipeline to future datasets.
Advantages
- Enforces strict train-test discipline to prevent data leakage.
- Provides a consensus-driven approach to feature selection that reduces bias.
- Offers diagnostic tools to evaluate feature set stability and predictive performance.
- Delivers a portable inference script that encapsulates the entire transformation logic.
Usage examples
- “I have a table of sensor data and want to select the best features for a regression model.”
- The agent will initiate the intake phase, confirm your output directory, and guide you through the feature engineering and selection workflow.
- “Can you help me select features for my classification task and check if they are stable?”
- The agent will perform the selection and then offer to run stability gates to see if the same features are chosen across resampled data.
Install
8 of its files are not mirrored here, so install it from its source:
https://github.com/matlab/matlab-agentic-toolkit/tree/2a7f215e965597acf6ed495beb9e7b8aff45a3be/skills-catalog/ai-and-statistics/matlab-engineer-tabular-features
Documentation by MathWorks
Engineer Tabular Features
A lean, functional pipeline: intake → feature pool → select → assess →
deliver → report. There is no shared context object — each phase is a direct
call to leaf utilities in scripts/, and the reference file for each phase
carries the detail. Your value is the structured, data-driven process and,
above all, the consensus selection at its center — not an ad-hoc answer.
This skill bundles the workflow in references/ (per-phase detail read on demand)
and scripts/ (the computation and plotting utilities). Do not invoke files in
references/ as separate skills — they are loaded only via the Read tool when
the phase that needs them runs. The per-phase files call the pipeline's leaf helpers
for you; if you ever need a helper's signature,
references/internal-helpers.md documents each one's
inputs, outputs, and an example call — so you never open a helper's source.
When to Use
Engineering or selecting the best predictors for single-response supervised classification or regression on a plain in-memory
table.You want a structured, data-driven selection — a ranker panel, a consensus vote, and an elbow cut — rather than an ad-hoc hand-picked feature set.
The data is non-tabular (signals, images, battery/machinery telemetry): this skill routes extraction to the matching domain skill, then engineers, selects, assesses, and delivers on the resulting table (see references/domain-routing.md).
When NOT to Use
Multi-response problems — this skill is single-response only.
Model training, tuning, or deployment — it prepares features and stops. Hand the delivered table to a model-training/classification workflow to fit and compare models.
Raw data acquisition. And for the extraction step on non-tabular data, the actual feature computation belongs to the matching domain extraction skill — this skill orchestrates that handoff (see references/domain-routing.md), it does not re-implement it.
Requires the Statistics and Machine Learning Toolbox (SMLT) —
gencfeatures/genrfeatures build the pool and the ranker/assessment utilities
are SMLT-based. MATLAB Report Generator is optional (enables the PDF report;
markdown is always produced).
Running MATLAB
Run all MATLAB through the MATLAB MCP server (mcp__matlab__evaluate_matlab_code,
or mcp__matlab__run_matlab_file for scripts). Set project_path to this skill's
scripts/ directory so the utilities resolve on the current working folder
without any addpath calls. Every utility is a leaf function called directly —
there is no initialization step and no context object to construct. Validate any
code you author with mcp__matlab__check_matlab_code before running it.
Start each dataset from scratch — but use the live workspace within a run. The
MCP session is stateful, so a run's intermediates should live in the workspace: set
RawTbl, Splits, FullEng, SelectedNames, Baseline, etc. once and pass them
phase-to-phase. Do not round-trip them through save/load .mat files (noise,
risks stale reads) and do not addpath. Across different datasets/runs, carry
nothing — begin each analysis by setting every variable afresh.
Communication style while running this skill
Talk to the user about their data and results, not the skill's plumbing.
Everything under references/ and scripts/ is internal. Rule of thumb: if a
sentence would only make sense to someone who has read this skill's source files,
don't say it.
Never name internal files, helpers, or phase/gating mechanics.
runConsensusSelection,GenInfo.BinaryReliant, "the redundancy dimension", etc. are internal — give the outcome ("these features duplicate each other, so I'm keeping the strongest"), not the mechanism. Name an internal only when it is a problem the user can act on. Read reference files silently.Use plain words for each check. The three assessment reads: performance → whether the new features improve predictions (a held-out estimate, or a cross-validated mean ± std); fixed-pool stability → whether the same features get picked when rows are resampled; generation stability → whether the same features get built and picked when the whole pipeline re-runs on resampled rows. Say "the ranking step" not "the borda voter"; "reliably re-selected" not "consensus core".
Don't narrate uncertainty or mid-flight course-corrections — settle how a function is called silently, then report only the outcome. Surface a difficulty only when the user must decide on it.
Announce cost before long work, one sentence — pool size before selection, expected time before a K-fold, and before either stability gate (both re-run selection many times; the generation gate also re-builds the pool each time). And surface user-facing questions verbatim where a phase specifies one (output directory, wide-input, domain routing).
Report what was dropped at every phase (screened predictors, excluded WoE columns, skipped rankers) — a silent shrink reads as data loss. But selection evaluates the pool, it doesn't necessarily shrink it — never call it a reduction.
Output directory — REQUIRED, HARD HALT
Deliverables are written to disk. Always confirm the output directory with the user before writing anything. Do not assume the working directory, do not create one silently.
The pipeline
Follow the phases in order. Each links to its reference; read the reference before executing the phase.
1. Intake — references/intake.md
Ask before running any code. Intake is a required conversation, not a
default-fill. Confirm every run parameter with the user before proceeding past
the screen — data source, response, dataset name, output directory (hard-halt),
domain description, separate-test-set, model family (+ lens if agnostic),
evaluation strategy, report opt-out — asked one at a time, in the order pinned in
intake.md §1 (never dump the whole list in one message).
Every item must be asked; offer a default where one exists, but confirm rather than
assume — when the opening request implies an answer, state what you inferred and
have the user confirm it. Two are
non-negotiable — do not proceed without an explicit answer:
Output directory — the disk-write hard-halt (see above).
Domain description — the sole input to domain routing. Ask what the data is and where it came from; route to a domain extractor if one fits, else the generic path. Never infer the domain from column names or fall through to generic generation on silence. Verbatim prompt in intake.md.
Then assemble the data into one plain table, briefly confirm what was
loaded (shape, response, problem type), and screen degenerate predictors. A
timetable/tall/gpuArray/datastore isn't a dead end — it's a signal to run a
tabularizing step first (often a domain skill, see
domain-routing.md) and then re-enter intake with
the resulting table; only halt if no tabular path exists.
[ScreenedTbl, ScreenInfo] = screenPredictors(RawTbl, Response); % or (X, y)screenPredictors handles the polymorphic response (a name already in the table,
or a separately-supplied vector/table it concatenates) and drops constant and
near-empty predictors. Then profile, reserve the user's untouched slice, and split:
Profile = profileForSplit(ScreenedTbl, ScreenInfo.ResponseVar);
[WorkingIdx, UserHeldOutIdx, ReserveInfo] = reserveHoldoutForUser( ...
ScreenedTbl, Profile.ProblemType, ScreenInfo.ResponseVar, ReserveForUser = HasNoSeparateTest);
[Splits, SplitDecision] = splitStrategy(ScreenedTbl, Profile.ProblemType, ...
ScreenInfo.ResponseVar, Subset = WorkingIdx, EvaluationStrategy = EvaluationStrategy);reserveHoldoutForUser sets aside an untouched slice for the user's own testing
when they have no separate test set (default 20%, user-settable via HoldoutFraction;
else nothing); the rest is the working data all phases run on. When a carve
happens, materialize the slice as the table variable's name + _test from the original
rows and narrate the split in plain words (fraction, row counts, and method —
stratified/random — from ReserveInfo); see intake.md. splitStrategy then sets
Splits.TrainIdx/.TestIdx over the working rows — a train/test split under
holdout, or all working rows with empty TestIdx under cross_validated.
Generation and selection run on TrainIdx only.
2. Feature pool — references/feature-pool.md
Produce the candidate pool. First check whether a domain skill fits the data (references/domain-routing.md); otherwise use the default SMLT path:
Opts.TargetModel = TargetModel; Opts.Standardization = "auto";
OptArgs = namedargs2cell(Opts);
TrainIdx = Splits.TrainIdx; TestIdx = Splits.TestIdx;
TrainTbl = ScreenedTbl(TrainIdx, :); % fit generation on train rows only
[~, Transformer, GenInfo] = generateFeatures(TrainTbl, Response, ProblemType, OptArgs{:});
Recipe = Transformer; % SMLT recipe (domain path: the captured struct)
FullEng = transformFeatures(Recipe, ScreenedTbl); % engineered pool over ALL rows
FullEng.(Response) = ScreenedTbl.(Response); % transformFeatures returns predictors onlyGenerate-only (external consensus does the cutting). Mind the wide-input guard
(generateFeatures:tooManyPredictors) — hold the wide-input conversation and
re-call with Opts.NumFeatures set. FullEng is the canonical pool: engineered
over all rows with the response re-attached, train-fit so the held-out rows
stay leakage-clean. On the domain path the captured table already spans all
rows — use it as FullEng directly. Whichever path runs, downstream reads only
the pool contract (Recipe, describeFeatures, transformFeatures) — never
the producer. Announce GenInfo.PoolSize.
Set OriginalData/OriginalPredVars here — the baseline's "original" reference is
path-dependent (raw columns when they exist, else the full pool). assess.md §1 pins the rule.
3. Select — references/select.md
The heart of the skill. One call runs the ranker panel, the consensus vote, and the elbow cut:
[SelectedNames, VoteTable, PanelInfo] = runConsensusSelection( ...
FullEng(TrainIdx, :), Response, ProblemType, ExcludeFeatures = GenInfo.BinaryReliant, ...
TargetModel = TargetModel);Selection runs on the training rows only — slice FullEng(TrainIdx,:); the
held-out rows never enter ranking. TargetModel gates the ranker panel: a declared
family runs its own embedded probe (linear→lasso, tree_ensemble→oob,
kernel_distance→nca) plus the two model-agnostic rankers; agnostic (the default)
keeps the full five-ranker panel. Report PanelInfo.Reasoning. Build a
plotSelectionConsensus figure only when the report is on (GenerateReport) —
every figure is a report input, so a report opt-out skips all plot calls.
The score elbow alone sets the count (see select.md); when the scores decline too gradually to show a knee the full pool is kept — report a keep-all as "the candidates are comparably useful," not as a failure to select.
4. Assess — references/assess.md
Diagnostic only; never revises the delivered set. The performance read follows the evaluation strategy chosen at intake:
Quality = featureSetQuality(ScreenedTbl, FullEng, TrainIdx, Response, ...
OriginalPredVars, SelectedNames, ProblemType); % model-free, always
if Splits.Strategy == "holdout" % point estimate on the held slice
Baseline = baselineComparison(ScreenedTbl, FullEng, TrainIdx, TestIdx, Response, ...
OriginalPredVars, SelectedNames, ProblemType, TargetModel = TargetModel, InternalCV = false);
else % cross_validated: folds ARE the estimate
KFold = assessKFold(ScreenedTbl(TrainIdx, :), Response, ProblemType, CVP, TargetModel = TargetModel);
end
% Stability — two independent opt-in gates, both default OFF (assess.md §3):
rng(0, "twister");
SelStab = assessSelectionStability(StabEng, Response, ProblemType, ... % Gate 1 (fixed pool)
ExcludeFeatures = StabExclude, TargetModel = TargetModel, MaxRows = RowBudget);
GenStab = assessGenerationStability(RawTrainTbl, Response, ProblemType, ... % Gate 2 (pool regenerated)
TargetModel = TargetModel, MaxRows = RowBudget);Pass a row budget to both gates as MaxRows only when the user named one
("~1000 patients is fine" → MaxRows = 1000) to keep the audit affordable; else
omit it (default 3000) — don't pass an empty value.
Quality (model-free representation quality) and the strategy's performance read
are unconditional: under holdout, baselineComparison scores the held
TestIdx as a point estimate (InternalCV = false, no error bars); under
cross_validated (TestIdx empty), assessKFold is the headline (regenerates per
fold, reports mean ± std, performance-only) — skip baselineComparison.
Stability is two independent opt-in gates, both default OFF (see
assess.md §3): Gate 1 SelStab (re-selects on a fixed
pool) and Gate 2 GenStab (also regenerates the pool per subsample; needs a
re-runnable fitter — SMLT by default, a domain path needs Recipe.Fit, §3c). Both
are computationally expensive — each re-runs the consensus selection M times, and
Gate 2 adds a full regeneration on top — so both stay off unless asked for. Both
grade the procedure; neither revises the delivered set. Offer the choice, don't
default them on, and announce the cost — ask "Want a stability check — would the
same features get picked if the data were resampled? It re-runs selection many times
so it takes a while; a deeper version also re-builds the features each time, costing
more again." and run only what the user opts into. When neither is requested, add
neither and set Results.StabilitySkipped = true (report says "not requested"). Skip
a requested gate automatically with that flag when the pool has < 2 features or < 2
subsamples are possible.
5. Deliver — references/deliver.md
Write the artifacts to the confirmed output directory:
[ScriptPath, MatPath] = writeInferenceScript(Recipe, SelectedNames, OutputDir, ...
DatasetName = DatasetName, ResponseVar = ResponseVar);
SelectedTbl = selectFeatures(Recipe, RawTbl, SelectedNames); % in-workspace CHECK only — do NOT write to diskThe recipe is the inference recipe — fe_transform_<dataset>.m +
its companion .mat reproduce the exact selected set on new raw data with one
call. writeInferenceScript is generator-agnostic (SMLT FeatureTransformer or a
Kind=="domain" recipe) and inlines its logic, so the deliverable stands alone.
SelectedTbl is only a verification value — never write the feature table to
disk (features_<dataset>.mat/.csv); the inference script regenerates it on
demand, so a materialized dump is redundant. Write the figures' fig_*.svg
alongside only when the report is on; under a report opt-out the inference
script + state are the whole deliverable.
Also save the full Results struct unconditionally — every run result at full
resolution, so nothing a plot or a truncated table only previews is lost:
ResultsMatPath = saveResults(Results, OutputDir, DatasetName = DatasetName);This fe_results_<dataset>.mat ships even on a report opt-out (assemble Results
first — see report.md). Tell the user it exists, and that
the same data is live in the Results workspace variable this session.
6. Report — references/report.md
Optional (opt-out, default on) — run only if the user kept the report at intake
(GenerateReport == true), the same flag that gates every figure in
Select/Assess/Deliver. When run, assemble the plain Results struct from every
prior phase and emit:
MdPath = generateFeatureReport(Results, OutputDir); % always
if hasReportGenerator(); PdfPath = generateFeatureReportPdf(Results, OutputDir); end % if available7. Hand off to model training (offer, don't invoke)
This skill stops at features. If the user's ask mentioned training a model (supervised learning, classification, regression, etc.) and an installed skill covers tabular model training, name that candidate once at close-out and offer to hand off — semantic match against the injected skill list, confirm before invoking, no hardcoded skill names (same discovery rule as domain-routing.md). If nothing fits or the ask was features-only, stop silently.
Behavioral contract (all phases)
Train/test discipline — generation, selection, and all training-side statistics use the training rows only; the test rows certify the delivered artifact and are never seen before then.
Generator-agnostic downstream — after the pool phase, touch generator internals only through
describeFeatures/transformFeatures.No hidden truncation — if you cap, sample, or skip anything, say so.
Never fabricate a pool — when routing offers a choice, or the data needs a tabularizing step this skill can't perform, presenting the choice or asking ends the turn. Wait for the user before building the pool by any means (domain skill, generic path, or an extractor of your own).
Confirm outward-facing actions — writing to disk is confirmed once at intake (the output-dir hard-halt); don't write outside it.
MathWorks Coding Guidelines for any MATLAB you author (lowerCamelCase,
argumentsblocks, ≤6 in / ≤4 out,endterminators; modern APIs).
MATLAB and Simulink are registered trademarks of The MathWorks, Inc. See www.mathworks.com/trademarks for a list of additional trademarks.
Copyright 2026 The MathWorks, Inc.
Files
- ATTRIBUTION.mdAdded by AutoGineer1.1 KiB
- LICENSELicence file added by AutoGineer1.4 KiB
- SKILL.md18.9 KiB
- manifest.yaml570 B
- references/assess.md16.7 KiB
- references/deliver.md13.0 KiB
- references/domain-routing.md18.1 KiB
- references/feature-pool.md10.9 KiB
- references/intake.md16.9 KiB
- references/internal-helpers.md12.2 KiB
- references/provider-protocol.md12.3 KiB
- references/report.md9.7 KiB
- references/reproduce-pool.md7.1 KiB
- references/select.md7.1 KiB
- scripts/BordaVoter.m2.3 KiB
- scripts/ConsensusVoter.m2.5 KiB
- scripts/MajorityVoter.m2.0 KiB
- scripts/RRFVoter.m1.9 KiB
- scripts/assessGenerationStability.m11.9 KiB
- scripts/assessKFold.m20.6 KiB
- scripts/assessSelectionStability.m9.8 KiB
- scripts/baselineComparison.m18.9 KiB
- scripts/buildRecipe.m8.2 KiB
- scripts/chooseNumBins.m5.0 KiB
- scripts/chooseStandardization.m4.5 KiB
- scripts/computeNSelect.m2.9 KiB
- scripts/createConsensusVoter.m2.2 KiB
- scripts/describeFeatures.m5.1 KiB
- scripts/expandBlockMap.p(opens on GitHub)Not mirrored: opens on GitHub440 B
- scripts/featureSetQuality.m12.3 KiB
- scripts/generateFeatureReport.m67.2 KiB
- scripts/generateFeatureReportPdf.m64.9 KiB
- scripts/generateFeatures.m14.6 KiB
- scripts/hasReportGenerator.m630 B
- scripts/isDomainRecipe.m1.4 KiB
- scripts/isStringEmpty.m577 B
- scripts/lambdaSequence.p(opens on GitHub)Not mirrored: opens on GitHub637 B
- scripts/lassoFeatureRanking.p(opens on GitHub)Not mirrored: opens on GitHub971 B
- scripts/macroF1Score.m931 B
- scripts/mrmrFeatureRanking.p(opens on GitHub)Not mirrored: opens on GitHub239 B
- scripts/mutualInformationPanel.p(opens on GitHub)Not mirrored: opens on GitHub1.1 KiB
- scripts/ncaFeatureRanking.p(opens on GitHub)Not mirrored: opens on GitHub464 B
- scripts/nogueiraStability.m3.8 KiB
- scripts/oobFeatureRanking.p(opens on GitHub)Not mirrored: opens on GitHub414 B
- scripts/plotSelectionConsensus.m2.7 KiB
- scripts/plotSelectionDecision.m6.3 KiB
- scripts/plotSelectionStability.m10.2 KiB
- scripts/plotValidationBaseline.m6.1 KiB
- scripts/printSummary.m18.3 KiB
- scripts/profileForSplit.m6.2 KiB
- scripts/rankAgreement.m1.3 KiB
- scripts/rankFeatures.m6.2 KiB
- scripts/reserveHoldoutForUser.m5.5 KiB
- scripts/runConsensusSelection.m9.4 KiB
- scripts/sampleSkewness.m600 B
- scripts/saveResults.m2.1 KiB
- scripts/scoreFeaturePanel.p(opens on GitHub)Not mirrored: opens on GitHub1.6 KiB
- scripts/screenPredictors.m13.3 KiB
- scripts/selectFeatures.m2.3 KiB
- scripts/splitStrategy.m5.4 KiB
- scripts/stratifiedDownsampleRows.m2.2 KiB
- scripts/stratifiedSubsampleIndices.m3.4 KiB
- scripts/transformFeatures.m6.1 KiB
- scripts/validateRecipe.m12.3 KiB
- scripts/writeInferenceScript.m10.5 KiB
references/assess.md
Assess — is the engineered feature set actually good?
Assessment answers three independent questions about the set selection produced. None of it revises the delivered set — it is diagnostic only — but it is what lets you report the set honestly rather than asserting it.
| Question | Tool | Read |
|---|---|---|
| Does it predict better than raw / naive? | baselineComparison (holdout) or assessKFold (cross_validated) | point estimate, or cross-fold mean ± std |
| Is it a good representation regardless of model? | featureSetQuality | relevance / redundancy / compactness |
| Do we trust the selector — same set again (fixed pool)? re-generated pool? | assessSelectionStability / assessGenerationStability | Nogueira stability + reliably re-selected features |
The performance question is answered by one tool, chosen by the evaluation
strategy (§3): baselineComparison under holdout, assessKFold under
cross_validated. The model-specific read is scored through the lens of the
declared model family (TargetModel), not an assumed random forest, so the
baseline reflects the user's actual downstream model.
Narrating — name each read correctly. Call the performance read "cross-validated"
only under cross_validated; under holdout it is a single point estimate — say
so. The two stability gates (§3) both come from stratified subsampling of the
training rows, not the performance k-fold — frame them as "re-selecting on resampled
data," never as fold results. Name which one you mean: the fixed-pool read holds the
pool constant; the generation read regenerates it per subsample.
1. Model-specific baseline — baselineComparison (holdout strategy)
The performance read for the holdout evaluation strategy. Trains the declared
family on original vs. engineered features and contrasts both against a
naive predictor on the held TestIdx, as a point estimate. Under
cross_validated this is skipped — assessKFold (§3) is the headline instead.
Baseline = baselineComparison(OriginalData, FullEng, TrainIdx, TestIdx, Response, ...
OriginalPredVars, SelectedNames, ProblemType, ...
TargetModel = TargetModel, PrimaryFamily = PrimaryFamily, InternalCV = false);Both tables span all rows: this fits on TrainIdx and scores on the held-out
TestIdx, so it needs the engineered features present for the test rows too
(FullEng, not its train slice). InternalCV = false — a single split has no
honest performance variance, so no error bars are drawn (see the plot call below);
the number is reported as a plain point estimate.
What "original" is — the reference the lift is measured against (path-dependent).
baselineComparison and featureSetQuality both contrast an original set
(OriginalData + OriginalPredVars) against the selected set. baselineComparison
is agnostic about what "original" means — it fits a model on whatever columns you
name — so the orchestrator must set the reference, once, at the end of the pool
phase, by whether a model-ready raw predictor table exists:
RawPredVars = setdiff(string(ScreenedTbl.Properties.VariableNames), Response, "stable");
% The original reference is FIT by a learner, so keep only model-ready predictor
% types. A datetime/duration column is a valid GENERATION input (the generator
% extracts components from it into the pool) but a learner cannot fit a bare
% datetime — drop those types from the reference only, never from generation.
IsModelReady = ~varfun(@(c) isdatetime(c) || isduration(c) || isa(c, "calendarDuration"), ...
ScreenedTbl(:, RawPredVars), OutputFormat = "uniform");
RawPredVars = RawPredVars(IsModelReady);
if ~isempty(RawPredVars) % model-ready raw predictor columns exist: SMLT path, or a domain TABLE input
OriginalData = ScreenedTbl; OriginalPredVars = RawPredVars;
% The fitters and MI panel take floating-point, not integer types. Integer
% predictors ARE fittable once widened, so cast them to double in the reference
% copy only (generation already ran on the untouched integer columns).
for v = OriginalPredVars
if isinteger(OriginalData.(v)); OriginalData.(v) = double(OriginalData.(v)); end
end
else % non-tabular domain input (signal/image/…), or only non-fittable raw types: no columns to fit on
OriginalData = FullEng; OriginalPredVars = setdiff(string(FullEng.Properties.VariableNames), Response, "stable");
endRaw predictor table exists → original = the model-ready raw screened columns (datetime/duration and other non-fittable types filtered out — they still feed generation, they just can't be a learner's input), so the baseline reports the lift of feature engineering + selection over the raw inputs. This is the SMLT path and any domain path whose input was already a table of model-ready predictors.
No raw predictor table (a signal vector, image,
timetablethe domain skill extracted from) → there are no columns a learner could fit on, so original = the full extracted pool (FullEng) and the baseline reports the lift of consensus selection over the whole pool ("did cutting to the selected set beat keeping everything?"). This is the case you'd otherwise mis-handle: passing empty raw columns collapsesbaselineComparisonto engineered-vs-naive only (itsHasOrig = ~isempty(OrigX)guard), losing the comparison entirely.
Either way it is the honest lift question for that input; the shipped decision
(selection on train rows) is unchanged. assessKFold sets its own equivalent
internally (assessKFold.m:108, raw-columns case). See
domain-routing.md §5.
The panel of families follows
TargetModel:agnostic→ all three (bagged ensemble, linear, kernel); a declared family trains through that family alone (the user named their downstream model, so a reference lens adds nothing). The panel is evaluation-only — N families scored on the one engineered set; it never produces N feature sets.The primary lens (the family whose metrics become the top-level fields) defaults to the panel's first family. Under
agnosticonly, aPrimaryFamilyof"bag","linear", or"kernel"promotes that family instead — keeping the broad panel yet headlining on the chosen lens. A declared single-family model fixes its own primary; namingPrimaryFamilythere errors.Linear/kernel learners auto-encode categorical predictors from a table (no manual dummy-coding); multi-class classification is wrapped in one-vs-one ECOC. No manual scaling — engineered features arrive standardized from the generator; originals are scored raw.
Metric is AUC + macro-F1 (classification) or RMSE (regression).
Resultcarries.Naive/.Original/.Engineered,.Improvement, pass/beat flags,.Primary, and.Panel. WithInternalCV = false,.CV/.CVOriginalcarryNumFolds = 0(no variance computed).
.Naive, .Original, .Engineered, and .Metric are the primary family's
scalars; .MacroF1 and .Accuracy are structs with .Engineered/.Original
fields — index into them, don't print them whole. .Accuracy is always present, with
a separate .AccuracyReliable flag (true when the training imbalance ratio ≤ 1.5).
Narrate the metrics and imbalance in words. State which metric leads and why
(classification: AUC, with macro-F1 as a balanced-per-class read; regression:
RMSE vs a train-mean naive), before quoting its value. For classification, speak
the imbalance ratio (Profile.ClassBalance.ImbalanceRatio): when .AccuracyReliable
is false, say the classes are imbalanced (ratio in words, e.g. "about 4:1"), that raw
accuracy is therefore misleading, and that this is why AUC/macro-F1 lead — report
accuracy with that caveat, never drop it, and never name the flag field.
plotValidationBaseline takes the .Panel struct array — one naive/original/
engineered bar group per family (three under agnostic, one for a declared family).
Holdout point estimates carry no std, so no error caps. Draw only when the report
is on (GenerateReport) — like every figure, it's a report input:
if GenerateReport
plotValidationBaseline(Baseline.Panel, Baseline.Metric, OutputDir);
end2. Model-free quality — featureSetQuality
Judges the representation with no trained model, comparing raw originals vs. the selected engineered set along three model-agnostic dimensions. It fits nothing and grades nothing for generalization, so it has no leakage exposure — it describes the shipped set (refit on all working rows) and runs at delivery, not here (deliver.md §2b).
Relevance — mutual information with the response, computed with the same algorithm
fscmrmr/fsrmrmruse..Max(peak single-feature signal) and.NumRelevant(breadth above threshold). A raw→eng rise means FE concentrated signal onto an accessible axis; a drop means it destroyed the best signal.Redundancy — pairwise MI across feature pairs, both the mean (set-wide duplication) and the max (the single worst near-duplicate pair the mean can hide). One statistic spanning numeric–numeric, categorical–categorical, and numeric–categorical pairs, so a numeric feature duplicating a categorical one is visible. Lower is better.
Compactness — feature count and effective dimensionality (participation ratio of the numeric correlation eigenvalues). Lower effective dim at a comparable count is a tighter representation.
Announce every model-free output. The console close-out (printSummary, run at
delivery — deliver.md §6) prints the full
relevance | redundancy | compactness line — narrate all of it, not a subset.
Verdicts are diagnostic; this never gates the pipeline. Under cross_validated,
assessKFold also reports a per-fold quality band on these dimensions (§3) —
how much the recipe's output varies across resampling, not the shipped set's quality.
3. Performance strategy + two opt-in stability gates
Everything here grades the procedure; the shipped set is refit on all working data (deliver.md) and never revised. Performance and stability are separate axes.
Performance follows the evaluation strategy, and the two are mutually exclusive:
if Splits.Strategy == "holdout" % §1: point estimate on the held slice
Baseline = baselineComparison(OriginalData, FullEng, TrainIdx, TestIdx, Response, ...
OriginalPredVars, SelectedNames, ProblemType, TargetModel = TargetModel, InternalCV = false);
else % cross_validated: the folds ARE the estimate
rng(0, "twister");
CVP = cvpartition(RawTrainTbl.(Response), KFold=5); % height(...) for regression
KFold = assessKFold(RawTrainTbl, Response, ProblemType, CVP, TargetModel = TargetModel);
endassessKFold is performance-only (mean ± std over folds, plus a per-fold quality
band); it carries no stability read. Plot either with plotValidationBaseline —
holdout point estimates draw no error caps, k-fold panels do (pass a fold-count
Subtitle).
Stability is two independent opt-in gates, both default OFF, each requested on its own under either strategy:
Gate 1 — assessSelectionStability | Gate 2 — assessGenerationStability | |
|---|---|---|
| Asks | is the selector sensitive to row noise? | how much does the whole recipe move? |
| Per subsample | re-select on a fixed pool | regenerate the pool, then select |
| Cost / fitter | M× the consensus panel; no fitter | M× (generate + select); needs a re-runnable fitter (§3c) |
Both draw M stratified subsamples (stratifiedSubsampleIndices) and report the
Nogueira chance-corrected stability index + per-feature selection frequency;
features re-selected in ≥ CoreThreshold of subsamples are the reliably
re-selected list (say it in those plain words, never "consensus core"). Run on the
same M and SubsampleFraction, their Nogueira numbers are comparable and the
gap is the generation-variance contribution — so Gate 1 is the base and Gate 2's
gap only means something alongside it. The pool-regenerating Gate 2 additionally
reports drift (features present in only some subsamples' pools).
rng(0, "twister"); % caller owns the seed
SelStab = assessSelectionStability(StabEng, Response, ProblemType, ... % Gate 1
ExcludeFeatures = StabExclude, TargetModel = TargetModel, MaxRows = RowBudget);
GenStab = assessGenerationStability(RawTrainTbl, Response, ProblemType, ... % Gate 2
TargetModel = TargetModel, MaxRows = RowBudget); % Producer defaults to SMLTRowBudget is the intake row budget: when the user names a working-set size for
speed, both gates stratified-downsample to it before the subsample re-runs (unset →
default 3000). It's the first lever to reach for on a slow audit — cut rows before
cutting M or SubsampleFraction, since fewer rows keeps the subsample count (and
the Nogueira estimate's precision) intact.
Both gates are expensive and both default OFF. Gate 1 re-runs the full consensus
panel M times over a pool built once; Gate 2 is heavier still — it also regenerates
the pool each subsample (≈ M× a full generation), slowing sharply on wide data.
Announce the cost in one sentence before running either (not just Gate 2), and
offer to lower M or SubsampleFraction if it's too slow. Both print a live
per-subsample cost line as they run.
StabEng is the fixed pool sliced to all working rows (matched to the shipped
selector); under holdout regenerate it on the working set first, since FullEng
was fit on TrainIdx alone. Knobs (both gates): M (default 30),
SubsampleFraction (0.5), MaxRows (3000), CoreThreshold (0.8, a reporting label
only). See each function's help for the full contract, scope caveats, and error ids.
3c. Domain path — generation stability needs a Fit
Gate 1 works on any path. Gate 2 and the cross_validated read both regenerate
the pool, so they need a re-runnable fitter. A domain path captures only a frozen
Apply (fit once, replayed), which cannot regenerate the pool — so it supplies the
optional Recipe.Fit (same signature as the producer contract, validated by
buildRecipe; see domain-routing.md §4). When present the
orchestrator passes Recipe.Fit as the Producer; it is diagnostic-time only
(inference always ships the frozen Apply). Absent Fit, both regenerating reads
are N/A — Gate 1 is the honest read; don't fake the rest.
3d. The figures — plotSelectionDecision + plotSelectionStability
Only when the report is on (GenerateReport) — under a report opt-out neither
is drawn. When on: plotSelectionDecision draws the decision (consensus-score
elbow, cut at NSelect), always, independent of whether a stability gate ran.
plotSelectionStability draws the stability of that decision (same features,
same rank order; bar length = re-selection frequency), and takes an optional
GenStabSelection = GenStab to add the generation bar series; with no stability read
it draws nothing and returns []. Drawing spec is in each function's help.
if GenerateReport
plotSelectionDecision(VoteTable, NSelect, OutputDir); % elbow (always, when report on)
plotSelectionStability(VoteTable, NSelect, SelStab, OutputDir, ...
GenStabSelection = GenStab); % second series optional
endNarrate it after drawing — plain-language read of this run's numbers: the Nogueira value on its scale (≥ 0.75 stable / 0.4–0.75 moderate / < 0.4 unstable); any fragile shipped picks (shipped but rarely re-selected) named with their percentages, flagged for confirmation, never removed; and, if Gate 2 ran, the gap as the generation-variance read. This mirrors report.md ch. 7.
Handing off
The orchestrator carries Quality (always) and one performance read — Baseline
(holdout) or KFold (cross_validated) — into the plain Results struct
delivery and reporting consume. A stability read is added only when its gate was
opted into: SelStab and/or GenStab. Record Results.Strategy = Splits.Strategy.
When no gate was requested, omit both and set Results.StabilitySkipped = true so
the report says "not requested" rather than "not recorded".
MATLAB and Simulink are registered trademarks of The MathWorks, Inc. See www.mathworks.com/trademarks for a list of additional trademarks.
Copyright 2026 The MathWorks, Inc.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs an MCP server
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- MathWorks
- Publisher
- MathWorks (Official)
- Source
- matlab/matlab-agentic-toolkit · skills-catalog/ai-and-statistics/matlab-engineer-tabular-features · commit 2a7f215
- Licence
- BSD 2-Clause License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE.md)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by MathWorks.
Get it from the source
8 of this skill’s files are not mirrored here, so there is no ZIP. Get the complete skill from its source: matlab/matlab-agentic-toolkit