The Bag-of-Words Model
Imagine trying to teach a computer to tell spam from legitimate email. The words "free," "winner," and "click" appear constantly in spam. They appear rarely in a message from a colleague. You do not need to know where those words sit in a sentence to make that judgment. You just need to know that they appear, and how often. This simple observation is the seed of one of the oldest and most durable ideas in Natural Language Processing: the bag-of-words model. The bag-of-words (BoW) model represents a document as an unordered collection — a "bag" — of its words. It discards grammar, word order, and syntax entirely. Each document becomes a vector of word counts (or presence/absence flags) indexed by a fixed vocabulary. What is thrown away is enormous: no sentence structure, no negation scope, no idiom. What remains, surprisingly, is often enough to solve real problems. Spam filtering, topic classification, sentiment analysis, and information retrieval all ran for years on variants of this representation. Many still run in production today on the same basic idea. The model matters because it is the bridge from raw tokenized text to the vectors that classical machine learning algorithms understand. Before word embeddings and transformers, virtually every practical text classifier consumed bag-of-words vectors. This included Naive Bayes spam filters, SVM sentiment classifiers, and search engine relevance scorers. Even today, BoW and its weighted cousin TF-IDF remain useful. They are the fastest, most interpretable, most compute-cheap baseline anyone building a text system should try first. This article builds the bag-of-words model from the ground up. It covers the precise mathematical definition, and the algorithm that constructs it. It explains why the independence assumptions it makes are both its strength and its Achilles' heel. It also shows where the model silently fails. It closes by tracing the natural next steps. These include weighting scarce, informative words more heavily (foreshadowing TF-IDF). They also include feeding these vectors into classifiers (foreshadowing text classification).
- Bag-of-words represents a document as a vector of word counts over a fixed vocabulary, discarding word order.
- Construction requires two passes: build a vocabulary, then count occurrences of each vocabulary word per document.
- The model assumes conditional independence between word positions — the "bag" assumption — which is mathematically false but often good enough.
- Time complexity is linear in total token count; space is linear in vocabulary size times number of documents (often sparse).
- BoW is the direct ancestor of TF-IDF Weighting, which reweights raw counts by informativeness, and feeds text classifiers.
- Its blind spots — negation, word order, synonymy, out-of-vocabulary words — are the reason richer representations like embeddings were invented.
A document can be usefully summarized by which words appear and how often, ignoring the order they appear in — a drastic simplification that trades linguistic fidelity for computational simplicity and often still works.
The idea
Formally, fix a vocabulary , the set of distinct terms across a corpus after tokenization (and often after stemming or lemmatization to collapse inflectional variants like "running" and "run" into one vocabulary entry). Every document is mapped to a vector
where is the number of times word occurs in . This is the count vector (sometimes a binary presence vector with is used instead, ignoring frequency entirely). The name "bag of words" captures the key discarded information: a bag, unlike a sequence, has no order. "Dog bites man" and "man bites dog" produce identical vectors — a fact that is either irrelevant or catastrophic depending on the downstream task. The bag-of-words model rests on an explicit independence assumption, most visible when it is paired with a Naive Bayes classifier: the probability of a document given a class is modeled as a product over positions,
treating each word token as drawn independently from a class-conditional distribution over the vocabulary, with no memory of neighboring words. This is the bag-of-words assumption, a special case of the broader "naive" conditional-independence assumption in statistical NLP. It is known to be false — words are highly context-dependent — yet the resulting models are cheap to estimate, robust to overfitting given limited data, and surprisingly competitive. A second implicit assumption is exchangeability: the identity of which position a word occupies carries no information, only the multiset of words does. This means the representation is invariant under any permutation of the tokens, which is exactly why syntax, negation scope ("not good" vs "good"), and multi-word idioms are invisible to a pure BoW model unless special-cased (e.g., by including bigrams as vocabulary items, a common patch called bag-of-n-grams).
How it works
Building a bag-of-words representation for a corpus is a two-pass algorithm: first establish the vocabulary (the coordinate system for every document vector), then count.
Pass 1 — Vocabulary construction. Tokenize every document in the corpus (assumed already done via Text Preprocessing and Tokenization), optionally apply stemming/lemmatization and stopword removal, and collect the set of distinct surviving tokens. This defines , the dimensionality of every document vector, and a fixed mapping from token string to integer index (a hash map in practice).
Pass 2 — Vectorization. For each document, walk its token stream and increment the count at the vocabulary index of each token. Tokens not in the fixed vocabulary (out-of-vocabulary, or OOV, words — common at inference time on unseen documents) are either dropped or routed to a special `` bucket. Because most documents use only a small fraction of the full vocabulary, the resulting vectors are sparse — mostly zeros — and are almost always stored in sparse formats (dictionaries of `{index: count}`, or compressed sparse row matrices) rather than dense arrays, which is what makes the model practical even for vocabularies of hundreds of thousands of words. ``` function BUILD_VOCABULARY(corpus, min_freq = 1): freq = empty hashmap from token -> integer count for document in corpus: for token in TOKENIZE(document): freq[token] += 1 vocabulary = sorted list of tokens where freq[token] >= min_freq index = hashmap mapping vocabulary[i] -> i for i in 0..len(vocabulary)-1 return index // O(|V|) entries function VECTORIZE(document, index, binary = false): vector = sparse map from int -> number, default 0 for token in TOKENIZE(document): if token in index: i = index[token] if binary: vector[i] = 1 else: vector[i] = vector[i] + 1 // else: OOV token, silently dropped (or mapped to index) return vector function VECTORIZE_CORPUS(corpus, index, binary = false): matrix = empty list for document in corpus: matrix.append(VECTORIZE(document, index, binary)) return matrix // list of sparse rows ``` Two design choices matter in practice, and both are worth naming explicitly:
- Min-frequency / max-frequency pruning. Extremely rare tokens (typos, IDs) add dimensions with almost no statistical signal; extremely common tokens (stopwords) add dimensions with almost no discriminative signal. Pruning both keeps manageable and the vectors more informative — this pruning step is also the seed of the idea behind TF-IDF Weighting, which instead of dropping common words, down-weights them continuously.
- Binary vs. count vectors. Binary (presence/absence) vectors are more robust to a single word being repeated many times in one document (a phenomenon sometimes called "burstiness") and are the natural choice for the Bernoulli variant of Naive Bayes; raw counts preserve more information and are the natural choice for the Multinomial variant.
Complexity & correctness
Time complexity. Let be the total number of tokens across the corpus and the number of documents. Vocabulary construction (Pass 1) touches every token exactly once and performs a constant-time (amortized) hash-map update per token, giving time. Vectorization (Pass 2) again touches every token once, doing an amortized hash lookup and increment, giving time. Total: , i.e., linear in corpus size, independent of vocabulary size — this is the algorithmic reason BoW scales to web-scale corpora when, say, a quadratic pairwise-comparison method would not.
Space complexity. The vocabulary index stores entries. The corpus representation, if stored as dense matrix, would need space — but because each document typically uses only a small number of distinct words , the sparse representation needs only space, which is the practical reason sparse matrix formats (CSR, dict-of-dicts) are used rather than dense arrays. This sparsity is not an implementation detail — it is a direct consequence of Zipf's Law: a small number of words account for most tokens, so any single document's vocabulary footprint is a small, biased sample of .
Correctness argument. "Correctness" for a representation, rather than an algorithm with a single right answer, means: the mapping from document to vector is well-defined, deterministic, and faithfully preserves exactly the information the model claims to preserve — word identity and frequency, nothing else. Two invariants make this precise: 1. Determinism / well-definedness. Given a fixed vocabulary index and tokenizer, `VECTORIZE` is a pure function: the same document always produces the same vector, because the algorithm never depends on token order — it only accumulates counts into a map keyed by token identity. This is the formal content of "bag" — the vector is invariant under any permutation of the token sequence, since summation is commutative: for any permutation . 2. Information preservation, exactly at the level claimed. The count vector is a sufficient statistic for the multiset of tokens: from one can reconstruct exactly which words occurred and how many times, but never their order or adjacency — this is not a bug in the implementation but the definition of the representation. Any algorithm (Naive Bayes, cosine similarity, logistic regression) that consumes only is therefore mathematically guaranteed to be blind to word order, by construction, not by accident.
Honest tradeoffs. The independence assumption underlying bag-of-words is provably false as a generative model of language — real text has strong sequential dependencies (that is exactly what n-gram and neural language models exploit) — yet the estimation problem it induces (estimating from counts) is extremely well-posed: with parameters per class instead of exponentially many joint parameters, it needs far less data to estimate reliably, which is precisely the classical bias–variance tradeoff of "naive" models: higher bias (wrong independence assumption) for lower variance (few parameters, robust with small data).
Worked example
Consider a tiny two-document, two-class corpus for a coarse sentiment task, after tokenization and lowercasing (stopword removal applied for brevity):
- (positive): "great acting great story"
- (negative): "boring acting boring plot"
Step 1 — Vocabulary. Distinct tokens across the corpus, in first-seen order:
Step 2 — Count vectors.
| index | 0 (great) | 1 (acting) | 2 (story) | 3 (boring) | 4 (plot) |
|---|---|---|---|---|---|
| 2 | 1 | 1 | 0 | 0 | |
| 0 | 1 | 0 | 2 | 1 |
Step 3 — A new, unseen test document. "great acting boring story" arrives at inference time.
| index | 0 (great) | 1 (acting) | 2 (story) | 3 (boring) | 4 (plot) |
|---|---|---|---|---|---|
| 1 | 1 | 1 | 1 | 0 |
Notice immediately what has been lost: contains both "great" and "boring" with equal weight, and BoW alone cannot tell that "boring" negates the sentiment of "great" or vice versa — there is no structure connecting them. A downstream classifier trained on would essentially see as a near-equal mixture of positive and negative evidence (two positive-associated counts, two negative-associated counts), landing close to the decision boundary — which, arguably, is the right answer for a genuinely mixed review, but the model reaches it by accident of counting rather than by understanding contradiction.
Step 4 — Cosine similarity, a typical downstream use. Treating and as vectors:
and
By this pure count-based similarity, is judged closer to the positive review than to — driven entirely by "great" and "story" contributing weight, and by "acting" appearing in both. This illustrates both the power (a fast, well-defined numeric similarity with no linguistic engineering) and the fragility (the judgment ignores that "boring" is a strong standalone negative signal) of the representation — exactly the gap that motivated reweighting rare, discriminative words more heavily, which is the idea developed next under TF-IDF Weighting.
Common pitfalls & traps
| Pitfall | What goes wrong | Fix |
|---|---|---|
| Fitting vocabulary on train+test | Test-set words leak into the vocabulary, inflating benchmark performance unrealistically | Build vocabulary only from training data; treat unseen test words as OOV/`` |
| Ignoring vocabulary explosion | With no pruning, can reach millions on web-scale corpora, blowing up memory even in sparse form | Apply min/max document-frequency pruning, stemming, and stopword removal |
| Treating BoW as capturing meaning | Assuming similar count vectors imply similar meaning, ignoring negation and word order | Recognize the exchangeability assumption; consider n-gram features or move to contextual embeddings |
| Comparing raw counts across documents of different lengths | Long documents get inflated counts, biasing similarity/classification toward document length rather than content | Normalize vectors (L2 normalization) or move to frequency-based weighting like TF-IDF |
| Silent OOV dropping at inference | Words unseen during vocabulary construction vanish with no warning, sometimes deleting the most important content word in a review (e.g., a new slang term) | Log OOV rate; route OOV tokens to an explicit `` bucket rather than silent deletion |
| Assuming binary and count vectors are interchangeable | Multinomial Naive Bayes trained on counts behaves differently from Bernoulli Naive Bayes trained on presence flags; mixing conventions breaks the underlying probability model | Match vectorization mode (binary vs. count) to the model's assumed generative distribution |
Pioneers & history
The term "bag of words" and the underlying independence assumption trace back to the intersection of information retrieval and probabilistic text modeling in the mid-20th century, well before "NLP" was a common phrase. The representation grew out of two converging lines: statistical linguistics' interest in word-frequency distributions, and information retrieval's need for a fast, computable notion of document similarity.
Origins · 1 minHans Peter Luhn and how this was discovered
Luhn's work on automatic abstracting and indexing in the 1950s laid the practical groundwork for treating a document as a statistical distribution over its terms — the direct ancestor of the frequency-count vector used in bag-of-words models and, later, TF-IDF weighting.
Origins · 1 minhow this was discovered
Where this matters
Bag-of-words representations remain embedded throughout production NLP systems, often invisibly, as the fast first stage before more expensive models are invoked. Spam and abuse filters at email providers still use bag-of-words or bag-of-n-grams features feeding lightweight linear classifiers, because they run in microseconds at the scale of billions of messages per day. Search engines' classical relevance-scoring components (like the BM25 family) are direct descendants of the bag-of-words vector-space model, computing relevance from term frequencies without needing to parse syntax. Document clustering and topic modeling pipelines — including classical Latent Semantic Analysis and Latent Dirichlet Allocation — take a bag-of-words (or TF-IDF-weighted bag-of-words) matrix as their starting input, because topic assignment depends on which words co-occur across documents, not their order within a sentence. Recommendation systems that match user queries to product descriptions frequently use sparse bag-of-words vectors for a cheap, interpretable first-pass filter before a heavier neural re-ranker refines the shortlist. In education and interpretability-sensitive domains (legal discovery, medical record triage), bag-of-words remains attractive precisely because it is fully transparent: every feature is a literal word, every model weight is directly inspectable, unlike the distributed representations inside a transformer. It is also the standard baseline against which every new text representation — word embeddings, contextual embeddings, transformer encodings — is measured, because if a method cannot beat bag-of-words plus a linear classifier on a given task, it has not yet demonstrated its added value.
Ask the tutor: How does TF-IDF weighting change the bag-of-words vectors from the worked example, and would it change which test document is judged most similar?
Is bag-of-words the same thing as TF-IDF?
No. Bag-of-words is the raw representation — a vector of word counts (or presence flags) per document. TF-IDF is a weighting scheme applied on top of that same count structure: it multiplies each raw term frequency by an inverse document frequency factor that down-weights words appearing in many documents (uninformative) and up-weights words appearing in few (informative). Every TF-IDF vector still lives in the same bag-of-words coordinate system — same vocabulary, same dimensionality, same discarding of word order — it simply rescales the entries. Understanding plain bag-of-words first is what makes the motivation for TF-IDF weighting, covered separately, immediately clear.
Why does bag-of-words work at all if it ignores grammar entirely?
Many text classification tasks are, empirically, driven more by which words appear than by how they are arranged. Spam detection, topic labeling, and coarse sentiment classification correlate strongly with vocabulary choice: spam uses "free," "winner," "click now"; sports articles use "goal," "innings," "coach," largely independent of sentence structure. The bag-of-words assumption is mathematically false as a model of language generation, but for classification tasks where the decision boundary correlates with word presence/frequency rather than syntax, the approximation costs little accuracy while buying enormous simplicity and speed.
How does bag-of-words handle words it has never seen before?
It cannot represent them within the fixed vocabulary — any token absent from the vocabulary built during training is out-of-vocabulary (OOV). Standard practice either silently drops OOV tokens (losing their information entirely) or routes them to a shared `` index, which at least preserves a signal that an unknown word occurred, without knowing which one. This is a structural limitation: the vector's dimensionality is fixed at vocabulary-construction time, so no new dimension can be added at inference without retraining any downstream model that consumes the vector.
What is the difference between binary bag-of-words and count-based bag-of-words?
Binary bag-of-words records only presence or absence of each vocabulary word (0 or 1), while count-based bag-of-words records the actual frequency. The choice matters for the generative model implied downstream: Bernoulli Naive Bayes assumes each word is independently present or absent, matching binary vectors, while Multinomial Naive Bayes assumes word counts are drawn from a multinomial distribution, matching count vectors. Empirically, binary vectors are more robust when a single repeated word could otherwise dominate a document's vector (a phenomenon called burstiness), while counts preserve more nuanced frequency signal for longer documents.
Can bag-of-words capture any word order information at all?
Plain unigram bag-of-words captures none. A common practical extension is bag-of-n-grams, where the vocabulary consists of contiguous word sequences (bigrams, trigrams) rather than single words — "not good" becomes its own vocabulary item, distinct from "not" and "good" separately, partially recovering local order and negation scope. This comes at a steep cost: the vocabulary size grows combinatorially with n, sparsity worsens sharply, and most n-grams still occur only once or twice in any realistic corpus, limiting how much order information can practically be recovered this way before moving to sequence models.
How does bag-of-words relate to the vector space model used in information retrieval?
The vector space model in information retrieval represents both documents and queries as vectors in the same space defined by a shared vocabulary — precisely a bag-of-words space, usually with TF-IDF weighting applied. Relevance ranking is then computed via cosine similarity or dot products between the query vector and each document vector. This means the vector space model is not a separate idea from bag-of-words; it is bag-of-words (typically weighted) put to a specific use — ranking documents by similarity to a query — rather than classification.
Why is bag-of-words still taught and used given the existence of transformer-based embeddings?
Bag-of-words remains the fastest, cheapest, most interpretable baseline for text problems, and interpretability plus speed still matter enormously in production: a bag-of-words linear classifier can score millions of documents per second on commodity hardware with fully inspectable weights, while a transformer requires GPU inference and produces representations that are far harder to audit. It also remains the standard sanity-check baseline — any new representation method should demonstrably beat bag-of-words plus a linear model before being trusted, which keeps the model relevant as a benchmark even where it is not the final production choice.