Skip to main content
Nori reads numeric tables, but real tables often carry free text too: a product title, a review, a job description. Point Nori at those columns and it turns them into a handful of extra numeric features the frozen model consumes like any other column. Nothing is trained: the sentence encoder and Nori both stay frozen, so the gain comes purely from making text legible at inference time.

Signal from text

Columns your numeric features can’t see, like sentiment, topic and phrasing, become predictive features.

Zero-shot

No fine-tuning, no gradients. A frozen sentence encoder plus an unsupervised SVD; Nori’s weights never move.

One estimator

It’s still a NoriRegressor. Name the text columns in the constructor and call fit / predict on your DataFrame.

scikit-learn native

Round-trips through clone / GridSearchCV / cross_val_score and pickle, just like the numeric path.
Text support needs the text extra, which pulls in sentence-transformers. It is covered by the recommended line in Installation. The import is lazy, so numeric-only use never requires it. Extraction runs on the local synthefy-nori package; the sentence-encoder weights download from Hugging Face on first use.

Quickstart

Name the text columns in the constructor and use the estimator exactly as you would for a numeric table. X is a pandas.DataFrame that holds numeric, categorical, and text columns together.
That’s the whole API surface: everything else (embedding, SVD, column encoding) happens inside fit / predict. Numeric and categorical columns you don’t list as text are handled automatically: numeric passes through, categorical is label-encoded.

How it works

The text path is a preprocessing layer in front of the ordinary numeric model. Nori itself is unchanged.
1

Build one paragraph per row

Each row’s text columns are concatenated into a single column-prefixed string, e.g. "title: Wireless Mouse. review: works great, tiny lag.".
2

Embed with a frozen encoder

A sentence-transformer (MiniLM by default) turns each paragraph into a dense vector. The encoder is frozen, with no fine-tuning.
3

Reduce with TruncatedSVD

The embeddings are reduced to svd_dim columns (default 128) with a TruncatedSVD fit on the training rows only. Unsupervised, it never sees y.
4

Append and predict

The SVD columns are appended to the numeric/categorical block, and the frozen Nori predicts on the widened table. predict re-embeds the query rows and applies the already-fit SVD, so the train/test transform is symmetric.

Does it actually help? A runnable check

examples/text_features_synthetic.py builds a small synthetic dataset where the target depends on numeric columns (x1, x2), a categorical column (brand), and the sentiment word buried in a free-text review — signal the numeric columns can’t reach. It then fits Nori twice, with and without the text column, on a held-out split.
The review carries most of the target, so holding it out caps the tabular model at R² ≈ 0.15, while embedding it recovers R² ≈ 0.99. The core of the example:
text_columns=[] is a valid fit on a DataFrame that keeps only the numeric and categorical columns (no encoder loaded), handy for an apples-to-apples baseline against text_columns=[...].

Configuration

"minilm" (384-d, fast) is a strong default and needs no extra setup. Larger encoders trade speed for representation quality:You can also pass a preloaded SentenceTransformer (reuse one across fits) or any callable texts -> (n, dim) ndarray.

Good to know

Under the hood: the numeric-only path (text_columns=None) is unchanged. High-cardinality columns are embedded rather than label-encoded, categorical codes are bounded (unseen values map to a single in-range “other” code), and the SVD is fit on train only, so a fitted text model pickles and clones like any sklearn estimator.

Next steps

Explainability

See which features drove a prediction, text columns included.

Embeddings

Target-aware row vectors for probing, clustering, and search.