Introduction to the vnc R package
Load the vnc package
Load the package, as well as others that we’ll use in this vignette.
library(vnc)
library(tidyverse)
library(dendextend)
library(ggdendro)
Check the data
Let’s begin by looking at the frequencies of the bigram witch hunt and the plural witch hunts, which are available in the package data and come from Google Books.
knitr::kable(witch_hunt, caption = "Data included with vnc package")
decade |
token_count |
total_count |
counts_permil |
|---|---|---|---|
1600 |
3 |
1585485 |
1.89 |
1680 |
1 |
1945902 |
0.51 |
1810 |
4 |
499552970 |
0.01 |
1860 |
1 |
3247012381 |
0.00 |
1880 |
2 |
6267570245 |
0.00 |
1890 |
4 |
8293033235 |
0.00 |
1900 |
26 |
11103867317 |
0.00 |
1910 |
21 |
11777942915 |
0.00 |
1920 |
55 |
9964203621 |
0.01 |
1930 |
199 |
9067323416 |
0.02 |
1940 |
948 |
9673970472 |
0.10 |
1950 |
2639 |
13293905516 |
0.20 |
1960 |
3906 |
23660378418 |
0.17 |
1970 |
6379 |
29211995598 |
0.22 |
1980 |
6786 |
36463128334 |
0.19 |
1990 |
15064 |
58099277939 |
0.26 |
2000 |
29775 |
111276927919 |
0.27 |
Periodization
The purpose of Variability-Based Neighbor Clustering is to divide the use of a word or phrase into historical periods based on changes in frequency. Rather than assuming that a year, decade, or other division is statistically meaningful, the algorithm clusters segments of time into periods.
For this to work, the time data that we input must be evenly spaced. There can’t be any gaps. In the data above, it is clear that there are many missing decades. If we tried to plot the data, a function called is.sequence() would halt the process and produce an error.
vnc_scree(witch_hunt$decade, witch_hunt$counts_permil, distance.measure = "sd")
#> Error in vnc_scree(witch_hunt$decade, witch_hunt$counts_permil, distance.measure = "sd"): It appears that your time series contains gaps or is not evenly spaced.
Preparing the data
To prevent this error, we could fill in missing decades with zero counts. Alternatively, we can select only data from the twentieth century, which is what we will do here.
wh_d <- witch_hunt %>%
filter(decade > 1899) %>%
dplyr::select(decade, counts_permil)
Generate a scree plot
We will use the data to generate a scree plot. The platting function takes a vector of time intervals and a vector of values.
vnc_scree(wh_d$decade, wh_d$counts_permil, distance.measure = "sd")

Generate an hclust object
Next, we can generate an hclust object using the vnc_clust() function. Like the vnc_scree() function, it takes a vector of time intervals and a vector of values.
hc <- vnc_clust(wh_d$decade, wh_d$counts_permil, distance.measure = "sd")
Plot the dendrogram
A dendrogram can be plotted from the generated hclust object.
plot(hc, hang = -1)

Cut the dendrogram
For the next step, we’ll cut the dendrogram into 3 clusters based on the output of the scree plot we that generated. Note that we’re storing the output into an object cut_hc.
plot(hc, hang = -1)
cut_hc <- rect.hclust(hc, k=3)

Prepare data for fancier plotting
We’ve already plotted our data with base R. However, if we want more control, we probably want to use ggplot2. To do that, we need to go through a couple of intermediate steps. First, convert the cut_hc object that we just generated into a data.frame and join that with our original wh_d data.
clust_df <- data.frame(decade=as.numeric(names(unlist(cut_hc))),
clust=rep(c(paste0("clust_", seq(1:length(cut_hc)))),
times=sapply(cut_hc,length)))
clust_df <- clust_df %>% right_join(wh_d, by = "decade")
And check the result…
knitr::kable(clust_df)
decade |
clust |
counts_permil |
|---|---|---|
1900 |
clust_1 |
0.00 |
1910 |
clust_1 |
0.00 |
1920 |
clust_1 |
0.01 |
1930 |
clust_1 |
0.02 |
1940 |
clust_2 |
0.10 |
1950 |
clust_3 |
0.20 |
1960 |
clust_3 |
0.17 |
1970 |
clust_3 |
0.22 |
1980 |
clust_3 |
0.19 |
1990 |
clust_3 |
0.26 |
2000 |
clust_3 |
0.27 |
Next, we’ll convert our cluster data into dendrogram data using
as.dendrogram() from ggdendro. We also MUST maintain the order of our time series. There are a variety of ways of doing this, but dendextend
has an easy function called sort(). We’ll take the easy way!
To get ggplot-friendly data, we have to transform it yet again… This time using the ggdendro package’s function dendro_data().
dend <- as.dendrogram(hc) %>% sort
dend_data <- dendro_data(dend, type = "rectangle")
Now let’s do some fancy plotting! We’re going to combine the dendrogram and a time series line plot like Gries and Hilpert do on pg. 140 of their chapter on VNC. The first three lines pull data from clust_df for the line plot using the clusters to color each point according to group. The geom_segment pulls data from dend_data to build the dendrogram. For the tick marks we again pull from dend_data using the x column for the breaks and and the label column to label the breaks.
ggplot(clust_df, aes(x = as.numeric(rownames(clust_df)), y = counts_permil)) +
geom_line(linetype = "dotted") +
geom_point(aes(color = clust), size = 2) +
geom_segment(data = dend_data$segments, aes(x = x, y = y, xend = xend, yend = yend))+
scale_x_continuous(breaks = dend_data$labels$x,
labels=as.character(dend_data$labels$label)) +
xlab("") + ylab("frequency (per million words)") +
theme_minimal()

Bibliography
Gries, Stefan Th, and Martin Hilpert. 2012. “Variability-Based Neighbor Clustering: A Bottom-up Approach to Periodization in Historical Linguistics.” The Oxford Handbook of the History of English, 134–44. https://www.oxfordhandbooks.com/view/10.1093/oxfordhb/9780199922765.001.0001/oxfordhb-9780199922765-e-14#oxfordhb-9780199922765-div1-50.
Gries, Stefan Th, and Sabine Stoll. 2009. “Finding Developmental Groups in Acquisition Data: Variability-Based Neighbour Clustering.” Journal of Quantitative Linguistics 16 (3): 217–42. https://www.tandfonline.com/doi/abs/10.1080/09296170902975692.
Th. Gries, Stefan, and Martin Hilpert. 2008. “The Identification of Stages in Diachronic Data: Variability-Based Neighbour Clustering.” Corpora 3 (1): 59–81. https://www.euppublishing.com/doi/abs/10.3366/E1749503208000075.