| Title: | Social Representation Analysis via Semantic Network Mapping |
|---|---|
| Description: | Implements the ThemeScope framework for detecting and visualising social representations in large-scale digital text corpora. From raw documents it builds, via 'udpipe' annotation, sentence-level word co-occurrence networks and derives two community-level indicators grounded in Social Representation Theory: the Prototypical Salience Index (PSI) for anchoring and the Concreteness Score (CS) for objectification. Communities are located in a two-dimensional, theoretically grounded representational map. The whole pipeline is usable from the R console; an optional 'shiny' graphical interface calls the same exported functions. The method is described in Misuraca, Spano and D'Aniello (2026) <doi:10.1177/01655515261454276>. |
| Authors: | Luca D'Aniello [aut, cre] (ORCID: <https://orcid.org/0000-0003-1019-9212>), Michelangelo Misuraca [aut] (ORCID: <https://orcid.org/0000-0002-8794-966X>), Maria Spano [aut] (ORCID: <https://orcid.org/0000-0002-3103-2342>) |
| Maintainer: | Luca D'Aniello <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.1 |
| Built: | 2026-09-22 09:11:20 UTC |
| Source: | https://github.com/lucadaniello/themescoper |
Maps communities to the four quadrants of the ThemeScope representational space based on their z-scored Prototypical Salience Index (PSI, anchoring) and Concreteness Score (CS, objectification).
assign_quadrant(psi_z, cs_z)assign_quadrant(psi_z, cs_z)
psi_z |
Numeric vector of z-scored PSI values. |
cs_z |
Numeric vector of z-scored CS values (same length as |
A factor with levels:
"Stable Core": high PSI, high CS.
"Ideological Core": high PSI, low CS.
"Emerging Practices": low PSI, high CS.
"Latent Representations": low PSI, low CS.
assign_quadrant(c(1, 1, -1, -1), c(1, -1, 1, -1))assign_quadrant(c(1, 1, -1, -1), c(1, -1, 1, -1))
A lexicon of concreteness ratings for English words, from crowd-sourced human
judgements on a 1–5 scale (1 = highly abstract, 5 = highly concrete). Used by
compute_cs() and themescope() to compute the Concreteness Score (CS),
operationalising objectification in Social Representation Theory.
brysbaertbrysbaert
A data frame with two columns:
Character. The English word (lower-case).
Numeric. Mean concreteness rating on a 1–5 scale.
Brysbaert, M., Warriner, A. B., & Kuperman, V. (2014). Concreteness ratings for 40 thousand generally known English word lemmas. Behavior Research Methods, 46(3), 904–911. doi:10.3758/s13428-013-0403-5
data(brysbaert) head(brysbaert)data(brysbaert) head(brysbaert)
Counts how many text units (sentences or documents) each pair of vocabulary
terms co-occur in (each pair counted at most once per unit), then optionally
re-weights the counts with a similarity measure (see normalize_cooccurrence()).
Also returns the presence of each term, the number of units
containing it, which is the correct marginal for the normalisation.
build_cooccurrence_matrix( words_df, vocab = NULL, unit = c("lemma", "token"), vocab_size = NULL, pos_filter = c("NOUN", "ADJ", "PROPN"), window = c("sentence", "document"), normalization = NULL )build_cooccurrence_matrix( words_df, vocab = NULL, unit = c("lemma", "token"), vocab_size = NULL, pos_filter = c("NOUN", "ADJ", "PROPN"), window = c("sentence", "document"), normalization = NULL )
words_df |
An annotated words data frame (e.g. from |
vocab |
Optional vocabulary. If |
unit |
Character. Word column to use: |
vocab_size |
Integer or |
pos_filter |
Character vector of POS tags. Passed to |
window |
Co-occurrence unit: |
normalization |
Similarity measure applied to the counts: one of
|
A named list with:
cooc_matrixSymmetric sparse matrix after normalization
(raw counts if NULL/"frequency").
countsThe raw sentence/document co-occurrence counts.
presenceNamed integer vector (units containing each term).
vocabThe vocabulary data frame used.
unit, window, normalization
The settings used.
df <- data.frame( doc_id = c(1, 1, 1, 1, 1), sentence_id = c(1, 1, 1, 2, 2), lemma = c("dog", "cat", "dog", "bird", "cat"), upos = "NOUN" ) res <- build_cooccurrence_matrix(df, normalization = "association") res$cooc_matrixdf <- data.frame( doc_id = c(1, 1, 1, 1, 1), sentence_id = c(1, 1, 1, 2, 2), lemma = c("dog", "cat", "dog", "bird", "cat"), upos = "NOUN" ) res <- build_cooccurrence_matrix(df, normalization = "association") res$cooc_matrix
Constructs an undirected weighted igraph graph from a similarity
matrix, retaining only edges whose weight exceeds the threshold_percentile
quantile of all non-zero weights.
build_cooccurrence_network( as_matrix, threshold_percentile = 0.98, verbose = TRUE )build_cooccurrence_network( as_matrix, threshold_percentile = 0.98, verbose = TRUE )
as_matrix |
Symmetric sparse |
threshold_percentile |
Numeric in |
verbose |
Logical. Print progress messages (default |
An undirected weighted igraph object; E(graph)$weight holds
the edge weights and V(graph)$name the term labels. Only terms with at
least one retained edge appear as vertices.
words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) vocab <- build_vocab(words, vocab_size = 300) cooc <- build_cooccurrence_matrix(words, vocab = vocab) net <- build_cooccurrence_network(cooc$cooc_matrix, threshold_percentile = 0.98, verbose = FALSE) networds <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) vocab <- build_vocab(words, vocab_size = 300) cooc <- build_cooccurrence_matrix(words, vocab = vocab) net <- build_cooccurrence_network(cooc$cooc_matrix, threshold_percentile = 0.98, verbose = FALSE) net
Counts the most frequent terms in a POS-filtered, annotated data frame and
returns them as a tidy vocabulary. The analysis unit, the "word", can be the
surface token or the lemma.
build_vocab( words_df, unit = c("lemma", "token"), vocab_size = NULL, pos_filter = c("NOUN", "ADJ", "PROPN") )build_vocab( words_df, unit = c("lemma", "token"), vocab_size = NULL, pos_filter = c("NOUN", "ADJ", "PROPN") )
words_df |
An annotated data frame (e.g. the output of
|
unit |
Character. Which column to use as the word: |
vocab_size |
Integer or |
pos_filter |
Character vector of Universal POS tags to retain (default
|
A data frame ordered by decreasing frequency with columns:
token or lemma
The word (lower-cased); the column is named
after unit.
freqInteger term frequency (number of occurrences).
uposThe dominant (most frequent) Universal POS tag of the term.
df <- data.frame( doc_id = c(1, 1, 1, 1), sentence_id = c(1, 1, 1, 2), token = c("dogs", "cats", "dog", "birds"), lemma = c("dog", "cat", "dog", "bird"), upos = "NOUN" ) build_vocab(df, unit = "lemma")df <- data.frame( doc_id = c(1, 1, 1, 1), sentence_id = c(1, 1, 1, 2), token = c("dogs", "cats", "dog", "birds"), lemma = c("dog", "cat", "dog", "bird"), upos = "NOUN" ) build_vocab(df, unit = "lemma")
Convenience wrapper around normalize_cooccurrence() with
method = "association": (ThemeScope
papers, Eq. 1). Values lie in ; higher values indicate stronger
association, reducing the bias of highly frequent terms.
compute_association_strength(cooc_matrix, presence)compute_association_strength(cooc_matrix, presence)
cooc_matrix |
Symmetric sparse |
presence |
Named numeric vector of term presences |
A sparse dgCMatrix of Association Strength values.
normalize_cooccurrence() for other similarity measures.
m <- Matrix::sparseMatrix( i = c(1, 2), j = c(2, 1), x = c(3, 3), dims = c(3, 3), dimnames = list(c("a", "b", "c"), c("a", "b", "c")) ) compute_association_strength(m, c(a = 5, b = 4, c = 2))m <- Matrix::sparseMatrix( i = c(1, 2), j = c(2, 1), x = c(3, 3), dims = c(3, 3), dimnames = list(c("a", "b", "c"), c("a", "b", "c")) ) compute_association_strength(m, c(a = 5, b = 4, c = 2))
Coherence is the ratio of actual to maximum possible edges within a community
(its edge density): .
compute_coherence(graph, communities)compute_coherence(graph, communities)
graph |
An |
communities |
Named list of character vectors of community members. |
Named numeric vector of coherence values in . Communities
with fewer than 2 members return NA.
words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) compute_coherence(result$graph, result$communities)words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) compute_coherence(result$graph, result$communities)
The CS operationalises objectification: the degree to which a community is grounded in concrete, perceptually accessible content. It is the association-strength-weighted mean concreteness of edge endpoints
where is the AS edge weight and the concreteness
rating. Only edges where both endpoints have a rating contribute.
compute_cs(graph, communities, concreteness_lexicon = brysbaert)compute_cs(graph, communities, concreteness_lexicon = brysbaert)
graph |
An undirected weighted |
communities |
Named list of character vectors of community members. |
concreteness_lexicon |
Data frame with columns |
A named numeric vector of CS values. Communities where no edge has
both endpoints in the lexicon return NA.
words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) compute_cs(result$graph, result$communities)words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) compute_cs(result$graph, result$communities)
Compute network-level and community-level statistics
compute_network_stats(graph, communities)compute_network_stats(graph, communities)
graph |
An |
communities |
Named list of character vectors of community members. |
A list with community_stats (data frame: community, size,
density, mean_degree, n_edges) and global_stats (named list:
n_nodes, n_edges, mean_degree, modularity, n_communities).
words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) compute_network_stats(result$graph, result$communities)$global_statswords <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) compute_network_stats(result$graph, result$communities)$global_stats
The PSI operationalises anchoring in Social Representation Theory: the degree to which a thematic community is structurally embedded in the wider discourse. Following the ThemeScope papers it is defined as
where is the presence of term (the number of sentences
containing it), is
the mean association strength of 's neighbours within the community
subgraph, and is the weighted
internal density of community ( = sum of internal edge
weights).
compute_psi(graph, communities, presence)compute_psi(graph, communities, presence)
graph |
An undirected weighted |
communities |
Named list of character vectors of community members, as
returned by |
presence |
Named numeric vector of term presence |
This implementation follows the equations in the ThemeScope manuscript
(Eqs. 2–4) and case study (Eq. 2): the per-term contribution uses the
mean neighbour association strength (not the raw degree) and the
normalisation uses the maximum weighted community density (not the maximum
numerator). Because the denominator is a single global constant, the
subsequent z-scoring used for the ThemeScope map is unaffected by it.
A named numeric vector of PSI values, one per community.
words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) compute_psi(result$graph, result$communities, result$presence)words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) compute_psi(result$graph, result$communities, result$presence)
Runs walktrap or Louvain community detection on a weighted undirected graph and filters out small communities.
detect_communities( graph, algorithm = "walktrap", steps = 4, resolution = 1, min_size = 10, seed = NULL, verbose = TRUE )detect_communities( graph, algorithm = "walktrap", steps = 4, resolution = 1, min_size = 10, seed = NULL, verbose = TRUE )
graph |
An undirected weighted |
algorithm |
Character. One of |
steps |
Integer. Number of steps for the walktrap random walk (default
|
resolution |
Numeric resolution parameter for |
min_size |
Integer. Minimum number of members for a community to be
retained; smaller communities are assigned |
seed |
Optional integer. If supplied, sets the random seed before community detection for reproducibility (Louvain and Leiden are stochastic). |
verbose |
Logical. Print a progress message (default |
A named list with:
membershipNamed integer vector mapping each vertex to its
community ID (NA for vertices in removed small communities).
communitiesNamed list of character vectors of member names, one
per retained community ("C1", "C2", ...).
algorithm_resultThe raw communities object from igraph.
words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) vocab <- build_vocab(words, vocab_size = 300) cooc <- build_cooccurrence_matrix(words, vocab = vocab) net <- build_cooccurrence_network(cooc$cooc_matrix, verbose = FALSE) comm <- detect_communities(net, algorithm = "walktrap", min_size = 5, seed = 1) lengths(comm$communities) comm <- detect_communities(net, algorithm = "louvain", resolution = 1, min_size = 5, seed = 1) lengths(comm$communities)words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) vocab <- build_vocab(words, vocab_size = 300) cooc <- build_cooccurrence_matrix(words, vocab = vocab) net <- build_cooccurrence_network(cooc$cooc_matrix, verbose = FALSE) comm <- detect_communities(net, algorithm = "walktrap", min_size = 5, seed = 1) lengths(comm$communities) comm <- detect_communities(net, algorithm = "louvain", resolution = 1, min_size = 5, seed = 1) lengths(comm$communities)
For each retained community, induces the subgraph of its vertices and their interconnecting edges.
get_community_subgraphs(graph, membership)get_community_subgraphs(graph, membership)
graph |
An |
membership |
Named integer vector of community assignments from
|
A named list of igraph objects, one per community
("C1", "C2", ...).
words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) vocab <- build_vocab(words, vocab_size = 300) cooc <- build_cooccurrence_matrix(words, vocab = vocab) net <- build_cooccurrence_network(cooc$cooc_matrix, verbose = FALSE) comm <- detect_communities(net, min_size = 5, seed = 1) subgraphs <- get_community_subgraphs(net, comm$membership) vapply(subgraphs, igraph::vcount, numeric(1))words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) vocab <- build_vocab(words, vocab_size = 300) cooc <- build_cooccurrence_matrix(words, vocab = vocab) net <- build_cooccurrence_network(cooc$cooc_matrix, verbose = FALSE) comm <- detect_communities(net, min_size = 5, seed = 1) subgraphs <- get_community_subgraphs(net, comm$membership) vapply(subgraphs, igraph::vcount, numeric(1))
Reports how many terms have a rating in a concreteness lexicon, overall and
for a themescope() result, per community. A low coverage means the
Concreteness Score (CS) is computed on few edges and may be unreliable; this
typically happens when the corpus language does not match the lexicon (the
bundled brysbaert norms cover English words only).
lexicon_coverage(x, lexicon = brysbaert)lexicon_coverage(x, lexicon = brysbaert)
x |
Either a |
lexicon |
Data frame with columns |
A data frame with columns community (community id, or "(all)" for
the overall row), n_terms, n_matched and coverage (proportion in
).
compute_cs(), match_concreteness().
lex <- data.frame(word = c("dog", "cat"), conc.m = c(4.8, 4.7)) lexicon_coverage(c("dog", "cat", "freedom"), lex)lex <- data.frame(word = c("dog", "cat"), conc.m = c(4.8, 4.7)) lexicon_coverage(c("dog", "cat", "freedom"), lex)
Looks up concreteness ratings for a set of terms. Matching is case-insensitive.
match_concreteness(terms, lexicon = brysbaert)match_concreteness(terms, lexicon = brysbaert)
terms |
Character vector of terms to look up. |
lexicon |
Data frame with columns |
Named numeric vector of concreteness values aligned to terms. Terms
not found in the lexicon receive NA.
lex <- data.frame(word = c("dog", "cat", "freedom"), conc.m = c(4.8, 4.7, 1.5)) match_concreteness(c("dog", "freedom", "unknown"), lex)lex <- data.frame(word = c("dog", "cat", "freedom"), conc.m = c(4.8, 4.7, 1.5)) match_concreteness(c("dog", "freedom", "unknown"), lex)
Re-weights raw co-occurrence counts with one of several similarity measures
commonly used in co-word analysis. Given the co-occurrence count
and the term presences :
"association", Association Strength
(ThemeScope default; van Eck & Waltman).
"equivalence", equivalence index.
"jaccard".
"salton", cosine or Salton's measure.
"inclusion".
"frequency"the raw counts, unchanged.
normalize_cooccurrence( cooc_matrix, presence, method = c("association", "equivalence", "jaccard", "salton", "inclusion", "frequency") )normalize_cooccurrence( cooc_matrix, presence, method = c("association", "equivalence", "jaccard", "salton", "inclusion", "frequency") )
cooc_matrix |
Symmetric sparse |
presence |
Named numeric vector of term presences |
method |
One of |
A sparse dgCMatrix of the same dimensions as cooc_matrix.
m <- Matrix::sparseMatrix( i = c(1, 2), j = c(2, 1), x = c(3, 3), dims = c(3, 3), dimnames = list(c("a", "b", "c"), c("a", "b", "c")) ) normalize_cooccurrence(m, c(a = 5, b = 4, c = 2), method = "jaccard")m <- Matrix::sparseMatrix( i = c(1, 2), j = c(2, 1), x = c(3, 3), dims = c(3, 3), dimnames = list(c("a", "b", "c"), c("a", "b", "c")) ) normalize_cooccurrence(m, c(a = 5, b = 4, c = 2), method = "jaccard")
Draws the co-occurrence network with igraph: nodes are coloured by
community using the shared pastel palette (matching plot_themescope()),
edges are light grey, node size scales with degree, and the highest-degree
terms of each community are labelled.
plot_network( graph, membership, palette = NULL, layout = "fr", top_n_labels = 8, edge_color = "grey85", repulsion = 0, seed = NULL, ... )plot_network( graph, membership, palette = NULL, layout = "fr", top_n_labels = 8, edge_color = "grey85", repulsion = 0, seed = NULL, ... )
graph |
An |
membership |
Named integer vector of community assignments from
|
palette |
Optional vector of community colours (community |
layout |
Character layout: |
top_n_labels |
Integer. Highest-degree nodes per community to label
(default |
edge_color |
Colour of the edges (default light grey). |
repulsion |
Numeric in |
seed |
Optional integer seed for a reproducible layout. |
... |
Passed to |
Invisibly NULL; called for the plot it draws.
words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) plot_network(result$graph, result$membership, top_n_labels = 3, seed = 1)words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) plot_network(result$graph, result$membership, top_n_labels = 3, seed = 1)
Produces the two-dimensional strategic diagram locating each community in the
space defined by z-scored PSI (x-axis, anchoring) and z-scored CS (y-axis,
objectification). Points are coloured by community with the shared pastel
palette (so colours match plot_network()); the four SRT quadrants are
annotated in the corners. The only legend is community size (number of terms),
placed at the bottom.
plot_themescope( psi, cs, community_labels = NULL, community_sizes = NULL, title = "ThemeScope Map", palette = NULL, quadrant_fill = FALSE, ... )plot_themescope( psi, cs, community_labels = NULL, community_sizes = NULL, title = "ThemeScope Map", palette = NULL, quadrant_fill = FALSE, ... )
psi |
Named numeric vector of PSI values (one per community). |
cs |
Named numeric vector of CS values (one per community); may contain
|
community_labels |
Optional named character vector of point labels (e.g.
top terms per community). If |
community_sizes |
Optional named numeric vector of community sizes (the number of terms in each community) used to scale point area. |
title |
Character. Plot title (default |
palette |
Optional vector of community colours (one per community, in the
order of |
quadrant_fill |
Logical (default |
... |
Currently unused. |
A ggplot object.
plot_themescope( psi = c(C1 = 1.2, C2 = -0.5, C3 = 0.3), cs = c(C1 = 0.8, C2 = -1.1, C3 = 0.2) )plot_themescope( psi = c(C1 = 1.2, C2 = -0.5, C3 = 0.3), cs = c(C1 = 0.8, C2 = -1.1, C3 = 0.2) )
Tokenises, lemmatises and POS-tags a collection of raw documents using a
udpipe language model, returning a tokens data frame ready for
themescope() and the rest of the backend.
preprocess_texts( collection, model, text_col = "text", doc_id_col = "doc_id", batch_size = 500, parallel_cores = 1, verbose = TRUE )preprocess_texts( collection, model, text_col = "text", doc_id_col = "doc_id", batch_size = 500, parallel_cores = 1, verbose = TRUE )
collection |
Data frame with a text column and a document-id column,
typically the output of |
model |
A udpipe model object, a path to a |
text_col |
Name of the text column (default |
doc_id_col |
Name of the document-id column (default |
batch_size |
Integer. Documents processed per batch (default |
parallel_cores |
Integer (default |
verbose |
Logical. Print progress messages (default |
The complete udpipe annotation as a data frame (one row per
token), with all columns returned by udpipe::udpipe_annotate() preserved
(doc_id, paragraph_id, sentence_id, sentence, token_id, token,
lemma, upos, xpos, feats, head_token_id, dep_rel, ...). Nothing
is dropped, so the full linguistic annotation is available for inspection.
Downstream functions (build_vocab(), build_cooccurrence_matrix()) select
and case-fold the relevant word column (token or lemma) themselves, and
use doc_id + sentence_id as the sentence key.
# The shape of the annotation this returns; the bundled demo corpus ships # pre-annotated, so no model download is needed to inspect it words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) head(words[, c("doc_id", "sentence_id", "token", "lemma", "upos")]) # Annotating your own corpus. The language model is downloaded once and # cached; here it goes to the session temporary directory instead. coll <- read_collection(system.file("extdata", "sample_collection.csv", package = "themescopeR")) model <- try(ts_download_model("english", model_dir = tempdir()), silent = TRUE) if (!inherits(model, "try-error")) { tokens <- preprocess_texts(utils::head(coll, 5), model = model) head(tokens[, c("doc_id", "token", "lemma", "upos")]) }# The shape of the annotation this returns; the bundled demo corpus ships # pre-annotated, so no model download is needed to inspect it words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) head(words[, c("doc_id", "sentence_id", "token", "lemma", "upos")]) # Annotating your own corpus. The language model is downloaded once and # cached; here it goes to the session temporary directory instead. coll <- read_collection(system.file("extdata", "sample_collection.csv", package = "themescopeR")) model <- try(ts_download_model("english", model_dir = tempdir()), silent = TRUE) if (!inherits(model, "try-error")) { tokens <- preprocess_texts(utils::head(coll, 5), model = model) head(tokens[, c("doc_id", "token", "lemma", "upos")]) }
Imports a corpus of raw documents from a single file or a zip archive of many
files, returning a tidy data frame with a doc_id and a text column (plus
any metadata columns present in the source).
read_collection(path, text_col = NULL, id_col = NULL, sequential_ids = FALSE)read_collection(path, text_col = NULL, id_col = NULL, sequential_ids = FALSE)
path |
Path to a single file or a |
text_col |
Optional name of the text column. If |
id_col |
Optional name of the document-id column. If |
sequential_ids |
Logical (default |
Supported single-file formats: .csv, .tsv, .txt, .xlsx/.xls, and
.RData/.rda. A .zip may contain any number of these (e.g. many .csv
or many .txt); all are read and row-bound. For .txt files, each file is
treated as one document (its doc_id is the file name); this makes a zip of
.txt files a natural multi-document collection.
A data frame with doc_id (character) and text (character) as the
first two columns, followed by any remaining source columns. Duplicated ids
are made unique with a warning.
# The bundled 1000-document sample path <- system.file("extdata", "sample_collection.csv", package = "themescopeR") coll <- read_collection(path) head(coll$doc_id, 3) # Readable ids; the identifier from the file survives as `source_id` coll <- read_collection(path, sequential_ids = TRUE) head(coll[, c("doc_id", "source_id")], 3) # A zip holding many .txt files, one document each d <- file.path(tempdir(), "texts") dir.create(d, showWarnings = FALSE) writeLines("the cat sat on the mat", file.path(d, "doc1.txt")) writeLines("the dog ran in the park", file.path(d, "doc2.txt")) z <- file.path(tempdir(), "texts.zip") utils::zip(z, files = list.files(d, full.names = TRUE), flags = "-qj") read_collection(z) unlink(c(d, z), recursive = TRUE)# The bundled 1000-document sample path <- system.file("extdata", "sample_collection.csv", package = "themescopeR") coll <- read_collection(path) head(coll$doc_id, 3) # Readable ids; the identifier from the file survives as `source_id` coll <- read_collection(path, sequential_ids = TRUE) head(coll[, c("doc_id", "source_id")], 3) # A zip holding many .txt files, one document each d <- file.path(tempdir(), "texts") dir.create(d, showWarnings = FALSE) writeLines("the cat sat on the mat", file.path(d, "doc1.txt")) writeLines("the dog ran in the park", file.path(d, "doc2.txt")) z <- file.path(tempdir(), "texts.zip") utils::zip(z, files = list.files(d, full.names = TRUE), flags = "-qj") read_collection(z) unlink(c(d, z), recursive = TRUE)
.themescope archiveLoads an analysis written by save_themescope(). Use summary() on the
result for a table describing what the file contains, and archive$result
for the themescope object itself (which plots, prints and coerces to a data
frame as usual).
read_themescope(file) ## S3 method for class 'themescope_archive' print(x, ...) ## S3 method for class 'themescope_archive' summary(object, ...)read_themescope(file) ## S3 method for class 'themescope_archive' print(x, ...) ## S3 method for class 'themescope_archive' summary(object, ...)
file |
Path to a |
x |
A |
... |
Ignored. |
object |
A |
An object of class themescope_archive: a list with result (the
themescope object), words, collection, terms, communities,
meta, created, package_version and version.
print(themescope_archive): Print a one-screen overview of the archive.
summary(themescope_archive): Describe the archive as a two-column data frame
(field, value), covering the corpus, the parameters of the run, the
vocabulary, the network and the lexicon. This is what the Shiny import
window displays.
words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) f <- file.path(tempdir(), "demo.themescope") save_themescope(result, f) archive <- read_themescope(f) summary(archive) archive$result unlink(f)words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) f <- file.path(tempdir(), "demo.themescope") save_themescope(result, f) archive <- read_themescope(f) summary(archive) archive$result unlink(f)
.themescope archiveWrites a finished analysis, together with the data it was computed from, to a
single .themescope file. The archive is a compressed RDS holding the
themescope object, the annotated words it was built on, the raw collection
(optional), the term-level table with the scores used to draw the maps, the
community-level table, and the parameters of the run.
Reopening the archive with read_themescope() restores maps, communities and
top terms without re-annotating the corpus or re-running the pipeline.
save_themescope( x, file, words = NULL, collection = NULL, meta = list(), compress = "xz" )save_themescope( x, file, words = NULL, collection = NULL, meta = list(), compress = "xz" )
x |
A |
file |
Destination path. The extension |
words |
Optional annotated words data frame (the |
collection |
Optional raw document collection (the |
meta |
Optional named list of extra information to record, for example
|
compress |
Compression passed to |
The path of the written file, invisibly.
read_themescope() to load an archive back.
words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) f <- file.path(tempdir(), "demo.themescope") save_themescope(result, f, meta = list(language = "english", lexicon = "Brysbaert")) summary(read_themescope(f)) unlink(f) # Store the annotated corpus alongside the results, so the archive can be # re-analysed later with different parameters (slower: it compresses 60k rows) g <- file.path(tempdir(), "demo-with-corpus.themescope") save_themescope(result, g, words = words) summary(read_themescope(g)) unlink(g)words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) f <- file.path(tempdir(), "demo.themescope") save_themescope(result, f, meta = list(language = "english", lexicon = "Brysbaert")) summary(read_themescope(f)) unlink(f) # Store the annotated corpus alongside the results, so the archive can be # re-analysed later with different parameters (slower: it compresses 60k rows) g <- file.path(tempdir(), "demo-with-corpus.themescope") save_themescope(result, g, words = words) summary(read_themescope(g)) unlink(g)
Implements the ThemeScope case-study term-relevance measure (Eq. 4), which combines lexical salience with the balance between a term's internal and external connectivity:
where is term presence, the total association
strength linking to terms within its own community, and
the total linking it to terms outside. Higher values identify
terms that are both frequent and predominantly embedded within their
community, downweighting generic terms shared across clusters.
term_relevance(graph, membership, presence)term_relevance(graph, membership, presence)
graph |
An undirected weighted |
membership |
Named integer vector of community assignments ( |
presence |
Named numeric vector of term presence |
A data frame with columns term, community (e.g. "C1"),
relevance, degree, presence, ordered by community then decreasing
relevance. Unassigned vertices are dropped.
words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) rel <- term_relevance(result$graph, result$membership, result$presence) head(rel)words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) rel <- term_relevance(result$graph, result$membership, result$presence) head(rel)
Executes the complete ThemeScope workflow in a single call. Accepts either
an annotated words data frame or a raw document collection (in which case
it is annotated with udpipe via preprocess_texts() first). The steps:
(Optional) annotate raw texts into words.
Build the vocabulary from POS-filtered words (build_vocab()).
Build the co-occurrence matrix + term presence, with the chosen
normalization (build_cooccurrence_matrix()).
Construct a thresholded network (build_cooccurrence_network()).
Detect communities (walktrap / louvain / leiden).
Compute PSI (anchoring) and CS (objectification) per community.
Collect network statistics.
themescope( data, model = NULL, text_col = "text", doc_id_col = "doc_id", unit = c("lemma", "token"), concreteness_lexicon = brysbaert, vocab_size = 1500, pos_filter = c("NOUN", "ADJ", "PROPN"), window = c("sentence", "document"), normalization = "association", threshold_percentile = 0.98, community_algorithm = "walktrap", walktrap_steps = 4, resolution = 1, min_community_size = 10, seed = NULL, verbose = TRUE ) ## S3 method for class 'themescope' print(x, ...) ## S3 method for class 'themescope' summary(object, ...) ## S3 method for class 'themescope' plot( x, type = c("map", "network"), label = c("id", "terms"), label_by = c("relevance", "frequency", "degree"), n_label_terms = 3, ... ) ## S3 method for class 'themescope' as.data.frame(x, ...)themescope( data, model = NULL, text_col = "text", doc_id_col = "doc_id", unit = c("lemma", "token"), concreteness_lexicon = brysbaert, vocab_size = 1500, pos_filter = c("NOUN", "ADJ", "PROPN"), window = c("sentence", "document"), normalization = "association", threshold_percentile = 0.98, community_algorithm = "walktrap", walktrap_steps = 4, resolution = 1, min_community_size = 10, seed = NULL, verbose = TRUE ) ## S3 method for class 'themescope' print(x, ...) ## S3 method for class 'themescope' summary(object, ...) ## S3 method for class 'themescope' plot( x, type = c("map", "network"), label = c("id", "terms"), label_by = c("relevance", "frequency", "degree"), n_label_terms = 3, ... ) ## S3 method for class 'themescope' as.data.frame(x, ...)
data |
Either an annotated words data frame (columns |
model |
Required only when |
text_col, doc_id_col
|
Column names used when annotating a raw collection. |
unit |
Word unit for the vocabulary: |
concreteness_lexicon |
Data frame with columns |
vocab_size |
Integer or |
pos_filter |
Character vector of Universal POS tags (default
|
window |
Co-occurrence unit: |
normalization |
Similarity measure for the co-occurrence matrix:
|
threshold_percentile |
Numeric in |
community_algorithm |
|
walktrap_steps |
Integer (default |
resolution |
Numeric resolution for louvain/leiden (default |
min_community_size |
Integer (default |
seed |
Optional integer seed for reproducible community detection. |
verbose |
Logical. Print progress messages (default |
x |
A |
... |
Ignored. |
object |
A |
type |
Character. |
label |
Map point labels: |
label_by |
Ranking used to pick the terms when |
n_label_terms |
Integer. Number of terms per label when |
An S3 object of class "themescope" with elements graph,
communities, membership, psi, cs, presence, network_stats,
vocab (a build_vocab() data frame), params, and call.
print(themescope): Print a concise summary of a themescope object.
summary(themescope): Detailed summary with a community statistics table.
plot(themescope): Plot a themescope object (type = "map" or
"network"). With label = "terms", communities on the map are labelled
with their most representative terms (up to n_label_terms), chosen with
the label_by method, instead of the community id; communities keep the
same colour in both views.
as.data.frame(themescope): Coerce to a tidy community-level data frame (raw and
z-scored metrics plus quadrant).
# The bundled demo corpus is shipped already tokenised and POS tagged words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) result as.data.frame(result) # Straight from raw texts: annotated internally, which downloads a udpipe # language model on first use (kept in the session temporary directory here) coll <- read_collection(system.file("extdata", "sample_collection.csv", package = "themescopeR")) model <- try(ts_download_model("english", model_dir = tempdir()), silent = TRUE) if (!inherits(model, "try-error")) { res <- themescope(utils::head(coll, 100), model = model, vocab_size = 100, threshold_percentile = 0.9, min_community_size = 3, seed = 1, verbose = FALSE) plot(res, type = "map") }# The bundled demo corpus is shipped already tokenised and POS tagged words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) result as.data.frame(result) # Straight from raw texts: annotated internally, which downloads a udpipe # language model on first use (kept in the session temporary directory here) coll <- read_collection(system.file("extdata", "sample_collection.csv", package = "themescopeR")) model <- try(ts_download_model("english", model_dir = tempdir()), silent = TRUE) if (!inherits(model, "try-error")) { res <- themescope(utils::head(coll, 100), model = model, vocab_size = 100, threshold_percentile = 0.9, min_community_size = 3, seed = 1, verbose = FALSE) plot(res, type = "map") }
Opens the optional graphical interface to the ThemeScope pipeline. The app is
a thin front-end: it delegates all computation to the exported package
functions (read_collection(), preprocess_texts(), themescope(),
top_terms(), save_themescope(), read_themescope()), so the GUI
reproduces the console results exactly.
themescope_app(display_mode = "normal", max_upload_size_mb = NULL, ...)themescope_app(display_mode = "normal", max_upload_size_mb = NULL, ...)
display_mode |
Passed to |
max_upload_size_mb |
Optional numeric. Baseline maximum file-upload size
(in megabytes) for the GUI. Shiny's own default is only 5 MB, far too small
for typical ThemeScope collections, so the app already raises this to 50 MB.
Set a larger value here to lift the baseline further from R (e.g. |
... |
Further arguments passed to |
The interface follows the pipeline step by step: import (raw text, a saved
.themescope archive, or the bundled demo corpus, which ships already
annotated), preprocess, run, and refine the look of the network without
recomputing anything. Results are shown as an interactive map and network, a
filterable community table and per-cluster term lists, and the whole study can
be exported as a .themescope archive.
The GUI requires a few additional packages declared in Suggests:
shiny, bslib, DT, plotly, visNetwork and
shinycssloaders. Install any that are missing with
install.packages().
Called for its side effect of launching the app; does not return a useful value.
if (interactive()) { themescope_app() }if (interactive()) { themescope_app() }
Returns the directory where downloaded udpipe language models are
cached, so a model is fetched only once and reused across analyses. Defaults
to tools::R_user_dir("themescopeR", "cache"), the location R reserves for
package caches; override it with options(themescopeR.model_dir = "/path").
themescope_cache_dir(create = FALSE)themescope_cache_dir(create = FALSE)
create |
Logical. Create the directory if it does not exist
(default |
Asking where the cache is does not create anything: the directory is
created only when a model is actually downloaded, that is when you call
ts_download_model() or hand a language name to preprocess_texts().
Nothing else in the package writes outside the session temporary directory.
Use ts_clear_cache() to delete what has been cached.
A character path to the cache directory.
ts_clear_cache() to remove cached models,
ts_download_model() to populate the cache.
# Where models would be cached; this call creates nothing themescope_cache_dir()# Where models would be cached; this call creates nothing themescope_cache_dir()
Returns the built-in palette of 40 pastel colours used to colour communities
consistently across plot_themescope() and plot_network().
themescope_colours(n = NULL)themescope_colours(n = NULL)
n |
Optional number of colours to return (recycled if |
A character vector of hex colours.
themescope_colours(5)themescope_colours(5)
Returns the highest-ranked terms of each community, used to label and interpret the thematic clusters. Terms can be ranked by three criteria:
"relevance" (default)the ThemeScope case-study relevance measure
(term_relevance()), which combines lexical salience with
a term's internal-versus-external connectivity. This is the labelling
method described in the ThemeScope case study: it downweights generic,
high-frequency terms that co-occur across several clusters and favours
terms that are both frequent and structurally embedded in their own
community.
"frequency"the term presence (the number of sentences,
or documents, in which the term occurs), the salience component of
on its own, i.e. plain frequency-based labelling.
"degree"the network degree of the term (number of co-occurrence links).
top_terms(x, n = 10, by = c("relevance", "frequency", "degree"))top_terms(x, n = 10, by = c("relevance", "frequency", "degree"))
x |
A |
n |
Integer. Number of terms to return per community (default |
by |
Ranking criterion: |
A data frame with columns community, rank, term, relevance,
frequency (term presence ) and degree, ordered by community and
then by the chosen ranking criterion (decreasing).
term_relevance() for the relevance measure .
words <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) top_terms(result, n = 5) # by relevance R_t (default) top_terms(result, n = 5, by = "frequency") # by term presence a_twords <- readRDS(system.file("extdata", "demo_annotated.rds", package = "themescopeR")) result <- themescope(words, vocab_size = 300, seed = 1, verbose = FALSE) top_terms(result, n = 5) # by relevance R_t (default) top_terms(result, n = 5, by = "frequency") # by term presence a_t
Removes the models and the registry downloaded by ts_download_model() and
ts_list_models(), and the cache directory itself. Nothing else in the
package writes persistently, so this clears everything themescopeR has ever
stored on disk.
ts_clear_cache(model_dir = NULL, ask = interactive())ts_clear_cache(model_dir = NULL, ask = interactive())
model_dir |
Directory to clear. Defaults to |
ask |
Logical. Ask for confirmation before deleting. Defaults to
|
The paths that were removed, invisibly (character(0) if the cache
was empty or the user declined).
themescope_cache_dir(), ts_download_model().
# Point the cache at a throwaway directory and clear it again d <- file.path(tempdir(), "themescope-cache-example") dir.create(d, showWarnings = FALSE) file.create(file.path(d, "example-ud-2.15.udpipe")) ts_clear_cache(model_dir = d, ask = FALSE) dir.exists(d)# Point the cache at a throwaway directory and clear it again d <- file.path(tempdir(), "themescope-cache-example") dir.create(d, showWarnings = FALSE) file.create(file.path(d, "example-ud-2.15.udpipe")) ts_clear_cache(model_dir = d, ask = FALSE) dir.exists(d)
Downloads a Universal Dependencies 2.15 udpipe model from the TALL
language-model collection into themescope_cache_dir(). If the model is
already cached it is not re-downloaded, so models are fetched only once
and reused across analyses.
ts_download_model( language, treebank = NULL, model_dir = NULL, overwrite = FALSE )ts_download_model( language, treebank = NULL, model_dir = NULL, overwrite = FALSE )
language |
Character language name (e.g. |
treebank |
Optional treebank name (e.g. |
model_dir |
Directory to download into. If |
overwrite |
Logical. Re-download even if cached (default |
The updated models and lexicons are curated and maintained by
Massimo Aria in the tall.language.models repository, part of the
TALL project. We gratefully acknowledge that work
and redistribute nothing here: models are fetched on demand from the source
repository.
To explore which models are available, together with their treebanks,
contributors, descriptions, corpus sizes and Universal Dependencies hub
pages, use ts_list_models() (whose returned data frame includes
description and hub_page_link columns), or browse the repository directly:
https://github.com/massimoaria/tall.language.models.
The path to the cached .udpipe model file (invisibly).
Aria, M. tall.language.models: updated UDPipe models and lexicons for TALL. https://github.com/massimoaria/tall.language.models
ts_list_models() for the full catalogue (descriptions and links),
ts_clear_cache() to delete the downloaded models.
# Downloaded into the session temporary directory, so nothing persists path <- try(ts_download_model("english", model_dir = tempdir()), silent = TRUE) if (!inherits(path, "try-error")) basename(path)# Downloaded into the session temporary directory, so nothing persists path <- try(ts_download_model("english", model_dir = tempdir()), silent = TRUE) if (!inherits(path, "try-error")) basename(path)
Retrieves the registry of updated udpipe models (Universal Dependencies
2.15) maintained for TALL. The registry is downloaded once, cached in
themescope_cache_dir() and reused on later calls; the cache directory is
created only at that point.
ts_list_models(refresh = FALSE, cache_dir = NULL)ts_list_models(refresh = FALSE, cache_dir = NULL)
refresh |
Logical. Force a re-download of the registry (default |
cache_dir |
Directory to keep the registry in. If |
A data frame with one row per model, including language_name,
treebank and file (the model code used to build the file name).
# Needs an internet connection the first time; cached afterwards. Kept in the # session temporary directory here, so nothing persists. models <- try(ts_list_models(cache_dir = tempdir()), silent = TRUE) if (!inherits(models, "try-error")) { head(models[, c("language_name", "treebank")]) }# Needs an internet connection the first time; cached afterwards. Kept in the # session temporary directory here, so nothing persists. models <- try(ts_list_models(cache_dir = tempdir()), silent = TRUE) if (!inherits(models, "try-error")) { head(models[, c("language_name", "treebank")]) }
Returns the path to a cached model without downloading anything and without creating any directory.
ts_model_path(language, treebank = NULL, model_dir = NULL)ts_model_path(language, treebank = NULL, model_dir = NULL)
language |
Character language name (e.g. |
treebank |
Optional treebank name (e.g. |
model_dir |
Directory to download into. If |
The path to the cached .udpipe file, or NA if it is not cached.
# NA until the model has been downloaded (needs the catalogue, hence a # connection on first use) p <- try(ts_model_path("english"), silent = TRUE) if (!inherits(p, "try-error")) p# NA until the model has been downloaded (needs the catalogue, hence a # connection on first use) p <- try(ts_model_path("english"), silent = TRUE) if (!inherits(p, "try-error")) p
Checks that an annotated data frame has all columns required by the
ThemeScope backend. The "word" used downstream is either the token or the
lemma column (chosen via the unit argument of build_vocab() /
build_cooccurrence_matrix()), so at least one of them must be present.
Emits informative errors via cli when columns are missing.
validate_words_df(words_df)validate_words_df(words_df)
words_df |
Data frame to validate. Must contain |
Invisibly returns TRUE if validation passes.
df <- data.frame(doc_id = 1, sentence_id = 1, token = "dog", upos = "NOUN") validate_words_df(df)df <- data.frame(doc_id = 1, sentence_id = 1, token = "dog", upos = "NOUN") validate_words_df(df)
Computes standardised (z-scored) values, handling NAs gracefully.
zscore(x)zscore(x)
x |
Numeric vector to standardise. |
Numeric vector of z-scores. NA values in the input remain NA.
Returns a vector of NAs (with a warning) if the standard deviation is zero.
zscore(c(1, 2, 3, 4, 5)) zscore(c(1, NA, 3))zscore(c(1, 2, 3, 4, 5)) zscore(c(1, NA, 3))