library(tidytext)
library(dplyr)
library(ggwordcloud)
library(ggplot2)
speech <- "Four score and seven years ago our fathers brought forth on this
continent, a new nation, conceived in Liberty, and dedicated to the proposition
that all men are created equal. Now we are engaged in a great civil war, testing
whether that nation, or any nation so conceived and so dedicated, can long endure.
We are met on a great battle-field of that war. We have come to dedicate a portion
of that field, as a final resting place for those who here gave their lives that
that nation might live. But, in a larger sense, we can not dedicate, we can not
consecrate, we can not hallow this ground. The brave men, living and dead, who
struggled here, have consecrated it, far above our poor power to add or detract.
The world will little note, nor long remember what we say here, but it can never
forget what they did here. It is for us the living, rather, to be dedicated here
to the unfinished work which they who fought here have thus far so nobly advanced.
It is rather for us to be here dedicated to the great task remaining before us,
that from these honored dead we take increased devotion to that cause for which
they gave the last full measure of devotion, that we here highly resolve that
these dead shall not have died in vain, that this nation, under God, shall have a
new birth of freedom, and that government of the people, by the people, for the
people, shall not perish from the earth."
text_df <- data.frame(text = speech, stringsAsFactors = FALSE)Word Cloud in R: A Modern Guide with tidytext and ggwordcloud
Turn text into a word cloud with a tidy, ggplot2-native workflow
Build a word cloud in R the modern way — tokenize text with tidytext, count word frequencies, drop stop words, and render a ggplot2-native cloud with ggwordcloud. A clean replacement for the older tm + wordcloud recipe.
- The modern word-cloud pipeline in R: tokenize → drop stop words → count → render — all tidy, all ggplot2-native.
- How to turn any text into a word-frequency table with
tidytext(unnest_tokens,stop_words,count). - How to draw the cloud with
ggwordcloud(geom_text_wordcloud_area) — sized by frequency, coloured on a viridis scale, and themeable like any other ggplot. - Why this replaces the old
tm+wordcloud()recipe — noCorpus, notm_map, noTermDocumentMatrix.
A word cloud turns a block of text into a picture: the more often a word appears, the bigger it shows up. It’s a fast, visually engaging way to surface the dominant themes in survey responses, reviews, speeches, or any free text.
The classic R recipe for this used the tm (text mining) and wordcloud packages — building a Corpus, running a chain of tm_map() transformations, and extracting a TermDocumentMatrix. That still works, but it’s verbose and off to the side of the modern tidy toolchain. This post does the same job with tidytext + ggwordcloud: a handful of pipeline steps that produce a tidy frequency table and a real ggplot you can theme, colour, and combine like any other figure.
We’ll use the Gettysburg Address (public domain) as the example text — but every step works on your text: paste it into a string, or read a file with readLines().
The modern word-cloud pipeline in one glance
Four steps take you from raw text to a finished cloud:
- Get the text into a one-column data frame (one row of text is fine).
- Tokenize — split the text into individual words with
tidytext::unnest_tokens(). - Clean + count — drop common stop words (
the,and,of…) withstop_words, thencount()what’s left. - Render the frequency table with
ggwordcloud::geom_text_wordcloud_area().
Everything below is that pipeline, one step at a time. You only need three packages: tidytext, dplyr, and ggwordcloud (plus ggplot2 for theming).
Step 1 — Get your text into a data frame
First, put the text somewhere R can work with it. The tidy workflow expects a data frame with a text column — each row can hold as much or as little text as you like. Here we drop the whole speech into a single-row data frame:
To use your own text instead, read a file straight into the same shape:
# One row per line of the file:
text_df <- data.frame(
text = readLines("my-text.txt"),
stringsAsFactors = FALSE
)Step 2 — Tokenize with tidytext
Next, split the text into individual words. unnest_tokens() does this in one call: give it the output column name (word) and the input column (text), and it returns a long data frame with one row per word — already lower-cased and stripped of punctuation:
tokens <- text_df |>
unnest_tokens(word, text)
head(tokens, 8) word
1 four
2 score
3 and
4 seven
5 years
6 ago
7 our
8 fathers
That’s the entire “text transformation + cleaning” stage of the old tm recipe — lower-casing, punctuation removal, whitespace stripping — collapsed into a single, readable line.
Step 3 — Drop stop words and count frequencies
Most of those tokens are stop words: high-frequency, low-information words like the, and, of, we. tidytext ships a ready-made stop_words table, so we remove them with an anti_join() and then count() what remains, sorted by frequency:
word_freq <- tokens |>
anti_join(stop_words, by = "word") |>
count(word, sort = TRUE)
head(word_freq, 10) word n
1 nation 5
2 dedicated 4
3 dead 3
4 people 3
5 conceived 2
6 dedicate 2
7 devotion 2
8 field 2
9 living 2
10 war 2
The result is a tidy word-frequency table — the same thing the old TermDocumentMatrix() produced, but as a plain data frame you can inspect, filter, and plot directly. Here the top words — nation, dedicated, dead, people, conceived — instantly summarize the speech.
Want your own stop words gone too (e.g. domain filler)? Just filter them out before plotting:
custom_stop <- c("said", "also", "one")
word_freq <- word_freq |>
filter(!word %in% custom_stop)Step 4 — Render the word cloud with ggwordcloud
Now the payoff. ggwordcloud adds a ggplot geom, so a word cloud is just a normal ggplot() call: map label to the word and size to its frequency, then add geom_text_wordcloud_area(). We set a seed first because the layout is placed randomly — seeding it keeps the figure reproducible:
set.seed(1234)
ggplot(word_freq, aes(label = word, size = n)) +
geom_text_wordcloud_area() +
scale_size_area(max_size = 24) +
theme_minimal()
geom_text_wordcloud_area() scales each word so its area — not just its height — is proportional to frequency, which reads more honestly than the older wordcloud() output. scale_size_area(max_size = ...) controls how big the largest word gets; nudge it to fit your canvas.
Add colour and keep every word inside the frame
Two upgrades make the cloud publication-ready. Map colour to frequency so the eye follows importance (a colourblind-safe viridis scale), and pass rm_outside = TRUE so any word that can’t fit is dropped rather than clipped at the edge:
set.seed(1234)
ggplot(word_freq, aes(label = word, size = n, color = n)) +
geom_text_wordcloud_area(rm_outside = TRUE) +
scale_size_area(max_size = 26) +
scale_color_viridis_c() +
theme_minimal()
Because it’s a real ggplot, everything you already know applies: swap in scale_color_gradient(low = "#cfe3f7", high = "#3a86d4") for a brand-azure ramp, add a labs(title = ...), or drop the legend with theme(legend.position = "none").

Limit the cloud to the top words
For a busier text, a cloud with hundreds of tiny words is noise. Keep only the words that carry the signal — the equivalents of the old min.freq / max.words arguments — by filtering or slicing the table before you plot:
# Keep words used at least twice:
word_freq |> filter(n >= 2)
# Or keep only the 100 most frequent:
word_freq |> slice_max(n, n = 100)Bonus — a word-frequency bar chart
A cloud is great for a quick gestalt, but for exact comparisons a bar chart is more precise. It’s one more ggplot on the same frequency table — reorder the words by count so the bars read top-to-bottom:
top_words <- head(word_freq, 12)
ggplot(top_words, aes(x = n, y = reorder(word, n))) +
geom_col(fill = "#3a86d4") +
labs(x = "Frequency", y = NULL, title = "Most frequent words") +
theme_minimal(base_size = 13)
Pair the two: the cloud for the poster, the bar chart when a reader asks exactly how often each word appears.
Frequently asked questions
You don’t need to. The tm + wordcloud() recipe (Corpus, tm_map, TermDocumentMatrix) still runs, but the tidytext + ggwordcloud pipeline shown here does the same job with less code, returns a tidy data frame you can inspect and reuse, and produces a real ggplot you can theme and colour. Reach for the tidy stack for anything new.
Read the file into the one-column data frame that starts the pipeline: text_df <- data.frame(text = readLines("my-text.txt"), stringsAsFactors = FALSE). Everything after Step 1 — unnest_tokens(), the stop-word anti_join(), count(), and geom_text_wordcloud_area() — is unchanged.
tidytext’s built-in stop_words table handles the common ones. For extra words, filter them out before plotting: word_freq |> filter(!word %in% c("said", "also", "one")). You can also append your list to stop_words with bind_rows() and keep one anti_join().
ggwordcloud places words with a random layout, so each run differs unless you fix the seed. Call set.seed(1234) (any integer) immediately before the plot to get a reproducible arrangement — essential for a figure you commit or publish.
Yes. Stemming collapses dedicate, dedicated, and dedication into one root. Add the SnowballC package and mutate the token column before counting: mutate(word = SnowballC::wordStem(word)). It’s optional — for a display word cloud, unstemmed words often read more naturally.
Going deeper in /learn
This post is the focused recipe. Because a word cloud is just a ggplot, the full ggplot2 toolkit — scales, colour palettes, and themes — carries straight over:
- ggplot2 series — the complete, reproducibility-gated walkthroughs of geoms, scales, and themes.
- Data Visualization — the whole pillar, from first plot to publication figure.
Citation
@online{kassambara2026,
author = {Kassambara, Alboukadel},
title = {Word {Cloud} in {R:} {A} {Modern} {Guide} with Tidytext and
Ggwordcloud},
date = {2026-07-08},
url = {https://www.datanovia.com/blog/word-cloud-in-r},
langid = {en}
}