A corpus of abstracts shares vocabulary unevenly: a word like
telescope binds a few documents tightly, while
challenges touches half the corpus weakly. A weighted
hypergraph represents that structure directly — documents are vertices
and every word is a hyperedge over the documents it occurs in, weighted
by tf-idf. Two questions drive this vignette, asked of the bundled
covid_abstracts corpus (165 COVID-19 education-research
abstracts, 2020–2024): do the abstracts organize into coherent groups
purely through shared vocabulary, and can a handful of labeled abstracts
classify the rest?
The methods are Zhou, Huang & Schölkopf’s (2006) hypergraph
Laplacian and Hayashi et al.’s (2020) random-walk Laplacian with
edge-dependent vertex weights, the spectral engines of hypernets’
hypergraph family (HyperNetX-parity verified), applied to a text
hypergraph built by text_hypergraph().
From corpus to hypergraph
text_hypergraph() tokenizes deterministically in base R,
counts document–word occurrences, and hands the weighted incidence
structure to group_hypergraph(). Function words and a few
corpus-boilerplate terms are excluded, and words in fewer than three
abstracts are dropped — a word seen once cannot connect anything.
stops <- c(
stop_words_en(),
"using", "used", "use", "based", "results", "study", "research",
"paper", "article", "findings", "data"
)
hg <- text_hypergraph(
covid_abstracts,
column = "abstract",
id = "doc",
weight = "tfidf",
stop_words = stops,
min_count = 3L
)
#> vocabulary pruned to 1453 of 4032 words, retaining 81.71% of tokens
hg
#> Text hypergraph: 165 documents, 1453 words (documents as nodes, weight = tfidf)
#> Hyperedges: 1453 (words); sizes 1-140, median 4
#> doc word count weight
#> 2-s2.0-85085897904 access 1 3.472930
#> 2-s2.0-85085897904 affects 2 9.005100
#> 2-s2.0-85085897904 alternative 1 4.166078
#> 2-s2.0-85085897904 american 1 3.809403
#> 2-s2.0-85085897904 association 1 3.547038
#> 2-s2.0-85085897904 available 1 4.032546
#> 2-s2.0-85085897904 awareness 1 4.166078
#> 2-s2.0-85085897904 cannot 1 4.166078
#> 2-s2.0-85085897904 care 1 3.714093
#> 2-s2.0-85085897904 caused 1 3.914763
#> ... 10372 more rowsThe corpus becomes 165 vertices connected by 1,453 word-hyperedges. The median word touches 4 abstracts; the largest hyperedge spans 140 of the 165 — a near-universal term that tf-idf weighting correspondingly down-weights.
hg_measures(hg, what = "summary")
#> measure value
#> 1 n_nodes 1.650000e+02
#> 2 n_hyperedges 1.453000e+03
#> 3 density 4.330434e-02
#> 4 avg_edge_size 7.145217e+00
#> 5 pairwise_participation 9.957132e-01A pairwise participation of 0.996 says nearly every pair of abstracts shares at least one word: the hypergraph is one dense component, so spectral methods apply to the whole corpus at once.
Which words hold the corpus together?
Reversing the orientation makes words the vertices — two words are close when they co-occur in many documents — and centrality then ranks the corpus’s organizing vocabulary.
word_hg <- text_hypergraph(
covid_abstracts,
column = "abstract",
id = "doc",
nodes = "word",
weight = "tfidf",
stop_words = stops,
min_count = 3L
)
#> vocabulary pruned to 1453 of 4032 words, retaining 81.71% of tokens
hg_centrality(word_hg, type = "clique", sort_by = "clique", n = 10)
#> node clique
#> 1 covid 0.2798710
#> 2 pandemic 0.2707863
#> 3 education 0.2491869
#> 4 students 0.1920792
#> 5 learning 0.1906239
#> 6 online 0.1705769
#> 7 teaching 0.1651923
#> 8 educational 0.1158119
#> 9 teachers 0.1084348
#> 10 challenges 0.1079189The ranking is the corpus in miniature: covid, pandemic, education, students, learning, online, teaching. This is remote-education research, and the centrality ranking recovers that without reading a single abstract.
Do the abstracts cluster?
hg_cluster() with type = "random_walk" is
the Hayashi et al. (2020) pipeline: the tf-idf weights become
edge-dependent vertex weights of a random walk, whose normalized
Laplacian is embedded and clustered. The number of clusters is explicit
by design; four is the working choice here and is checked below.
clusters <- hg_cluster(hg, k = 4, type = "random_walk", seed = 1)
aggregate(node ~ cluster, data = clusters, FUN = length)
#> cluster node
#> 1 Cluster 1 16
#> 2 Cluster 2 44
#> 3 Cluster 3 29
#> 4 Cluster 4 76
merged <- merge(clusters, covid_abstracts, by.x = "node", by.y = "doc")
aggregate(year ~ cluster, data = merged, FUN = mean)
#> cluster year
#> 1 Cluster 1 2021.062
#> 2 Cluster 2 2021.682
#> 3 Cluster 3 2021.345
#> 4 Cluster 4 2021.711The partition is 16/44/29/76, and every cluster’s mean publication year sits between 2021.1 and 2021.7: the clusters are thematic, not temporal. The vocabulary of this literature separates by topic while staying stable across the pandemic years.
Is k = 4 doing real work? Refitting at
k = 3 answers the sensitivity question:
clusters_3 <- hg_cluster(hg, k = 3, type = "random_walk", seed = 1)
hg_agreement(clusters, clusters_3, what = "table")
#> label_x label_y n
#> 1 Cluster 1 Cluster 1 15
#> 2 Cluster 1 Cluster 3 1
#> 3 Cluster 2 Cluster 1 1
#> 4 Cluster 2 Cluster 2 37
#> 5 Cluster 2 Cluster 3 6
#> 6 Cluster 3 Cluster 3 29
#> 7 Cluster 4 Cluster 1 22
#> 8 Cluster 4 Cluster 2 36
#> 9 Cluster 4 Cluster 3 18The two compact themes survive the change almost intact (15 of 16, and all 29, stay together), while the largest cluster redistributes. The small clusters are stable structure worth interpreting; the large one is the corpus’s residual mass, and conclusions should not lean on its boundary.
Classifying from six labels
Transductive label spreading (Zhou et al. 2006) takes labels for a
few vertices and propagates them over the hyperedges. Labeling the three
alphabetically first abstracts of 2020 as "early" and of
2024 as "late" asks whether pandemic-era vocabulary dates
an abstract:
labels <- c(
"2-s2.0-85085897904" = "early",
"2-s2.0-85087124472" = "early",
"2-s2.0-85087646998" = "early",
"2-s2.0-85107489479" = "late",
"2-s2.0-85112557152" = "late",
"2-s2.0-85113355756" = "late"
)
predictions <- hg_classify(hg, labels = labels)
predicted_years <- merge(predictions, covid_abstracts,
by.x = "node", by.y = "doc")
aggregate(year ~ predicted, data = predicted_years, FUN = mean)
#> predicted year
#> 1 early 2020.000
#> 2 late 2021.605
hg_agreement(predictions, covid_abstracts,
node = c("node", "doc"), label = c("predicted", "year"),
what = "table")
#> label_x label_y n
#> 1 early 2020 3
#> 2 late 2020 37
#> 3 late 2021 40
#> 4 late 2022 40
#> 5 late 2023 40
#> 6 late 2024 5The classifier is conservative and correct where it commits: every
abstract it calls "early" is genuinely from 2020, but it
finds only three of them — the rest of the corpus, 2020 included, reads
as "late". The decisive reading: this literature’s
vocabulary converged almost immediately, so publication year is weakly
encoded in word choice. Label spreading here is trustworthy for what it
asserts and honest about how little the structure supports.
Ranking, dualizing, and testing the structure
Three verbs complete the analysis layer. hg_pagerank()
ranks vertices by the stationary importance of the Chitra & Raphael
(2019) random walk with edge-dependent vertex weights – the walk that
actually uses the tf-idf weights (verified against the HyperNetX
reference implementation at machine precision):
hg_pagerank(hg, sort_by = "pagerank", n = 3)
#> node pagerank
#> 1 2-s2.0-85124537994 0.01648602
#> 2 2-s2.0-85116687295 0.01258984
#> 3 2-s2.0-85115059168 0.01231774The top-ranked abstracts are the corpus’s connective tissue –
documents whose vocabulary reaches everywhere. A
personalized = vector turns the same verb into “importance
from the perspective of these documents”. dual_hypergraph()
swaps the vertex and hyperedge roles of a fitted object, so the
word-level view never requires re-tokenizing:
dual_hypergraph(hg)
#> Text hypergraph: 165 documents, 1453 words (words as nodes, weight = tfidf)
#> Hyperedges: 165 (documents); sizes 22-121, median 63
#> doc word count weight
#> 2-s2.0-85085897904 access 1 3.472930
#> 2-s2.0-85085897904 affects 2 9.005100
#> 2-s2.0-85085897904 alternative 1 4.166078
#> 2-s2.0-85085897904 american 1 3.809403
#> 2-s2.0-85085897904 association 1 3.547038
#> 2-s2.0-85085897904 available 1 4.032546
#> 2-s2.0-85085897904 awareness 1 4.166078
#> 2-s2.0-85085897904 cannot 1 4.166078
#> 2-s2.0-85085897904 care 1 3.714093
#> 2-s2.0-85085897904 caused 1 3.914763
#> ... 10372 more rowsFinally, hg_null_test() asks whether observed structure
exceeds what the degree sequences alone imply: it rewires the binary
membership by degree-preserving checkerboard swaps (Gotelli 2000) and
compares. On the 2020 abstracts:
abstracts_2020 <- text_hypergraph(
subset(covid_abstracts, year == 2020),
column = "abstract",
id = "doc",
weight = "tfidf",
stop_words = stops,
min_count = 3L
)
#> vocabulary pruned to 349 of 1641 words, retaining 56.92% of tokens
hg_null_test(
abstracts_2020,
statistic = c("pairwise_participation", "avg_jaccard"),
n = 99,
seed = 1
)
#> statistic observed null_mean null_lo null_hi z
#> 1 pairwise_participation 0.98846154 0.99109039 0.98647436 0.99487179 -1.045800
#> 2 avg_jaccard 0.05659397 0.05583251 0.05519231 0.05634021 2.483364
#> p_value n method
#> 1 0.36 99 swap
#> 2 0.01 99 swapRead the two rows together: near-universal pairwise participation (0.988) is not evidence of structure – the null reproduces it (p = 0.36), because vocabulary-rich abstracts connect to everything under any degree-preserving rewiring. The mean edge Jaccard is significant (z = 2.48, p = 0.01): words repeat each other’s document combinations more than their frequencies require, which is exactly the topical co-occurrence the clustering exploits. The decisive practice: never report a structural statistic without its null interval.
Using an existing quanteda or tidytext pipeline
text_hypergraph() needs no NLP dependency, but any long
document–word table is already the incidence structure, so existing
pipelines plug into the same engines directly:
# quanteda: tidytext::tidy() turns a dfm into a long table with columns
# document, term, count (verified against quanteda 4.x / tidytext 0.4.x)
group_hypergraph(
tidytext::tidy(my_dfm),
actor = "term", group = "document", weight = "count"
)
# tidytext: word counts are already long (document, word, n)
group_hypergraph(
my_word_counts,
actor = "word", group = "document", weight = "n"
)When to use which
-
hg_cluster()— no labels, discover thematic groups; prefertype = "random_walk"whenever the weights are tf-idf (edge-dependent vertex weights are exactly its assumption). -
hg_classify()— a few trusted labels, classify the rest; readmarginas the confidence and expect conservative behavior when the vocabulary signal is weak. -
hg_centrality()— rank organizing vocabulary (words as nodes) or hub documents (documents as nodes). -
hg_measures()— the structural facts (sizes, overlaps, density) that say whether spectral analysis is even applicable; check connectivity-adjacent quantities before clustering.
References
Hayashi, K., Aksoy, S. G., Park, C. H., & Park, H. (2020). Hypergraph random walks, Laplacians, and clustering. CIKM 2020. doi:10.1145/3340531.3412034.
Zhou, D., Huang, J., & Schölkopf, B. (2006). Learning with hypergraphs: Clustering, classification, and embedding. NeurIPS 2006.