Text Preparation in R and Python: Collaborative Workflow

QTA principles, cleaning, tokens, and document-feature matrices

Yen-Chieh Liao 廖彥傑

2026-09-18Fall 2026 (115-1) · National Taiwan University (GIND)
NTLDEV 7191 · Fri. 16:30–18:20 · 國發 204
Last compiled: 2026-09-20 04:58:27 (Asia/Taipei)

Learning objectives

Week 2: Text Preparation in R and Python: Collaborative Workflow

  • Set up RStudio and Positron for text analysis with R and Python.
  • Organize code with GitHub and submit written coursework through shared Overleaf projects.
  • Inspect supplied corpora and check project data feasibility: coverage, metadata, and access.
  • Clean and tokenize text, build a DFM, and evaluate preprocessing choices.

Week 4 content introduced today: Text preprocessing and document-feature matrices (DFMs). Week 4 builds on these with dictionaries, sentiment analysis, and validation.

Schoonvelde (2026); Welbers et al. (2017)

Five principles of quantitative text analysis

  1. Domain knowledge defines the research problem and relevant evidence.
  2. Human judgment guides interpretation and validation.
  3. Discovery and testing require distinct stages.
  4. The task determines which methods are useful.
  5. Validation evaluates whether the measure fits the claim.

Schoonvelde (2026); Grimmer et al. (2022)

Selecting documents: Chapter 4
(Grimmer et al. 2022)

Classroom example: measuring expressed preference for coffee.

Design choice Example
Target population Thai-language public posts about coffee in a specified month
Quantity of interest Share of those posts expressing positive preference for coffee
Observed corpus Posts returned by one platform API and a specified keyword search

Found data: document how the texts were produced, preserved and retrieved.

The next step is to examine what the collection process leaves out.

Grimmer et al. (2022)

Selection bias begins before preprocessing

Source of bias Example Research consequence
Resources Well-funded groups archive more documents Available texts overrepresent some actors
Incentives Politicians publish strategically Public statements differ from private beliefs
Medium A platform limits length or visibility The medium shapes expression
Retrieval Searching only for “economy” Relevant texts with other words disappear

Schoonvelde (2026); Grimmer et al. (2022)

Corpus boundaries and document units

Decision Example What to record
Population Parliamentary speech contributions Chamber, actors, dates
Inclusion All contributions in selected debates Rule and exclusions
Document unit One contribution per row How segments were defined
Identifier A persistent doc_id Links to source and metadata

Changing the unit to speaker-day or party-year changes the comparison.

Schoonvelde (2026); Welbers et al. (2017)

Sources and import routes

Source Typical route First check
Existing corpus Download plus codebook Coverage and sampling
TXT, CSV, TSV readr, data.table, base R Encoding and columns
JSON or API jsonlite, httr2 Pagination and identifiers
HTML or XML rvest, xml2 Boilerplate and structure
PDF or DOCX pdftools, readtext Reading order and document boundaries

Scanned PDFs may need OCR before they contain usable text.

Schoonvelde (2026); Welbers et al. (2017)

The R text-analysis toolkit in 2017

Table 1 from Text Analysis in R: operations, example packages and alternatives, grouped into data preparation, analysis and advanced topics.

Historical package overview (2017). Package responsibilities and APIs have since changed.

Welbers et al. (2017)

Beyond 2017: annotation and embeddings

Tool / access from R Model type Output and use
UDPipe 1: R udpipe Supervised annotation pipeline trained on a treebank Tokens, lemmas, POS, dependency labels
UDPipe 2: R + REST API Neural annotation with contextual embeddings Linguistic annotations; current models use BERT-based representations
Flair: R reticulate + Python
(flaiR; Liao et al., 2023)
Contextual string embeddings or Transformer embeddings Word/document vectors; models for NER, tagging and classification

FlairEmbeddings uses character language models; TransformerWordEmbeddings uses models such as BERT.

R can prepare text, call these tools, and analyze the returned annotations or vectors.

Straka & Straková (2017); Straka (2018); Akbik et al. (2018, 2019); Liao et al. (2023)

quanteda, UDPipe, spaCy, and Flair

Tool / access Main emphasis Typical output
quanteda (R) Corpus management and feature analysis Corpus, tokens, DFM; KWIC and TF-IDF
UDPipe (R / API) Linguistic annotation using trained models Tokens, lemmas, POS, UD dependency labels
spaCy (Python) Configurable NLP pipelines Tokens, POS, dependencies, named entities
Flair (Python)
flaiR: R wrapper for Python Flair
Embeddings and neural task models Vectors; entity labels, POS, text categories

The tools can work together: lemmas or selected annotations can become features in a quanteda DFM.

Capabilities overlap and depend on the language, model, and task.

Benoit et al. (2018); Straka & Straková (2017); Explosion (n.d.); Akbik et al. (2019); Liao et al. (2023)

Beyond 2017: R and Python in one workflow

One possible application: compare the meanings of policy statements.

Stage Example tools Contribution
Prepare in R stringr, quanteda Clean text and preserve document IDs
Represent in Python sentence-transformers Encode texts as semantic vectors
Evaluate in R Statistical models and plots Validate similarities and interpret patterns

The languages can share a project. Each stage follows the research question.

Sentence Transformers contributors (n.d.); Ushey et al. (2026)

Connecting R and Python

Python inside an R session

  • R’s reticulate package imports Python modules and calls their functions.
  • I frequently used reticulate during my PhD (2018–2022).
  • It converts supported objects between R and Python.
  • Record the Python environment as well as R packages.

Separate scripts, shared data

  • R and Python scripts exchange files such as CSV or Parquet.
  • A stable document ID connects inputs and outputs.
  • Check row order, encoding and data types at each handoff.

An IDE organizes the work. Object or file exchange connects the languages.

Ushey et al. (2026); Posit (n.d.)

Positron: one IDE for R and Python

In 2022, RStudio the company became Posit, reflecting its broader support for data science across languages.

  • Posit developed Positron for multilingual data science.
  • R and Python have built-in support, with consoles and data exploration.
  • The RStudio IDE remains a separate, maintained product.

Annotated Positron layout showing its editor, console panel and sidebars, with a Python session and plot.

Our classroom examples use R.

Posit (2022, n.d.); Altman (2025)

The sequence of text-analysis operations

Figure 1: files and web pages become an R text corpus, then tokens, then DTM, tokenlist or other representations, and finally results.

The branch matters: analysis can use a matrix, token positions, or other representations.

Welbers et al. (2017)

Objects used in this session

Object Contents Use
Data frame Text, identifiers, metadata Import checks and joins
corpus Text with document variables Subsets and metadata access
tokens Sequences of tokens Phrase matching and context inspection
dfm Documents × features Counts and weighted representations

stringr handles strings. quanteda provides the corpus, tokens, and DFM workflow.

Welbers et al. (2017); Schoonvelde (2026)

Import checks and encoding

texts <- readLines("data/speeches.txt", encoding = "UTF-8")
length(texts)
head(texts)
  • Confirm the source encoding rather than guessing from appearance.
  • Inspect accented characters and Chinese text after import.
  • Count missing texts and duplicated identifiers.
  • Check whether a row is a line, paragraph, or whole document.

A UTF-8 argument does not repair a wrongly identified source encoding.

Schoonvelde (2026); Welbers et al. (2017)

A parliamentary example

Field Value in the source slide
Date 1997-01-20
Speaker William Hague
Party Conservative
Agenda Public Expenditure [Oral Answers To Questions > Wales]

Text includes [Interruption.], DRAMATICALLY, and !!!.

Which elements are metadata, meaningful language, or recording conventions?

Schoonvelde (2026)

Cleaning depends on the research question

Element Possible treatment Information at stake
Speaker and party Store as metadata Who speaks
[Interruption.] Remove or extract into an indicator Parliamentary interaction
Capitalization Preserve or lowercase Names and emphasis
Numbers Keep, normalize, or remove Years, budgets, percentages
Repeated punctuation Preserve or simplify Rhetorical emphasis

Keep an untouched copy of the raw text.

Schoonvelde (2026); Welbers et al. (2017)

String operations with stringr

Function Purpose Example use
str_detect() Test whether a pattern appears Flag bracketed annotations
str_extract() Retrieve the first match Extract a year
str_remove_all() Remove all matches Delete selected annotations
str_replace_all() Replace matches Standardize separators
str_squish() Normalize whitespace Remove repeated spaces
str_to_lower() Change case Merge case variants

Schoonvelde (2026); Wickham (2025)

Bracket patterns and unintended deletion

s <- "Trade [Commons] and jobs [Wales]"

str_remove_all(s, "\\[.*\\]")
# "Trade "

str_remove_all(s, "\\[[^\\[\\]]*\\]") |> str_squish()
# "Trade and jobs"

The first pattern spans from the first [ to the last ]. The second handles separate, non-nested bracket groups.

Schoonvelde (2026); Wickham (2025)

A cleaning example with an audit trail

agenda_raw <- "  Export Trade [Commons] -- vol. 407  "
agenda_clean <- agenda_raw |>
  str_remove_all("\\[[^\\[\\]]*\\]") |>
  str_to_lower() |>
  str_replace_all("[^[:alnum:][:space:]]", " ") |>
  str_squish()
agenda_clean
[1] "export trade vol 407"

This rule preserves numbers and removes punctuation. Another task may need a different rule.

Schoonvelde (2026)

A worked example: a small text corpus

ID Synthetic text
d1 Health workers need health services.
d2 The government supports health services.
d3 Workers do not support the policy.
d4 The government supports the policy.

These short texts let us inspect every token and verify every count.

Creating a corpus

library(quanteda)

corp <- corpus(
  raw,
  docid_field = "doc_id",
  text_field = "text"
)

docnames(corp)
docvars(corp)

Each row supplies one text. The remaining columns become document variables.

Welbers et al. (2017); Schoonvelde (2026)

Tokens, types, and vocabulary

Text: “Health services need health workers.”

Representation Tokens Distinct types
Case and punctuation retained 6 6
Lowercase, punctuation removed 5 4

After normalization: health, services, need, health, workers

A token is an occurrence. A type is a distinct form under the chosen representation.

Schoonvelde (2026); Welbers et al. (2017)

Tokens in R text analysis and LLMs

Both use tokens: units produced by tokenization for analysis or model processing.

Comparison Common R text-analysis workflow Typical LLM workflow
Common units Words; optionally characters or sentences Often subwords; can include whole words, characters or byte units
Segmentation depends on Package, tokenizer and your settings The model’s tokenizer and vocabulary
Main use Word counts, DFM, TF-IDF, topic models Token IDs used for model input and text generation

One word can become several LLM tokens. R can call either kind of tokenizer; the workflow determines the unit.

Benoit et al. (2018); Wolf et al. (2020)

Tokenizing with explicit choices

toks <- tokens(corp, remove_punct = TRUE) |>
  tokens_tolower()

as.list(toks)[[1]]
[1] "health"   "workers"  "need"     "health"   "services"

Inspect the output when texts contain hyphens, URLs, abbreviations, or non-Latin scripts.

Schoonvelde (2026); Benoit et al. (2018)

Preprocessing choices and information loss

Choice Possible benefit Possible loss
Lowercase Merge Health and health Proper-name distinctions
Remove stopwords Reduce frequent function words Negation or writing style
Stem or lemmatize Merge some word forms Differences in meaning
Trim rare features Reduce vocabulary size Rare but important concepts

The relevant question is how the choice affects the intended measure.

Schoonvelde (2026); Welbers et al. (2017)

Stopwords and negation

sw <- setdiff(stopwords("en"), c("not", "no", "nor"))
toks_content <- tokens_remove(toks, pattern = sw)
Rule applied to document d3 Remaining tokens
Standard English stopword list workers support policy
List that retains negation workers not support policy

Retaining not preserves a clue. A unigram model still needs a way to use its scope.

Schoonvelde (2026); Welbers et al. (2017)

The bag-of-words representation

A DFM stores one row per document and one column per feature.

ID health workers government policy not
d1 2 1 0 0 0
d2 1 0 1 0 0
d3 0 1 0 1 1
d4 0 0 1 1 0

Selected columns from our count DFM. Features can also represent phrases or other categories.

Schoonvelde (2026); Welbers et al. (2017)

What word counts cannot recover

“Workers support government.”
“Government support workers.”

The two sentences have the same lowercase unigram counts.

  • Word order indicates who supports whom.
  • Negation requires relationships between words.
  • Ambiguous words need context.
  • Sarcasm may depend on discourse beyond a sentence.

N-grams and syntax retain some additional information.

Schoonvelde (2026); Welbers et al. (2017)

Multiword expressions

phrase_toks <- tokens(
  "We support the armed forces.",
  remove_punct = TRUE
) |> tokens_tolower()

tokens_compound(
  phrase_toks,
  pattern = phrase("armed forces")
)

armed_forces becomes a single feature. Detect phrases before removing words that belong inside them.

Schoonvelde (2026); Welbers et al. (2017)

Bigrams and operation order

tokens("Workers do not support the policy.",
       remove_punct = TRUE) |>
  tokens_tolower() |>
  tokens_ngrams(n = 2)

workers_do, do_not, not_support, support_the, the_policy

Generate these before stopword removal if original adjacency matters. Deleting words first can create pairs that were never adjacent.

Schoonvelde (2026); Welbers et al. (2017)

Collocations and grammatical features

Feature strategy Example Required judgment
Known phrase armed_forces Which phrases matter?
Statistical collocation Candidate such as northern_ireland Is association substantively useful?
POS selection Nouns, verbs, adjectives Which grammatical roles fit the task?

A frequent or associated word pair is a candidate feature. Inspect its uses before adding it to the measurement rule.

Schoonvelde (2026); Welbers et al. (2017)

Stemming and lemmatization

Method Example Limitation
Stemming studies becomes studi Stems may not be dictionary words
Lemmatization studies may become study Accuracy depends on language and context
Explicit replacement Map forces to force Only the supplied mapping is handled

Neither approach guarantees correct semantic grouping. A dictionary built from full words may fail to match stemmed features.

Schoonvelde (2026); Welbers et al. (2017)

Language and model requirements

Language

  • English whitespace is not a universal word boundary.
  • Chinese segmentation needs inspection.
  • Stopword and morphology tools are language specific.

Model

  • A DFM may use word or phrase features.
  • A pretrained model expects its own tokenizer.
  • Preserve the input distinctions that model requires.

A BoW preprocessing recipe should not automatically become an LLM input recipe.

Schoonvelde (2026); Welbers et al. (2017)

UDPipe models: Japanese, Chinese, and Thai

Input language Access from R Model
Japanese udpipe: local UDPipe 1 japanese-gsd (UD 2.5)
Traditional Chinese udpipe: local UDPipe 1 chinese-gsd (UD 2.5)
Thai httr2: UDPipe 2 API thai-tud (UD 2.17)
  • All three provide segmented token and predicted upos (POS).
  • Dependency parsing adds heads and relation labels.
  • lemma is a predicted base form; this Thai model returns _ (unavailable).

Instructor demonstration: interpret the prepared model outputs shown in these slides.

Wijffels & Straka (2026); Straka (2018); Universal Dependencies contributors (n.d.)

Annotating UTF-8 text with UDPipe

library(udpipe)
# Set model_path to a downloaded Chinese-GSD model.
zh_model <- udpipe_load_model(model_path)
ann_zh <- udpipe_annotate(zh_model, x = "我喜歡咖啡。",
  doc_id = "zh_coffee", parser = "default") |> as.data.frame()
ann_zh[c("token_id", "token", "upos",
         "head_token_id", "dep_rel")]

Chinese: chinese-gsd; English: english-ewt; Thai: R + UDPipe 2 API.

  • parser = "default" adds dependency parsing to tokens and POS.
  • head_token_id identifies the head; dep_rel labels the relationship. Head 0 marks the sentence root.

Wijffels & Straka (2026); Universal Dependencies contributors (n.d.)

Word tokenization across languages

Same meaning: “I like coffee.”

Thai

R: httr2 + UDPipe 2 API

ฉันชอบกาแฟ

Tokens (POS)

ฉัน (PRON)
ชอบ (VERB)
กาแฟ (NOUN)

3 tokens

The Thai model finds word boundaries.

Japanese

R: udpipe (japanese-gsd)

私はコーヒーが好きです。

Tokens (POS)

私 (PRON) / は (ADP)
コーヒー (NOUN) / が (ADP)
好き (ADJ) / です (AUX)

6 tokens

Particles and auxiliaries remain separate tokens.

Chinese

R: udpipe (chinese-gsd)

我喜歡咖啡。

Tokens (POS)

我 (PRON)
喜歡 (VERB)
咖啡 (NOUN)

3 tokens

Multi-character words can form single tokens.

POS = part of speech. Counts exclude punctuation.

Straka & Straková (2017); Straka (2018)

Dependency labels across languages

Same meaning: “I like coffee.”

Chinese (Traditional)

R: udpipe (chinese-gsd)

我喜歡咖啡。

Token (POS) dep_rel
1 我 (PRON) nsubj
2 喜歡 (VERB) root
3 咖啡 (NOUN) obj

Head IDs: 2 / 0 / 2

English

R: udpipe (english-ewt)

I like coffee.

Token (POS) dep_rel
1 I (PRON) nsubj
2 like (VERB) root
3 coffee (NOUN) obj

Head IDs: 2 / 0 / 2

Thai

R: httr2 + UDPipe 2 API

ฉันชอบกาแฟ

Token (POS) dep_rel
1 ฉัน (PRON) nsubj
2 ชอบ (VERB) root
3 กาแฟ (NOUN) obj

Head IDs: 2 / 0 / 2

nsubj = nominal subject; root = sentence root; obj = object.

Head 0 marks the root; other IDs point to a token in the same sentence.

Punctuation is omitted from this display.

Straka & Straková (2017); Straka (2018); Universal Dependencies contributors (n.d.)

Japanese: observed segmentation

政府は医療従事者を支援する。

ID token lemma upos head dep_rel
1 政府 政府 NOUN 6 nsubj
2 は は ADP 1 case
3 医療 医療 NOUN 4 compound
4 従事者 従事者 NOUN 6 obj
5 を を ADP 4 case
6 支援 支援 VERB 0 root
7 する する AUX 6 aux
8 。 。 PUNCT 6 punct

Dependency parsing: head = head token ID (0 = root); dep_rel = relation.

Straka & Straková (2017)

Traditional Chinese: observed segmentation

政府支持醫療工作者。

ID token lemma upos head dep_rel
1 政府 政府 NOUN 5 nmod
2 支持 支持 NOUN 5 nmod
3 醫療 醫療 NOUN 5 nmod
4 工作 工作 VERB 5 compound
5 者 者 PART 0 root
6 。 。 PUNCT 5 punct

Dependency parsing: head = head token ID (0 = root); dep_rel = relation.

Model errors: 支持 is tagged NOUN; 者 is predicted as root. Inspect the original sentence before interpreting these outputs.

Straka & Straková (2017); Universal Dependencies contributors (n.d.)

Negation and POS-based feature selection

政府不支持刪減醫療預算。

ID token lemma upos head dep_rel
1 政府 政府 NOUN 3 nsubj
2 不 不 ADV 3 advmod
3 支持 支持 VERB 0 root
4 刪減 刪減 VERB 3 xcomp
5 醫療 醫療 NOUN 6 nmod
6 預算 預算 NOUN 4 obj
7 。 。 PUNCT 3 punct

Dependency parsing: head = head token ID (0 = root); dep_rel = relation.
Keeping only NOUN and VERB removes 不 (ADV), attached to 支持 (ID 3) by advmod. The feature list loses negation.

Straka & Straková (2017)

From Thai syntax to a research measure

ฉันชอบกาแฟ   “I like coffee.”

Dependency relations

Actual Thai dependency tree: ROOT points to ชอบ (VERB, like). ชอบ points to ฉัน (PRON, I) with nsubj and to กาแฟ (NOUN, coffee) with obj.

Arrows run from head to dependent.

  • Language relationships
    POS marks word classes. Dependencies link a subject to a predicate and target.
  • Operationalize preference
    Link the subject to “like” and its object. Check that the statement is affirmative.
  • Code this example
    Coffee: positive preference = 1.
  • Research question
    Which targets receive more expressions of positive preference?

Corpus measure: share of documents mentioning a target that express positive preference.

Straka (2018); Universal Dependencies contributors (n.d.)

UDPipe annotations as quanteda tokens

# Assume ann_multi contains an annotation table.
a <- subset(ann_multi, upos != "PUNCT")
a <- a[order(a$doc_id, a$sentence_id,
             as.integer(a$token_id)), ]

ja_tokens <- split(a$token[a$doc_id == "ja_01"],
                   a$doc_id[a$doc_id == "ja_01"])
ja_dfm <- dfm(as.tokens(ja_tokens), tolower = FALSE)

Use as.tokens() to preserve the existing segmentation. Build the Chinese DFM from the Chinese documents in the same way.

Code illustration: annotations → tokens → DFM.

Benoit et al. (2018); Wijffels & Straka (2026)

Tokens become DFM features

Two synthetic documents, A: Workers support reform; B: Workers oppose reform, feed three DFM routes. Word counts retain workers, support, reform, oppose. Keeping NOUN tokens retains workers and reform, making both document rows identical. A dictionary maps support or endorse to PRO and oppose or reject to CON, producing A: PRO 1 CON 0 and B: PRO 0 CON 1. Rows are documents, columns are chosen features, and cells are counts. POS tags are hand-labelled illustrations.

Benoit et al. (2018)

Building and inspecting the DFM

x <- dfm(toks_content)
dim(x)
topfeatures(x, 6)
[1] 4 9
    health    workers   services government   supports     policy 
         3          2          2          2          2          2 

Rows retain the document IDs. The full DFM includes features beyond the selected columns shown earlier.

Schoonvelde (2026); Welbers et al. (2017)

Term frequency and document frequency

Feature Total occurrences Documents containing it
health 3 2
workers 2 2
not 1 1
docfreq(x)
x_trim <- dfm_trim(x, min_docfreq = 2,
                  docfreq_type = "count")

This threshold removes not. A rare feature can still carry essential meaning.

Schoonvelde (2026); Welbers et al. (2017)

Counts, binary indicators, and TF-IDF

Representation Meaning of a cell Possible use
Count Number of occurrences Frequency-based measurement
Binary Whether the feature appears Presence-based comparison
TF-IDF Count adjusted for corpus prevalence Document retrieval or comparison

The choice changes what repeated words contribute. Model requirements may constrain which representation is appropriate.

Schoonvelde (2026); Welbers et al. (2017)

TF-IDF with a stated formula

For this lesson, use raw counts and a base-10 logarithm:

TF-IDF(d, t) = count(d, t) × log₁₀(N / df(t))

  • N is the number of documents in the reference corpus.
  • df(t) counts documents that contain feature t.
  • A feature in every document receives IDF = 0 under this formula.

Different normalizations and smoothing rules produce different weights.

Schoonvelde (2026); Benoit et al. (2018)

TF-IDF in the worked example

For health, N = 4, df = 2, so IDF = log₁₀(2) ≈ 0.3010.

Document Count of health TF-IDF
d1 2 0.6021
d2 1 0.3010
d3 0 0
d4 0 0
x_weighted <- dfm_tfidf(x, scheme_tf = "count",
                      scheme_df = "inverse", base = 10)

Benoit et al. (2018)

Context checks with KWIC

kwic(toks, pattern = "support*", window = 3)
docname pre keyword post
d2 the government supports health services
d3 workers do not support the policy
d4 the government supports the policy

Read the context around a matched term before treating its count as support for a policy.

Welbers et al. (2017); Benoit et al. (2018)

Wrap-up: DFM, KWIC, TF-IDF

Tool What it lets you do
DFM Represent document features for comparison and modeling.
KWIC Read terms in context and check what feature counts mean.
TF-IDF Emphasize distinctive terms for retrieval and similarity.

Wordfish is an unsupervised method for scaling texts on one dimension.

Word-count DFM → e.g., Wordfish → relative document positions

Use: compare party manifestos or speeches. Input: counts, not TF-IDF weights.
Interpret and validate the scale before calling it ideology.

Benoit et al. (2018); Slapin & Proksch (2008); Lowe & Benoit (2013)

Research applications of a DFM

Research goal Method Output
Estimate positions Wordfish / Wordscores Relative document positions
Predict a category Supervised classifiers Issue or stance labels
Study issue attention LDA / STM / keyATM Topics and topic shares
Measure a concept Dictionaries Category counts or proportions
Compare documents Cosine similarity / clustering Similarities or document groups

Inputs matter: Wordfish and these topic models use counts. Classification needs training labels; Wordscores needs reference scores. Similarity can use TF-IDF.

Benoit et al. (2018); Slapin & Proksch (2008); Laver et al. (2003); Blei et al. (2003); Roberts et al. (2019)

Topic models: STM and keyATM

STM Roberts et al. (2014)

Paper screenshot: Roberts et al. (2014)

Compare themes in survey responses across respondent groups.

keyATM Eshima et al. (2024)

Paper screenshot: Eshima et al. (2024)

Use selected keywords to guide topics and study policy attention.

Text scaling: Wordfish and Wordscores

Wordfish Slapin & Proksch (2008)

Paper screenshot: Slapin & Proksch (2008)

Estimate relative party positions from manifesto word counts.

Wordscores Lowe (2008)

Paper screenshot: Lowe (2008)

Position new texts using reference texts with known scores.

Wordfish: German party manifestos

Original Figure 1A from Slapin and Proksch (2008): estimated positions of PDS, Greens, SPD, CDU/CSU, and FDP at five German elections from 1990 to 2005. Higher values indicate the left in the authors' orientation. No uncertainty intervals are plotted.

Slapin & Proksch (2008)

  • Corpus: 25 manifestos from five parties and five elections, 1990–2005.
  • Model: word-count DFM → Wordfish → relative party positions.
  • Finding: the Greens move toward the centre over time.

Reading the axis: Higher values indicate the left; the sign is conventional.

Slapin & Proksch (2008), Figure 1A, p. 714

Wordscores: Irish confidence debate

Original Figure 3 from Laver et al. (2003): standardized Wordscores for Irish confidence-debate speeches by party and ministerial status. Higher scores indicate greater support for the government. Boxes summarize group distributions, not confidence intervals.

3 reference speeches (+1 / −1) → scores for 55 other speeches.
Higher scores indicate greater support for the government.

Laver et al. (2003), Figure 3, p. 328

TF-IDF: applications in political research

Text similarity Blumenau (2021)

Cambridge article webpage screenshot: Blumenau (2021), first published online in 2019

Track whose language later speakers use in Commons debates.

Stance classification Bestvater & Monroe (2023)

Paper screenshot: Bestvater & Monroe (2023)

Use TF-IDF with an SVM to classify political support or opposition.

TF-IDF supplies weighted features. The analysis determines what we measure.

Sensitivity to preprocessing

Compare Inspect Explain
With / without stopwords Negation and grammatical context Which distinctions vanish?
Unigrams / phrases Compound concepts Which matches change?
Full / trimmed vocabulary Removed features and empty rows Which documents lose evidence?
Counts / TF-IDF Relative feature contributions Does the result depend on weighting?

Keep the corpus and research question fixed while comparing a chosen rule.

Schoonvelde (2026); Welbers et al. (2017)

In-class setup: install, open, run

Download and install: R and Python 3, then RStudio Desktop and Positron.

Try the consoles: 1 + 1 in R; print(1 + 1) in Python. Both return 2.

Open Lab_Session_W2.R in lab/. In the R console, install:

install.packages(c("dplyr", "stringr", "ggplot2",
  "quanteda", "quanteda.textstats", "quanteda.textplots"))

Set the working directory to W2/lab/. Keep its Data/ folder alongside the script. Use the lab section by section for self-study at home.

Take-home skill: Open an .R file in RStudio or Positron and run the lab independently.

R Core Team (n.d.); Python Software Foundation (n.d.); Posit (n.d.)

Self-study lab

Self-study practice: ungraded; no submission required. Use the supplied sample of 5,000 House of Commons speech contributions (1945–2025).

  1. Inspect strings and clean agenda metadata with stringr.
  2. Build, subset, and reshape a quanteda corpus.
  3. Tokenize, preserve phrases, and inspect KWIC contexts.
  4. Build and trim DFMs; explore frequencies, plots, and keyness.
  5. Work through the 11 practice exercises in the lab.

Folder: lab/
Open Lab_Session_W2.qmd or its .R companion. Data are included.

Schoonvelde (2026)

Self-study practice and support

  • Run the worked examples section by section and inspect each output.
  • Bring the exercise number, relevant code, and output or error message.
  • Discuss questions before class, if time permits, or ask during TA hours.

Be ready to explain what your code changes and how that affects the evidence.

Discussion

Explore one application paper introduced earlier. Start with its research question and a main figure.

  1. What is the research question, and what counts as a document?

  2. How could word removal, document aggregation, or weighting affect the measure?

  3. What evidence supports the authors’ interpretation beyond successful preprocessing?

Readings and software references

Primary lecture source
Schoonvelde, M. (2026). Quantitative text analysis – Essex Summer School: Principles of QTA, cleaning text, and preprocessing text. Supplied slides, 38 pages.

Reading and reproduced figure/table
Welbers, K., Van Atteveldt, W., & Benoit, K. (2017). Text Analysis in R. Communication Methods and Measures, 11(4), 245–265. doi:10.1080/19312458.2017.1387238.

Corpus selection reading
Grimmer, J., Roberts, M. E., & Stewart, B. M. (2022). Text as Data: A New Framework for Machine Learning and the Social Sciences. Princeton University Press. Chapter 4: “Selecting Documents.” Book.

Software documentation
quanteda: tokens, TF-IDF, KWIC, stringr: pattern removal.

UDPipe guide, Japanese GSD, Chinese GSD.

The English, Thai, Japanese, and Chinese texts are synthetic teaching examples.