Concepts / Bag-of-Words Representation

Bag-of-Words Representation

A bag-of-words representation describes a text as its word occurrences, treating it as a bag or set and ignoring grammar and word order.

  • Programming

A Review Cut Into a Bag

A classifier cannot read a movie review the way a person does. It learns from numbers, so a review written as a sentence has to be converted into a numeric form before any learning can happen. The bag-of-words representation is one of the simplest ways to make that conversion. It describes a document by its word occurrences: which words appear and how many times each one appears.

What do you think happens?

Two sentences use exactly the same three words: 'dog bites man' and 'man bites dog'. Before reading on, predict whether a bag-of-words representation gives them the same count vector or different count vectors.

  • The same count vector

  • Different count vectors

Reveal answer
Answer:

The same count vector

Each sentence contains dog, bites, and man exactly once. A bag keeps the words and their counts but throws away their arrangement, so the two sentences look identical to the representation even though they mean different things.

Keep that surprise in mind. The rest of this article follows the representation step by step, so you can see exactly where the order of words is lost and where the counts come from.

What Fit Learns: The Vocabulary

Building a bag-of-words representation takes two steps. First, the text is split into tokens, which are the units you choose to count, most often individual words. Second, the distinct tokens that appear across the whole collection are gathered into a vocabulary, and that vocabulary is given a fixed order. When you call fit on a CountVectorizer, both of these things happen: it tokenizes the training text and builds the vocabulary from the unique words those tokens produce.

The learned vocabulary is stored in the vocabulary_ attribute. You read it the same way you would read any attribute of an object. Its keys are words and its values are column indexes. The printed dictionary is not in the order of the sentences. It is the mapping from each word to its position, and that position is what the later counting step relies on.

beindex 0butindex 1dothindex 2foolindex 3theindex 9wiseindex 12
Which column index does each learned word correspond to? Reading the dictionary by index rather than by printed order shows the layout of the vocabulary.

In the two-sentence example the source uses, the index numbers run from 0 to 12, so the 13 vocabulary words occupy 13 positions. Read in alphabetical order, 'be' is index 0, 'but' is index 1, 'doth' is index 2, and 'wise' is index 12. The vocabulary runs from 'be' to 'wise', and the words that appear in both sentences sit at indexes 3, 9, and 12, which are 'fool', 'the', and 'wise'. Notice that the diagram above shows only the words needed to read those indexes; the vocabulary contains every distinct word.

python
Output (expected)
{'acting': 0, 'boring': 1, 'great': 2, 'movie': 3}
Notes on the code
  • fit tokenizes both documents and builds the vocabulary; it does not yet produce any counts
  • vocabulary_ maps each learned word to its column index
  • The indexes follow the sorted word order, not the order of the sentences

Building the Count Matrix With transform

To produce the bag-of-words data, call transform on the same list of documents. The result is a matrix with one row per document and one column per vocabulary word. Each cell holds a count: how often that column's word appears in that row's document. Every document ends up with a vector of the same length, because every document is measured against the same vocabulary.

Sentencegreat movie great actingTokensgreat, movie, great, actingVocabulary lookupacting, boring, great,movieCount vector1, 0, 2, 1splitmatch each tokencount per column
What steps happen to a sentence before it becomes one row of numbers? The text is split into tokens, each token is matched against the fixed vocabulary, and the matches are counted.
Worked example

Counting one document against a fixed vocabulary

The vocabulary is acting, boring, great, movie, in that fixed order. Find the count vector for the document great movie great acting.

  1. 1
    Read the vocabulary positions

    acting is position 0, boring is position 1, great is position 2, and movie is position 3.

  2. 2
    Count acting

    acting appears once in the document, so position 0 holds 1.

  3. 3
    Count boring

    boring never appears in this document, so position 1 holds 0. It is still in the vocabulary, which keeps every vector the same length.

  4. 4
    Count great

    great appears twice, so position 2 holds 2.

  5. 5
    Count movie

    movie appears once, so position 3 holds 1.

Result

The count vector is [1, 0, 2, 1]. Each number lines up with a vocabulary word in the same fixed order.

The same counting procedure can be written directly in Python. The sketch below uses plain whitespace splitting as a stand-in for tokenization, which is enough to show the mechanics. It builds a sorted vocabulary from all documents and then produces one count vector per document.

python
Output (expected)
['acting', 'boring', 'great', 'movie']
[1, 0, 2, 1]
[0, 1, 0, 1]
Notes on the code
  • doc.split() is the whitespace tokenizer used as a stand-in here
  • The vocabulary is built from every document, then fixed in sorted order
  • Each inner list has one position per vocabulary term, so both vectors have length 4

Each printed list is one document's bag-of-words vector, and the position of each number matches the position of a term in the printed vocabulary. A library tool such as CountVectorizer handles tokenization more carefully, but the representation it produces has this same shape.

Reading the Sparse Storage Format

The matrix returned by transform is stored as a SciPy sparse matrix, which keeps only the nonzero entries. This matters because most documents use only a small part of the vocabulary, so most cells in the grid are zero. Storing every zero would waste memory.

The summary that repr prints for the transform result reports three things: the shape, which is 2 rows by 13 columns; the element type, which is numpy.int64; and the storage format, which is a sparse matrix in Compressed Sparse Row form holding 16 stored elements. A full grid for these two sentences would have 26 cells, so only 16 are nonzero and the other 10 are not stored at all. In a real corpus, such as movie reviews compared against the whole English vocabulary, the share of zeros is far larger.

A sparse matrix hides its zeros, so reading an individual count from it is awkward. The easiest way to check a count is to convert the matrix to a dense NumPy array with toarray. The dense version stores every cell, zeros included, so you can read any count directly. The code below uses the same two documents as before.

python
Output (expected)
(2, 4)
[[1 0 2 1]
 [0 1 0 1]]
Notes on the code
  • fit_transform does fitting and counting in one call
  • shape gives rows then columns: one row per document, one column per vocabulary word
  • toarray turns the stored entries into a full grid with every zero visible

Tracing a Count Back to Its Cause

When a count looks wrong, the useful question is where your expectation and the output part ways. Three links connect a sentence to a cell in the matrix, and you can check them in order. First, the input text: is the sentence exactly what you think it is? Second, the column that vocabulary_ gives for the word: which index does the word actually have? Third, the value at that column in the dense row: is the number there the one you expected?

  • Common mistake

    Reading the vocabulary in the order of the sentences

    What it looks like

    Assuming the first word of the first sentence is column 0 because it appears first in the text.

    Why it fails

    vocabulary_ is a word-to-index mapping, and its indexes follow the fixed vocabulary order, not the order of appearance.

    Do this instead

    Look up the word's index in vocabulary_, then read that column.

  • Common mistake

    Reading a count from the sparse matrix directly

    What it looks like

    Printing the transform result and trying to read a particular cell from its summary.

    Why it fails

    The sparse form stores only nonzero entries, so a zero cell is not visible in the output at all.

    Do this instead

    Convert with toarray and then read the value in the correct row and column.

  • Common mistake

    Reading the wrong row

    What it looks like

    Checking the second document's count in the first row of the dense array.

    Why it fails

    Each row is one document, in the same order as the input list.

    Do this instead

    Match the document's position in the list to its row index before reading the value.

  • Common mistake

    Assuming tokenization matches what you see

    What it looks like

    Expecting a word to be counted in a place where punctuation or capitalization changes how the text is split.

    Why it fails

    The count depends on the tokens the tokenizer produces, not on the words as you read them on the page.

    Do this instead

    Check how the text is tokenized, then confirm that the token exists in vocabulary_.

Why Word Order Disappears

The name is a metaphor. Take a review, cut it into individual words, and drop every word into a bag. Once the words are in the bag, you no longer know which came first, which words were attached to which, or how the sentence was built. What you still know is which words appeared and how many times. A document is therefore represented as a bag, or set, of its word occurrences.

Where the Bag Helps Classifiers

The source names three kinds of task where bag-of-words representations are a strong starting point: spam and fraud detection, sentiment analysis, and the movie review classification used as the running example. In each case, the question is which category a document belongs to, and the words it uses are often a useful clue. A message full of promotional wording or a review full of praising words gives a classifier something to work with, even without any understanding of sentence structure.

The same ideas extend to the terms the representation is built from. Tokenization decides what counts as a unit to count, and n-grams are groups of consecutive tokens. Choosing these units is part of the representation work, not a separate afterthought. Because the vocabulary is readable, you can also examine the tokens and features a model depends on. The source encourages this kind of introspection for text applications, both supervised and unsupervised, rather than treating the model as a black box.

Practice Reading a Bag

EASY

Two documents are: 'spam offer spam' and 'meeting notes'. Build the sorted vocabulary from both documents, then write the count vector for each document in that vocabulary order. Finally, state the index that vocabulary_ would give to the word offer.

Hints
  • Gather every distinct word from both documents first, then sort them alphabetically.
  • The vocabulary has four distinct words, so each count vector has four positions.
  • A word that does not appear in a document still gets a 0 in that document's vector.

Key Takeaways

  • CountVectorizer tokenizes text during fit and stores the learned words and their column indexes in vocabulary_, which is a word-to-index mapping rather than an ordered list of sentences.
  • transform produces one row per document and one column per vocabulary word, where each cell holds that word's count in that document, and the matrix is stored sparsely with only nonzero entries kept.
  • Use toarray to read every count directly, and when a count looks wrong, check the input text, the word's column in vocabulary_, and the value in the correct row, in that order.
  • A bag-of-words representation discards grammar and word order on purpose, so sentences with the same words in different arrangements receive identical vectors, a trade that still suits spam detection, sentiment analysis, and similar classification tasks.

Related concepts

More concepts starting with B