Compare how NLP algorithms reduce words to their base forms using heuristic vs dictionary approaches.
Input Text
Tip: Use words like "running", "better", "feet", or "is" to see the most dramatic differences.
Transformation Matrix
0 Words
Original
Stemmed
Lemma
Key Logic
Stemming (Porte's)
A crude heuristic process that chops off the ends of words. It often results in non-dictionary roots (e.g., "studies" → "studi").
Lemmatization
A sophisticated process using vocabulary and morphological analysis. It always returns a valid dictionary word (e.g., "are" → "be").
Differences
Rule-Based
Context-Aware
No Change
Overview
The Goal: Text Normalization
In Natural Language Handling (NLP), we often need to treat different forms of a word as the same. For case, "run", "running", and "ran" all refer to the same basic concept. The process of reducing these changes down to a common base form is called text normalization. Stemming and lemmatization are two popular methods for reaching this.
Stemming strips suffixes with handwritten rules. It is fast, needs no dictionary, and often gives non-words: "studies" turns "studi", "better" stays "better". The Porter stemmer is the most widely used. And its output is meant for indexing and grouping, not for display.
Lemmatization uses a dictionary and part-of-speech data to find the actual root word: "studies" turns "study", "better" turns "good", "mice" turns "mouse". It is slower and needs a POS tagger to work correctly. "saw" lemmatises to "see" as a verb and "saw" as a noun. But its output is always a real word.
The choice depends on the downstream task and the model. For bag-of-words models, TF-IDF pipelines and search indexes. Where vocabulary reduction is the goal, stemming is often enough. The non-word output does not matter because no human reads it. For tasks where the output must be readable. Text generation, summarisation, chatbot responses. Lemmatization is the right choice. For transformer-based models, neither is needed: the subword tokeniser handles morphological change internally, and applying stemming or lemmatization before the tokeniser removes data the model was trained to use.
Compare how NLP algorithms reduce words to their base forms using heuristic vs dictionary approaches.
Stemming vs. Lemmatization: A Practical Comparison
Explore two key NLP techniques for text normalization and understand when to use each one.
The Goal: Text Normalization
In Natural Language Handling (NLP), we often need to treat different forms of a word as the same. For case, "run", "running", and "ran" all refer to the same basic concept. The process of reducing these changes down to a common base form is called text normalization. Stemming and lemmatization are two popular methods for reaching this.
Stemming strips suffixes with handwritten rules. It is fast, needs no dictionary, and often gives non-words: "studies" turns "studi", "better" stays "better". The Porter stemmer is the most widely used. And its output is meant for indexing and grouping, not for display.
Lemmatization uses a dictionary and part-of-speech data to find the actual root word: "studies" turns "study", "better" turns "good", "mice" turns "mouse". It is slower and needs a POS tagger to work correctly. "saw" lemmatises to "see" as a verb and "saw" as a noun. But its output is always a real word.
The choice depends on the downstream task and the model. For bag-of-words models, TF-IDF pipelines and search indexes. Where vocabulary reduction is the goal, stemming is often enough. The non-word output does not matter because no human reads it. For tasks where the output must be readable. Text generation, summarisation, chatbot responses. Lemmatization is the right choice. For transformer-based models, neither is needed: the subword tokeniser handles morphological change internally, and applying stemming or lemmatization before the tokeniser removes data the model was trained to use.
Stemming: The Fast and Crude Approach
Stemming is a process that reduces words to their "stem" or root form by chopping off prefixes and suffixes. It uses a set of simple, rule-based heuristics and does not care if the coming stem is a real dictionary word.
Speed: Very fast, as it doesn't need to look up words in a dictionary.
Result: Often gives non-words. For case, "studies" might turn "studi".
Use Case: Ideal for uses where speed is critical and perfect precision isn't needed, such as search engine indexing or large-scale text review.
Lemmatization: The Smart and Exact Approach
Lemmatization is a more complex process that aims to find the true root form of a word, known as the lemma. It uses a dictionary and morphological review to think of the context of the word and its part of speech.
Method: Dictionary-based, thinks of the word's meaning and part of speech.
Speed: Slower than stemming. This is because it involves dictionary lookups.
Result: Always returns a valid dictionary word. For case, "better" is correctly found as having the lemma "good", and "are" turns "be".
Use Case: Best for uses needing high precision, such as chatbots, question-answering systems, and machine translation. Where grasp the correct meaning of words is key.
Two ways to collapse word forms
"Run", "runs", "running" and "ran" are four strings and one concept. To a bag-of-words model they are four unrelated features, each with a quarter of the evidence. Reducing them to a common form pools that evidence.
There are two methods, and they differ in how much they know.
Stemming chops suffixes according to rules. Fast, crude, and it gives strings that are not always words.
Lemmatisation looks the word up, using its part of speech, and returns the real dictionary form.
Word
Porter stemmer
Lemmatiser
running
run
run
runs
run
run
ran
ran
run
studies
studi
study
better
better
good
was
wa
be
caring
care
care
Two rows show the gap clearly. "Ran" and "better" need knowledge, not rules — a stemmer has no way to know that "ran" relates to "run" or that "better" is a form of "good". And "studi" and "wa" show what rule-based chopping gives. Fragments that work as index keys and read as errors.
When each is right
Stemming is right when the output is a lookup key nobody reads. Search indexing is the canonical case: if "studi" is the key for both "study" and "studies", a query for either matches. And the user never sees the stem.
Lemmatisation is right when the output is shown to someone, or when correctness matters. Topic modelling where you display the top terms, linguistic review, a thing with an audit trail.
That last line matters: without the part of speech, a lemmatiser assumes noun and gets adjectives and verbs wrong. lemmatize("running") returns "running" unless you tell it the word is a verb. In practice, run a POS tagger first — spaCy does this by itself.
Why neither belongs in a transformer pipeline
Both methods exist to solve a problem transformers do not have.
A subword tokeniser now splits "running" into "run" + "ning", so the shared stem is shown and the suffix is available as separate data. The model sees both the root and the inflection, and it can use the inflection. Tense, number and aspect all carry meaning.
Stemming before a transformer so does two harmful things. It destroys grammatical data the model was trained to use. And it creates strings ("studi", "wa") that are not in the vocabulary and tokenise into odd fragments.
The rule is simple. Stem or lemmatise for classical bag-of-words models. Never for transformers.
Chopping suffixes against looking words up
A stemmer applies rules and can give nonsense. A lemmatiser consults a dictionary and needs to know the part of speech. Both are implemented here on the same words, including the cases where each one fails.
The next block goes on with the left demonstrations.
example_02.pyNumPy
import re
def crude_stem(w):
# a simplified Porter-style stemmer: strip the commonest suffixes
for suf, repl in (("sses", "ss"), ("ies", "i"), ("ational", "ate"),
("tional", "tion"), ("ization", "ize"), ("iveness", "ive"),
("fulness", "ful"), ("ousness", "ous"), ("ing", ""),
("edly", ""), ("ed", ""), ("ly", ""), ("es", ""),
("s", "")):
if w.endswith(suf) and len(w) - len(suf) >= 3:
return w[:-len(suf)] + repl
return w
print("READ THE STEM COLUMN. several of those are not words:")
for w in ("studies", "universities", "flies"):
print(" %-14s -> %-10s <- not a word, and that is fine"
% (w, crude_stem(w)))
print(" a stemmer's output is not meant to be readable. it is meant to be")
print(" CONSISTENT -- if two words share a stem they get grouped, and that")
print(" is all a search index needs.")
print()
Output
The next block goes on with the left demonstrations.
example_03.pyNumPy
import re
def crude_stem(w):
# a simplified Porter-style stemmer: strip the commonest suffixes
for suf, repl in (("sses", "ss"), ("ies", "i"), ("ational", "ate"),
("tional", "tion"), ("ization", "ize"), ("iveness", "ive"),
("fulness", "ful"), ("ousness", "ous"), ("ing", ""),
("edly", ""), ("ed", ""), ("ly", ""), ("es", ""),
("s", "")):
if w.endswith(suf) and len(w) - len(suf) >= 3:
return w[:-len(suf)] + repl
return w
print("WHERE STEMMING GOES WRONG. two failure modes with names:")
print()
print(" OVER-STEMMING -- unrelated words collapsing to one stem. rather")
print(" than assert an example, let the stemmer find its own. stem a word")
print(" list and report every group that ends up sharing a stem:")
wordlist = ("universe university universal organ organic organize organization "
"relate relative relativity news new arm arms army general generous "
"operate operates operating operated police policy sing sings "
"singing communism communist community").split()
groups = {}
for w in wordlist:
groups.setdefault(crude_stem(w), []).append(w)
collisions = {k: v for k, v in groups.items() if len(v) > 1}
for stem_, members in sorted(collisions.items()):
print(" %-12s <- %s" % (stem_, ", ".join(members)))
if not collisions:
print(" (none in this list -- this stemmer is too simple to collide)")
print()
print(" look at each group and decide whether it SHOULD be one group.")
Output
The next block goes on with the left demonstrations.
example_04.pyNumPy
import re
def crude_stem(w):
# a simplified Porter-style stemmer: strip the commonest suffixes
for suf, repl in (("sses", "ss"), ("ies", "i"), ("ational", "ate"),
("tional", "tion"), ("ization", "ize"), ("iveness", "ive"),
("fulness", "ful"), ("ousness", "ous"), ("ing", ""),
("edly", ""), ("ed", ""), ("ly", ""), ("es", ""),
("s", "")):
if w.endswith(suf) and len(w) - len(suf) >= 3:
return w[:-len(suf)] + repl
return w
print(" any group mixing genuinely different meanings is over-stemming,")
print(" and a real Porter stemmer produces well-known ones: 'universe'")
print(" and 'university' both stem to 'univers', 'organization' and")
print(" 'organ' both to 'organ'.")
print()
print("LEMMATISATION HANDLES THOSE, because it looks them up:")
for w, pos in (("ran", "v"), ("mice", "n"), ("was", "v"), ("better", "a")):
print(" %-10s (%s) -> %s" % (w, pos, lemma(w, pos)))
print()
print("but it needs the part of speech, and the same string can be two")
print("things:")
for w in ("meeting", "saw", "studies"):
forms = {p: lemma(w, p) for p in ("n", "v", "a") if (w, p) in LEMMAS}
print(" %-10s %s" % (w, " ".join("as %s -> %s" % (p, l)
for p, l in forms.items())))
print(" so a lemmatiser needs a POS tagger in front of it, which needs")
print(" the surrounding sentence, which makes it far slower than a")
print(" stemmer and dependent on a second model being right.")
print()
Output
The next block goes on with the left demonstrations.
example_05.pyNumPy
import re
def crude_stem(w):
# a simplified Porter-style stemmer: strip the commonest suffixes
for suf, repl in (("sses", "ss"), ("ies", "i"), ("ational", "ate"),
("tional", "tion"), ("ization", "ize"), ("iveness", "ive"),
("fulness", "ful"), ("ousness", "ous"), ("ing", ""),
("edly", ""), ("ed", ""), ("ly", ""), ("es", ""),
("s", "")):
if w.endswith(suf) and len(w) - len(suf) >= 3:
return w[:-len(suf)] + repl
return w
print("THE COMPARISON, plainly:")
rows = [("what it does", "strips suffixes by rule", "looks up a dictionary"),
("output", "may not be a word", "always a real word"),
("needs context", "no", "yes -- part of speech"),
("speed", "microseconds", "milliseconds"),
("irregulars", "fails", "handles"),
("language support", "one stemmer per language", "needs a lexicon")]
print("%18s %28s %28s" % ("", "stemming", "lemmatisation"))
for a, b, c in rows:
print("%18s %28s %28s" % (a, b, c))
print()
print("AND THE MODERN ANSWER: usually neither.")
print(" subword tokenisation already splits 'running' into pieces that")
print(" 'runs' and 'ran' partly share, and the model learns the")
print(" relationship from data rather than from a rule table.")
print(" a transformer given 'ran' and 'running' places them near each")
print(" other in embedding space without anyone writing a suffix rule.")
print()
print(" they are still worth knowing for: classical search indexes (where")
print(" a stemmer is what makes a query for 'running' match a document")
print(" about 'runs'), bag-of-words baselines, and any pipeline where you")
print(" need the vocabulary to be small and the behaviour explainable.")
Output
Things to try
Use the interactive tool above to see the gap firsthand:
Analyze the Default Text: Click the "Normalize Text" button with the default sentence. Look at the results in the "Transformation Matrix".
Notice how for "foxes", both stemming and lemmatization give "fox".
For "jumping", stemming gives "jump" and lemmatization also gives "jump". Often, they agree.
But for "studying", stemming gives "studi" (not a real word). While lemmatization correctly returns "study".
Test Irregular Verbs: Clear the text and type "He went to the best restaurants. They were good." Click "Normalize".
Lemmatization shines: "went" turns "go", "best" turns "good", and "were" turns "be". It understands the underlying lemma.
The "Caring" vs. "Cars" Test: Type "caring for cars".
Stemming will likely reduce both to "car". It loses the meaning and context.
Lemmatization will correctly find "caring" -> "care" and "cars" -> "car".
Key Takeaway: Speed vs. Precision
The choice between stemming and lemmatization is a trade-off. Stemming is fast and good enough for many uses, but it can be wrong. Lemmatization is more exact and gives meaningful root words. But it comes at a higher compute cost. Choose the tool that best fits the needs of your set NLP task.
The stemmers you will meet
Porter (1980) is the classic for English — five phases of suffix rules, well understood, moderately aggressive. Still the default in many pipelines.
Snowball (Porter2) is Porter's own improvement, and it covers many languages. The better default when you have a choice.
Lancaster is more aggressive and gives shorter stems, sometimes conflating words that should stay distinct.
For lemmatisation, WordNet (via NLTK) is the traditional English option, and spaCy is the real one. It tags parts of speech and lemmatises in one pass, across many languages, and is much faster.
import spacy
nlp = spacy.load("en_core_web_sm")
doc = nlp("The studies were better than we ran previously")
[t.lemma_ for t in doc]
# ['the', 'study', 'be', 'well', 'than', 'we', 'run', 'previously']
Note "be" from "were" and "run" from "ran" — the irregulars a stemmer cannot reach.
What it buys, measured
The gain is vocabulary reduction, and it is large for classical models.
On a typical English corpus, stemming reduces the distinct term count by roughly 30–50%. That means more instances per feature, a smaller and better-conditioned matrix, and less overfitting on rare forms.
Whether it improves precision is less certain and task-dependent. For topic classification and search recall it often helps. For sentiment it can hurt, because it flattens distinctions ("caring" and "cared" carry different implications). For a thing where tense or number is the sign, it clearly hurts.
The honest guidance: treat it as a hyperparameter and test it, rather than applying it because a tutorial did.
Common mistakes
Lemmatising without a part of speech, so verbs and adjectives are treated as nouns and left unchanged.
Using an English stemmer on other languages. Suffix rules are language-set, and applying English ones to German or Turkish gives nonsense.
Stemming before a transformer. Destroys data and creates out-of-vocabulary fragments.
Stemming the query but not the index (or vice versa) in a search system, so nothing matches.
Assuming lemmatisation is always better. It is more correct and slower. And for a search index the stem is fine.
Applying it to named entities, where "Rogers" turning "Roger" changes the referent.
Questions people ask
Which should I use? Lemmatisation when output grade matters or is shown to users; stemming for search indexes and quick baselines; neither for transformers.
Is lemmatisation much slower? Meaningfully so, because it needs POS tagging and dictionary lookups. Roughly an order of size in NLTK, less with spaCy's pipeline.
Do I need stop-word cutting too? For classical bag-of-words models, often yes. For transformers, no.
What about other languages? Snowball covers many for stemming; spaCy has lemmatisers for a wide range. Morphologically rich languages gain more from lemmatisation than English does.
Does it help search? Yes, for recall — matching "running" to a query for "run". Modern search engines do this, alongside embedding-based retrieval.
Why does the Porter stemmer give non-words? Because it applies suffix rules without any dictionary. The stems are index keys, not words.
Recap in one screen
Both reduce inflected forms to a common form so evidence pools across them.
Stemming chops with rules — fast, and it gives non-words and misses irregulars.
Lemmatisation uses a dictionary and part of speech — correct, slower. And it needs the POS to be right.
Use stemming for index keys, lemmatisation for a thing readable, neither before a transformer.
Vocabulary shrinks by 30–50%. Precision gains are task-dependent, so test rather than assume.
Recall check
0 of 4
Say the answer out loud before you reveal it — recalling it is what makes it stick, and rereading it is not.
What is meant by “Lemmatising without a part of speech,” here?
What is meant by “Using an English stemmer on other languages” here?
What is meant by “Stemming before a transformer” here?
What is meant by “Stemming the query but not the index” here?
Cheat sheet
Stemming vs Lemmatization
Compare a stemmer chopping suffixes against a lemmatizer resolving proper dictionary forms of the very same words.
Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.