User Guide¶
Row2Vec turns rows into vectors. You give it a DataFrame; it gives you back
a numeric matrix with one row per input row, having dealt with the mixed dtypes,
the missing values, and the scaling on the way.
Almost everything you want to do with a table downstream — cluster it, find the nearest neighbours of a record, plot it, feed it to a model that only speaks floats — assumes the rows are already points in a metric space. This guide covers the concepts, helps you pick a method, and works through the common tasks. Every code block on this page is executed by the test suite, so the examples stay correct against the installed version.
New here? Read Core concepts and Choosing a method, then jump to the section closest to your problem.
Core concepts¶
One call for every method¶
Every embedding is produced by learn_embedding. You choose the method with
mode; everything else about the call stays the same.
import row2vec
df = row2vec.generate_synthetic_data(120)
embeddings = row2vec.learn_embedding(df, mode="pca", embedding_dim=3)
assert embeddings.shape == (120, 3)
Swapping methods means swapping one argument — the call site never changes.
The output is a DataFrame aligned with your input¶
The result has the same length and index as the input, so you can put it straight back next to the original columns.
import pandas as pd
import row2vec
df = row2vec.generate_synthetic_data(50)
embeddings = row2vec.learn_embedding(df, mode="pca", embedding_dim=2)
assert list(embeddings.index) == list(df.index)
combined = pd.concat([df, embeddings], axis=1)
assert len(combined.columns) == len(df.columns) + 2
Mixed dtypes are handled for you¶
The input can hold numbers, categories, and gaps at once. Numeric columns are scaled, categorical columns are encoded, and missing values are imputed — you do not have to prepare any of it.
import numpy as np
import pandas as pd
import row2vec
df = pd.DataFrame(
{
"amount": [10.0, 240.0, 35.5, np.nan, 88.0, 12.0, 300.0, 45.0],
"region": ["north", "south", "north", "east", "south", "east", "north", "south"],
"tier": ["a", "b", "a", "b", "a", "b", "a", "b"],
}
)
embeddings = row2vec.learn_embedding(df, mode="pca", embedding_dim=2)
assert embeddings.shape == (8, 2)
assert not embeddings.isna().to_numpy().any() # no NaN survives into the output
embedding_dim is the width of the output¶
It is the number of columns you get back — how much room the method has to describe a row. Two or three for plotting; five to fifty as features for another model.
import row2vec
df = row2vec.generate_synthetic_data(60)
for dim in (2, 5):
assert row2vec.learn_embedding(df, mode="pca", embedding_dim=dim).shape[1] == dim
If you would rather not pick, see Choosing the dimension automatically.
Choosing a method¶
Start from what you want the vectors for:
| Your situation | Mode | Key parameter |
|---|---|---|
| A fast, interpretable baseline | pca |
embedding_dim |
| The structure is non-linear | unsupervised |
hidden_units, max_epochs |
| A 2-D picture showing clusters | tsne |
perplexity |
| A 2-D picture that also keeps global layout | umap |
n_neighbors, min_dist |
| One vector per category, not per row | target |
reference_column |
| You know which rows are alike | contrastive |
auto_pairs, margin |
By data characteristics:
| Rows | Structure | Good default |
|---|---|---|
| Any | Unknown — you are exploring | pca first, then unsupervised |
| Thousands+ | Non-linear, plenty of data to fit | unsupervised |
| Up to a few thousand | You want to see it | umap (or tsne) |
| Any | You have labelled pairs or a grouping column | contrastive |
When-to-use, in one line each:
pca— linear, deterministic, and instant. Always worth running first: if a few components already separate what you care about, stop here.unsupervised— an autoencoder. Captures interactions PCA cannot, at the cost of training time and hyperparameters. Wants a few thousand rows.tsne— for visualisation only. Excellent at revealing clusters, but distances between clusters are not meaningful, and it cannot embed new rows.umap— usually the better plot: faster than t-SNE and keeps more of the global arrangement.target— flips the question around: instead of embedding rows, embed the values of one column by the rows they occur in.contrastive— supervised by pairs. Use it when you know that certain rows should be close (same customer, same cluster, same label).
The methods¶
PCA¶
The linear baseline. Fast, deterministic, and its components come out ordered by how much variance they account for.
import row2vec
df = row2vec.generate_synthetic_data(200)
embeddings = row2vec.learn_embedding(df, mode="pca", embedding_dim=3)
assert embeddings.shape == (200, 3)
# Ordered by explained variance, so the first component is the widest.
assert embeddings.iloc[:, 0].var() >= embeddings.iloc[:, 2].var()
Autoencoder (unsupervised)¶
A neural network trained to reconstruct each row through a narrow bottleneck; the
bottleneck is the embedding. hidden_units sets the layers before it — a single
integer for one layer, a list for several.
import row2vec
df = row2vec.generate_synthetic_data(200)
embeddings = row2vec.learn_embedding(
df,
mode="unsupervised",
embedding_dim=4,
hidden_units=[32, 16], # two hidden layers
max_epochs=3, # kept small for this example; use far more in practice
verbose=False,
)
assert embeddings.shape == (200, 4)
max_epochs bounds training; with early_stopping=True (the default) it stops
sooner once the reconstruction loss plateaus.
t-SNE and UMAP¶
Both exist to be looked at. perplexity (t-SNE) and n_neighbors (UMAP) control
how large a neighbourhood each point is fitted against — smaller values favour
tight local structure, larger ones a smoother global picture.
import row2vec
df = row2vec.generate_synthetic_data(150)
tsne = row2vec.learn_embedding(df, mode="tsne", embedding_dim=2, perplexity=10)
umap = row2vec.learn_embedding(df, mode="umap", embedding_dim=2, n_neighbors=10, min_dist=0.1)
assert tsne.shape == umap.shape == (150, 2)
perplexity must be small relative to the number of rows; Row2Vec says so
directly rather than letting scikit-learn fail obscurely.
import pytest
import row2vec
df = row2vec.generate_synthetic_data(30)
with pytest.raises(ValueError, match="should be less than"):
row2vec.learn_embedding(df, mode="tsne", embedding_dim=2, perplexity=100)
Target-based embeddings¶
Instead of one vector per row, get one vector per distinct value of a column — learned from the rows in which that value appears. Useful for turning a high-cardinality categorical into a small dense feature.
import row2vec
df = row2vec.generate_synthetic_data(200)
country_vectors = row2vec.learn_embedding(
df,
mode="target",
reference_column="Country",
embedding_dim=2,
max_epochs=3,
verbose=False,
)
# One row out per distinct country in, not one per input row.
assert len(country_vectors) == df["Country"].nunique()
Contrastive embeddings¶
Supervision by example: tell the model which rows should end up close together and which should not.
import row2vec
df = row2vec.generate_synthetic_data(120)
embeddings = row2vec.learn_embedding(
df,
mode="contrastive",
embedding_dim=3,
similar_pairs=[(0, 1), (2, 3)],
dissimilar_pairs=[(0, 50), (1, 60)],
contrastive_loss="contrastive",
max_epochs=3,
verbose=False,
)
assert embeddings.shape == (120, 3)
If you don't have pairs to hand, auto_pairs derives them: "categorical" (rows
sharing a category value are alike), "cluster", "neighbors", or "random".
import row2vec
df = row2vec.generate_synthetic_data(120)
embeddings = row2vec.learn_embedding(
df,
mode="contrastive",
embedding_dim=2,
auto_pairs="categorical",
reference_column="Country",
max_epochs=3,
verbose=False,
)
assert embeddings.shape == (120, 2)
Preparing data¶
Missing values¶
AdaptiveImputer analyses the missingness of each column and picks a strategy to
match. learn_embedding runs it for you, but you can use it on its own.
import numpy as np
import pandas as pd
from row2vec import AdaptiveImputer, ImputationConfig, MissingPatternAnalyzer
df = pd.DataFrame(
{
"age": [25.0, np.nan, 41.0, 33.0, np.nan, 29.0],
"city": ["rome", "oslo", None, "rome", "oslo", "rome"],
}
)
analysis = MissingPatternAnalyzer(ImputationConfig()).analyze(df)
assert analysis["total_missing"] == 3
imputed = AdaptiveImputer(ImputationConfig()).fit_transform(df)
assert imputed.isna().sum().sum() == 0
assert len(imputed) == len(df)
Choose the trade-off explicitly when you care: numeric_strategy="mean" with
prefer_speed=True for quick iteration, numeric_strategy="knn" when accuracy
matters more than time.
import numpy as np
import pandas as pd
from row2vec import AdaptiveImputer, ImputationConfig
df = pd.DataFrame({"x": [1.0, 2.0, np.nan, 4.0, 5.0, np.nan, 7.0, 8.0]})
fast = AdaptiveImputer(ImputationConfig(numeric_strategy="mean", prefer_speed=True)).fit_transform(
df
)
accurate = AdaptiveImputer(ImputationConfig(numeric_strategy="knn", knn_neighbors=3)).fit_transform(
df
)
assert fast.isna().sum().sum() == 0
assert accurate.isna().sum().sum() == 0
Set preserve_missing_patterns=True to keep the fact that a value was missing
as its own feature — often predictive in itself.
Categorical columns¶
The encoder picks a strategy from the column's cardinality: one-hot for a handful of values, ordinal or target encoding as the count grows, learned entity embeddings for the largest. You can inspect that decision:
import pandas as pd
from row2vec import CategoricalAnalyzer, CategoricalEncodingConfig
df = pd.DataFrame({"colour": ["red", "green", "blue", "red", "green", "blue"]})
analysis = CategoricalAnalyzer(CategoricalEncodingConfig()).analyze_column(df["colour"])
assert analysis["cardinality"] == 3
assert analysis["recommended_strategy"] # a strategy name, e.g. "onehot"
Scaling the output¶
scale_method rescales the embedding after it is computed — useful when a
downstream model expects a bounded range.
import row2vec
df = row2vec.generate_synthetic_data(100)
scaled = row2vec.learn_embedding(
df,
mode="pca",
embedding_dim=2,
scale_method="minmax",
scale_range=(0.0, 1.0),
)
values = scaled.to_numpy()
assert values.min() >= -1e-6
assert values.max() <= 1.0 + 1e-6
The options are "minmax", "standard", "l2", "tanh", and "none".
Tuning¶
Choosing the dimension automatically¶
auto_select_dimension evaluates candidate widths and recommends one, so you do
not have to guess.
import row2vec
df = row2vec.generate_synthetic_data(150)
recommended_dim, details = row2vec.auto_select_dimension(
df, methods=["pca_variance"], max_dimension=5
)
assert 1 <= recommended_dim <= 5
assert "method_results" in details
Searching the architecture¶
For the neural modes, search_architecture explores layer counts, widths,
dropout rates, and activations, and returns the best configuration it found.
import row2vec
from row2vec import ArchitectureSearchConfig, EmbeddingConfig, NeuralConfig
df = row2vec.generate_synthetic_data(150)
base_config = EmbeddingConfig(
mode="unsupervised", embedding_dim=3, neural=NeuralConfig(max_epochs=2)
)
search_config = ArchitectureSearchConfig(
method="random", max_trials=2, verbose=False, layer_range=(1, 2)
)
best_architecture, _result = row2vec.search_architecture(
df=df, base_config=base_config, search_config=search_config
)
assert "n_layers" in best_architecture
assert "hidden_units" in best_architecture
Searching costs one training run per trial — budget max_trials accordingly.
Saving and reusing a model¶
An embedding is only reproducible if the preprocessing is reproducible too. A saved Row2Vec model carries its encoders, imputers, and scalers with it, so embedding new rows later stays consistent with training.
import tempfile
from pathlib import Path
import row2vec
df = row2vec.generate_synthetic_data(100)
base = Path(tempfile.mkdtemp()) / "model"
embeddings, script_path, _binary_path = row2vec.train_and_save_model(
df, base_path=str(base), mode="pca", embedding_dim=2
)
model = row2vec.load_model(script_path)
new_rows = row2vec.generate_synthetic_data(20, seed=99)
assert embeddings.shape == (100, 2)
assert model.predict(new_rows).shape == (20, 2)
Saving produces two files: a readable Python loader script and a binary blob holding the fitted objects. Loading executes that script, so only load models you trust — see SECURITY.md.
Integrations¶
scikit-learn pipelines¶
Row2VecTransformer is a standard transformer: put it in a Pipeline and it
behaves like any other step.
from sklearn.pipeline import Pipeline
import row2vec
from row2vec import EmbeddingConfig, Row2VecTransformer
df = row2vec.generate_synthetic_data(100)
pipeline = Pipeline(
[("embed", Row2VecTransformer(config=EmbeddingConfig(mode="pca", embedding_dim=2)))]
)
transformed = pipeline.fit_transform(df)
assert transformed.shape == (100, 2)
The pandas accessor¶
Importing row2vec registers a .row2vec accessor on DataFrame, which is
convenient in a notebook.
import row2vec # importing registers the accessor
df = row2vec.generate_synthetic_data(80)
embeddings = df.row2vec.pca(dim=2)
assert embeddings.shape == (80, 2)
The command line¶
For batch work there is no need to write Python at all:
# Embed a file in one step
row2vec annotate --input data.csv --output embeddings.csv --mode pca --dim 5
# Train a reusable model, then apply it to new data
row2vec train --input data.csv --output model.py --mode unsupervised --dim 10
row2vec predict --input new.csv --model model.py --output predictions.csv
Practical notes¶
Reproducibility¶
Every mode takes a seed. The same seed and the same input give the same
embedding.
import row2vec
df = row2vec.generate_synthetic_data(80)
first = row2vec.learn_embedding(df, mode="pca", embedding_dim=2, seed=1305)
second = row2vec.learn_embedding(df, mode="pca", embedding_dim=2, seed=1305)
assert first.equals(second)
Neural modes are seeded the same way, though exact floating-point results can still differ across platforms and library versions.
Configuration objects¶
For anything beyond a few arguments, EmbeddingConfig groups the settings so
they can be reused, serialised, and version-controlled.
import row2vec
from row2vec import EmbeddingConfig, NeuralConfig
config = EmbeddingConfig(
mode="unsupervised",
embedding_dim=4,
neural=NeuralConfig(hidden_units=[32, 16], max_epochs=2, dropout_rate=0.1),
)
df = row2vec.generate_synthetic_data(100)
embeddings = row2vec.learn_embedding_v2(df, config)
assert embeddings.shape == (100, 4)
Logging¶
Training emits structured logs. Turn them off for quiet runs, or point them at a
file with log_file=.
import row2vec
df = row2vec.generate_synthetic_data(50)
embeddings = row2vec.learn_embedding(df, mode="pca", embedding_dim=2, enable_logging=False)
assert embeddings.shape == (50, 2)
What is not supported¶
Free text and datetime columns are not embedded directly. Preprocess them yourself — sentence embeddings for text, explicit features (month, weekday, elapsed days) for timestamps — and pass the result as ordinary columns.
Next steps¶
- API Reference — every public class and function, generated from the source.
- Home — the one-minute overview and the method table.