Text analysis (Design A) and Excel files — build 2026-10-31.48
Tests → Text analysis is rebuilt as five steps that follow quanteda’s order
(corpus → tokens → dfm → statistics and models). Everything runs inside
index.html, offline, with heavy work in the background worker.
| Step | What it does | quanteda equivalent |
|---|---|---|
| 1 Texts | text, group and date columns; each row or each sentence; language detected; warnings (Excel’s 32,767-character cell limit, blank rows, groups that differ only in capitals, date order) | corpus(), corpus_reshape() |
| 2 Clean | links, numbers, emoji, @mentions, #hashtags, hyphens; accents (ignore keeps ñ/ç); stopwords in 15 languages (Snowball or stopwords-iso) plus your own list (import/export, × in any table); stems (Snowball es/en) or lemmas (es/en/pt/fr); joined phrases with suggestions; rare/common word trimming; live preview of one text | tokens(remove_*), tokens_remove(stopwords()), tokens_compound(phrase()), tokens_wordstem(), tokens_replace(), dfm_trim() |
| 3 Explore | Words (table + word cloud with colour rules by frequency bands / group / dictionary category, split by any column, comparison cloud, bar chart); Phrases; Compare groups (keyness); In context (kwic with match lists); Over time; Connections (network); Clusters (dendrogram); Text stats | textstat_frequency, textplot_wordcloud(comparison =), textstat_collocations, textstat_keyness + textplot_keyness, kwic, fcm + textplot_network, textstat_simil/dist + hclust, textstat_lexdiv, textstat_readability |
| 4 Measure | Dictionary (glob * ?, phrases, -exclusions, untick false matches, accents, LIWC/YAML/Lexicoder import, precision check); Sentiment (polarity, valence); Topics (LDA and seeded LDA, choose k by divergence, 3-seed stability, pooling); Classify (Naive Bayes); Scale (Wordfish, Wordscores, CA) |
dictionary, tokens_lookup(nested_scope = "key"), quanteda.sentiment, seededlda::textmodel_lda/seededlda, divergence, textmodel_nb, textmodel_wordfish/wordscores/ca |
| 5 Save | per category: count, 1/0, per 100 words, share; word counts; main topic + shares; sentiment; predicted label; clusters; all rows or the subgroup; preview and an automatic totals check; R code that recreates the columns | convert(dfm(tokens_lookup()), to = "data.frame") |
Over time opens on the number of texts (e.g. tweets) per day, week, month, quarter or year, one line per author; empty periods count as 0 and each line runs from that author’s first to last text. It also compares authors and words/categories together: one line per author, one line per word / category, one chart per author (lines = words, categories or variables) or one chart per word / category (lines = authors). Items can be the words of a search (each its own line), all dictionary categories, or saved variables (d_economia, d_salud_any, …) from the second dropdown. The Dictionary results include the same timeline and a category cloud, shown by default: each category sized in proportion to its mentions, texts or % of texts (one cloud per author on a common scale), or the words inside each category coloured by category.
Every chart downloads as SVG (fonts embedded) or PNG at 150/300/600 dpi (with the dpi stored in the file). “Recipe · R code” shows the quanteda script for the current cleaning.
Fixes to the old tab
Accents broke matching (“economia” found 0 tweets, 313 when accents are ignored); keywords were not
Unicode-normalised; overlapping keywords were double counted; #hashtag, covid-19 and *terror*
keywords never matched; links became “https”/”co” words; inclusive spellings (tod@s) were split;
variable names lost accented letters (d_econom_a) and could pass Stata’s 32-character limit; two
categories with the same name overwrote each other; the R code could not reproduce the counts.
Excel
.xlsx/.xlsm load like CSV: shared and inline strings, rich text, numbers, booleans, errors
(missing), formulas’ cached values, 1900/1904 dates and date/time formats, hidden sheets, a sheet
picker under Data information, ZIP64, native or pure-JS inflate. Old .xls, .xlsb and .ods
files get a clear message asking to save as .xlsx or CSV.
Build and test
python calculator/tools/text_lab.py
node --test calculator/tests/text-lab.test.mjs calculator/tests/text-lab-component.test.mjs calculator/tests/xlsx.test.mjs
GM26_TEST_XLSX=/path/to/workbook.xlsx node --test calculator/tests/xlsx.test.mjs
GM26_TEXT_FILES="/path/tweets.csv,/path/speeches.xlsx" NODE_PATH=$(npm root -g) node calculator/tests/text-lab-e2e.cjs
Validation: the Snowball stemmers match the reference implementation on 3,000 Spanish and 183
English words; stopword lists are quanteda’s own (stopwords package data); keyness, collocations
(λ, z), hclust heights and cophenetic correlation are checked against hand computations; the
browser test walks every step on a CSV and an Excel file and checks that saved columns add up to
the dictionary table.
Sources: src/text-lab*.{mjs,js,css}, src/xlsx-reader.mjs, src/lemmas/ (spaCy lookup tables,
MIT), tools/text_lab.py.
