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 — sentiment, topic, 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 optional text extra (pip install "synthefy-nori[text]"), which pulls in sentence-transformers. It’s imported lazily, 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.

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 — 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.

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.

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.
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.