event2vec

Event2Vec

Vectors for event histories. A customer, an account or a machine becomes the sum of its events, and every score you build on top splits exactly into the events behind it.

pip install event2vector

Open source, MIT · scikit-learn API · trains on a laptop CPU · version 0.6.0

Figure 1Churn prediction with per-purchase attribution
–
Loading the model…
Churn model on Event2Vec features, with per-purchase attribution. Real customers of a UK online shop (Online Retail II, UCI). Event2Vec learned a 32-number vector for each of 3,114 products from the order in which 5,121 customers bought them, without labels, in 38 seconds on a laptop CPU. A customer's vector is the sum of their purchases. The bars show the model's score for the 90 days after 1 September 2011, split exactly into recency, frequency, spend, tenure and each product they bought; red pushes toward leaving, blue toward staying. The map places every product by its learned vector; the timeline shows one cell per product per order. These are real examples, including ones the model gets wrong.

Churn prediction

Every feature set below goes into the same logistic regression and is scored with five-fold cross-validation on who stopped buying in the next 90 days (57% of customers did).

FeaturescolumnsAUC
Which products they bought (tf-idf bag)3,1140.627
Event2Vec vector of the same purchases320.683
Recency, frequency, spend, tenure (RFM)40.787
RFM + Event2Vec360.785

As a description of what someone bought, Event2Vec's 32 numbers beat the full 3,114-column product list, and the gap is largest for customers with short histories (AUC 0.59 vs 0.54 below 20 purchases). Rarely bought products borrow strength from the products they behave like. Recency and frequency still carry most of the churn signal: adding Event2Vec to RFM does not raise the AUC. What it adds is the right-hand side of Figure 1, a reason for every score, product by product.

Method

Each kind of event gets a vector. A history is the running sum of its events' vectors, kept bounded by a norm clip so very long histories don't blow up. The model learns the vectors by predicting the next event from that sum, so it needs no labels, only events in the order they happened.

h = e1 + e2 + … + en   →   p(next event) = softmax(W h + b)

Because a history is a sum, any linear model on top of it is a sum as well. A churn, fraud or risk score splits into one term per event, the terms add up to the score exactly, and removing an event removes exactly its term. There is no sampling and no surrogate model in that explanation.

score = Σi ei·w + b

The same split applies to the model's own next-event prediction. attribute returns each earlier event's share of a predicted event's logit, rank_causes orders a history's events by that share, and counterfactual re-encodes the history with events removed, replaced or inserted. These explain what the model learned from the data; they are associations, not causal effects.

Events that lead to similar futures end up with similar vectors. That is what lets 32 numbers describe a customer better than thousands of product columns, and it is why the product map in Figure 1 has structure at all.

Figure 2Composition and decomposition (schematic)
history signed up ordered ordered again returned an item compose: add the vectors h decompose: score = Σ e·w + b signed up ordered ordered again returned baseline 0 toward leaving → ← staying
Composition and exact decomposition. Events are added as vectors into one history vector h (left). A linear score on h is a sum of one term per event (right), so the explanation is exact. The values here are illustrative; Figure 1 shows the real thing.

Structure in unlabelled sequences

The same estimator, pointed at sequences of letters, sounds and words, separates categories it was never told about, and does so much more cleanly than Word2Vec trained on the same sequences.

Figure 3Letters by vowel and consonant
Event2Vecsilhouette
Word2Vecsilhouette

Geometries

The sum in Figure 2 is the Euclidean case. Event2Vec ships seven more update rules, each the natural home of a structure plain addition can't carry: hierarchies, cycles, histories where the order of events changes the outcome, and events that act on the history instead of only adding to it. They share one estimator and one argument.

Figure 4The same three events, composed by each geometry
"euclidean"

vector addition

Bags of events and exact explanations. The default, and the fastest.

"hyperbolic"

Möbius addition, curvature c > 0

Hierarchies: product categories, diagnosis codes, org charts.

"stereographic"

κ-addition, signed κ

One dial: κ < 0 hyperbolic, 0 flat, κ > 0 spherical.

"spherical"

κ-addition, κ > 0

Cycles: weekdays, seasons, shift patterns.

"rotation"

product of SO(n) rotations

Histories where the same steps in another order end somewhere else.

"heisenberg"

Heisenberg group product

Same endpoint, opposite signed area: order shows up as a separate number.

"product"

one update per factor

Two structures at once, such as a hierarchy × a cycle.

"affine"

x ↦ exp(Me) x + ve

Events that shrink, stretch or turn the history, not only add to it.

# one curvature dial
Event2Vec(V,
  geometry="stereographic",
  kappa=-1.0)
# order matters: SO(3)
Event2Vec(V,
  geometry="rotation",
  rotation_n=3)
# hierarchy × cycle
Event2Vec(V,
  geometry="product",
  product_kappas=(-1, 1))
# events act on the history
Event2Vec(V,
  geometry="affine")
Order sensitivity by geometry. In the flat Euclidean panel both orders reach the same point, which is what makes its explanations exact. The curved and non-commutative geometries trade some of that exactness for memory of order and room for hierarchies. The animations run the package's own update formulas on three example events.

Cuisine attribution in recipes

The same exact split works for any event that follows a history. We gave Event2Vec 9,946 recipes from five cuisines, each recipe as its ingredients followed by a cuisine token, so naming the cuisine is just predicting the next event. Every cuisine score then splits into one term per ingredient, which shows what each cuisine is recognised by and which ingredients two cuisines share.

A second model reads the cuisine first and the ingredients after it. Given the same three ingredients, it shows what each cuisine would add next.

Figure 5Per-ingredient cuisine attribution and cuisine-conditioned completion
shared: raises two cuisinesterm per cuisine
Per-ingredient attribution of cuisine predictions. Recipes from Epicurious, Allrecipes and Menupan (Ahn et al., 2011, CC BY 4.0), grouped into East Asian, French, Indian, Italian and Mexican. Left: each ingredient's term in a cuisine's score, minus its average term over the five cuisines, averaged over held-out recipes and three training runs (± spread between runs). Shared ingredients raise two cuisines in every run. Right: the next ingredient each cuisine predicts after the same base. Event2Vec names the cuisine of 84% of held-out recipes; a logistic regression on the ingredient list, trained only for this, reaches 87%, but has no model of what comes next.

Quickstart

Give it one list of event IDs per customer, account or machine, in time order. It behaves like any scikit-learn transformer.

from event2vector import Event2Vec

# one list of event IDs per customer, in time order
histories = [[0, 3, 3, 7], [1, 4, 2], ...]

model = Event2Vec(num_event_types=V, geometry="euclidean",
                  embedding_dim=32, pad_sequences=True)
model.fit(histories)                    # no labels needed

X = model.transform(histories)          # one 32-number vector per customer
model.most_similar(3, topn=5)            # events that behave like event 3

Choosing a geometry: see Figure 4. Start with "euclidean"; switch when your events form a hierarchy or when order should matter.

Practical notes

License
MIT: the library can be used, modified and shipped in closed-source software, keeping the copyright notice.
Input format
One list of integer event IDs per entity, sorted by time. Map event names to IDs with a dictionary. Timestamps are only used for sorting.
Scale
The churn example has 590,454 events and trains in about 40 seconds on a laptop CPU. The model is the number of event types times the vector size, plus a linear decoder; for Figure 1 that is about 200,000 numbers.
Maturity
It is a research library with a small, stable surface (fit, transform, most_similar, and since 0.6.0 attribute, rank_causes and counterfactual) and a test suite. Pin the version (event2vector==0.6.0) and validate on your own data first.
Contact
Open an issue on GitHub, or reach Antonin through sulcantonin.github.io.

Paper

Sulc, A. Event2Vec: A Geometric Approach to Learning Composable Representations of Event Sequences. Proceedings of Machine Learning Research, vol. 282, pp. 492–512, 2026. PMLR · preprint arXiv:2509.12188 · software DOI 10.5281/zenodo.23399003.

BibTeX
@InProceedings{pmlr-v282-sulc26a,
  title     = {Event2Vec: A Geometric Approach to Learning
               Composable Representations of Event Sequences},
  author    = {Sulc, Antonin},
  booktitle = {Proceedings of the 4th (2025) and 3rd (2024) NeurIPS Workshops
               on Symmetry and Geometry in Neural Representations},
  pages     = {492--512},
  year      = {2026},
  volume    = {282},
  series    = {Proceedings of Machine Learning Research},
  publisher = {PMLR},
  url       = {https://proceedings.mlr.press/v282/sulc26a.html}
}