This project applies text mining techniques to analyze mental health discussions on Reddit, specifically examining how online mental health communities evolved during the COVID-19 pandemic. The analysis uses selected files from the Reddit Mental Health Dataset (Low et al., 2020) published on Zenodo: https://zenodo.org/records/3941387, which contains posts from 28 subreddits including 15 mental health support groups.
Selected data include:
2018: Jan 1 to April 20, 2018 (control for seasonal
fluctuations)2019: Jan 1 to April 20, 2019 (control for seasonal
fluctuations)post: Jan 1 to April 20, 2020 (mid-pandemic
period)Files used in this analysis:
adhd_2018_features_tfidf_256.csvadhd_2019_features_tfidf_256.csvadhd_post_features_tfidf_256.csv# Required packages
library(tm)
library(SnowballC)
library(lsa)
library(tidyverse) # zawiera dplyr, tidyr, ggplot2
library(tidytext)
library(topicmodels)
library(cluster)
library(factoextra)
library(fpc)
library(igraph)
library(ggraph)
library(RColorBrewer)
library(wordcloud)
library(DT)
library(reshape2)
library(forcats)
library(gridExtra)## Structure of adhd_2018 dataset:
## Dimensions: 5110 350
## Column names (first 10):
## [1] "subreddit" "author"
## [3] "date" "post"
## [5] "automated_readability_index" "coleman_liau_index"
## [7] "flesch_kincaid_grade_level" "flesch_reading_ease"
## [9] "gulpease_index" "gunning_fog_index"
##
## Structure of adhd_2019 dataset:
## Dimensions: 10223 350
## Column names (first 10):
## [1] "subreddit" "author"
## [3] "date" "post"
## [5] "automated_readability_index" "coleman_liau_index"
## [7] "flesch_kincaid_grade_level" "flesch_reading_ease"
## [9] "gulpease_index" "gunning_fog_index"
##
## Structure of adhd_post dataset:
## Dimensions: 13602 350
## Column names (first 10):
## [1] "subreddit" "author"
## [3] "date" "post"
## [5] "automated_readability_index" "coleman_liau_index"
## [7] "flesch_kincaid_grade_level" "flesch_reading_ease"
## [9] "gulpease_index" "gunning_fog_index"
adhd_2018_clean <- adhd_2018 %>%
mutate(
period = "control_2018",
date = as.POSIXct(date, origin = "1970-01-01")
) %>%
select(subreddit, period, author, date, post)
adhd_2019_clean <- adhd_2019 %>%
mutate(
period = "control_2019",
date = as.POSIXct(date, origin = "1970-01-01")
) %>%
select(subreddit, period, author, date, post)
adhd_post_clean <- adhd_post %>%
mutate(
period = "mid_pandemic",
date = as.POSIXct(date, origin = "1970-01-01")
) %>%
select(subreddit, period, author, date, post)
# combine periods
adhd_combined <- rbind(adhd_2018_clean, adhd_2019_clean, adhd_post_clean)
cat("Structure of adhd_combined dataset:\n")## Structure of adhd_combined dataset:
## Dimensions: 28935 5
## 'data.frame': 28935 obs. of 5 variables:
## $ subreddit: chr "adhd" "adhd" "adhd" "adhd" ...
## $ period : chr "control_2018" "control_2018" "control_2018" "control_2018" ...
## $ author : chr "Seftari" "LisaLoves2" "featherflutter" "vanillasky0" ...
## $ date : POSIXct, format: "2018-01-01" "2018-01-01" ...
## $ post : chr "Lethargic/Depressed when off meds First I'll give some background to my medical history. I have struggled with "| __truncated__ "Concerta not working on the first day?! Update!: Thank you all for your insightful and kind responses, if not f"| __truncated__ "Comorbid anxiety and ADHD-PI Medication Question Hi, I was recently diagnosed with ADHD-Pi. I am an adult, and "| __truncated__ "Fist Day on Concerta 18mg UPDATE! Update!: Thank you all for your insightful and kind responses, if not for the"| __truncated__ ...
##
## Breakdown by period:
##
## control_2018 control_2019 mid_pandemic
## 5110 10223 13602
##
## First 3 rows of combined data:
# create corpus from post text
corpus_adhd <- VCorpus(VectorSource(adhd_combined$post))
# check size
print(corpus_adhd) ## <<VCorpus>>
## Metadata: corpus specific: 0, document level (indexed): 0
## Content: documents: 28935
##
## Example document content:
## <<VCorpus>>
## Metadata: corpus specific: 0, document level (indexed): 0
## Content: documents: 1
##
## [[1]]
## <<PlainTextDocument>>
## Metadata: 7
## Content: chars: 761
# ensure encoding across the entire corpus
corpus_adhd <- tm_map(corpus_adhd, content_transformer(function(x) iconv(x, to = "UTF-8", sub = "byte")))
# function to replace characters with space
toSpace <- content_transformer(function (x, pattern) gsub(pattern, " ", x))
# Remove URLs and web remnants
corpus_adhd <- tm_map(corpus_adhd, toSpace, "@")
corpus_adhd <- tm_map(corpus_adhd, toSpace, "@\\w+")
corpus_adhd <- tm_map(corpus_adhd, toSpace, "\\|")
corpus_adhd <- tm_map(corpus_adhd, toSpace, "[ \t]{2,}")
corpus_adhd <- tm_map(corpus_adhd, toSpace, "(s?)(f|ht)tp(s?)://\\S+\\b")
corpus_adhd <- tm_map(corpus_adhd, toSpace, "http\\w*")
corpus_adhd <- tm_map(corpus_adhd, toSpace, "/")
corpus_adhd <- tm_map(corpus_adhd, toSpace, "www")
corpus_adhd <- tm_map(corpus_adhd, toSpace, "~")
# text cleaning
corpus_adhd <- tm_map(corpus_adhd, content_transformer(tolower))
corpus_adhd <- tm_map(corpus_adhd, removePunctuation)
corpus_adhd <- tm_map(corpus_adhd, removeNumbers)
corpus_adhd <- tm_map(corpus_adhd, removeWords, stopwords("english"))
# remove custom stopwords
corpus_adhd <- tm_map(corpus_adhd, removeWords, c("adhd", "im", "dont", "can", "get", "just", "like", "ive", "ill", "really"))
corpus_adhd <- tm_map(corpus_adhd, stripWhitespace)
# check
cat("\nCleaned document content:\n")##
## Cleaned document content:
## [1] "lethargic depressed meds first give background medical history struggled depression years nowim currently bouts crying absolute consumption life every week months ago diagnosed add started taking mg xr adderall morning noon montg starting noticed longer bouts depression overall happier mom super anti meds blames everything bad adderall will take away randomly every time feel withdrawals bouts depression come back feel want lie die anyone else feel way "
# keep a copy of the corpus to use as dictionary for stem completion
corpus_adhd_copy <- corpus_adhd
# perform stemming
corpus_adhd_stemmed <- tm_map(corpus_adhd, stemDocument)
# check stemmed content
cat("Original:\n")## Original:
## [1] "lethargic depressed meds first give background medical history struggled depression years nowim currently bouts crying absolute consumption life every week months ago diagnosed add started taking mg xr adderall morning noon montg starting noticed longer bouts depression overall happier mom super anti meds blames everything bad adderall will take away randomly every time feel withdrawals bouts depression come back feel want lie die anyone else feel way "
##
##
## Stemmed:
## [1] "letharg depress med first give background medic histori struggl depress year nowim current bout cri absolut consumpt life everi week month ago diagnos add start take mg xr adderal morn noon montg start notic longer bout depress overal happier mom super anti med blame everyth bad adderal will take away random everi time feel withdraw bout depress come back feel want lie die anyon els feel way"
## DTM dimensions:
## <<DocumentTermMatrix (documents: 28935, terms: 34524)>>
## Non-/sparse entries: 1718786/997233154
## Sparsity : 100%
## Maximal term length: 477
## Weighting : term frequency (tf)
# reduce matrix sparsity - keep terms that appear in at least 1% of documents
dtm_sparse_adhd <- removeSparseTerms(dtm_adhd, sparse = 0.99)
cat("Reduced DTM dimensions:\n")## Reduced DTM dimensions:
## <<DocumentTermMatrix (documents: 28935, terms: 1019)>>
## Non-/sparse entries: 1378978/28105787
## Sparsity : 95%
## Maximal term length: 12
## Weighting : term frequency (tf)
# count word frequencies
word_freq <- sort(colSums(dtm_adhd_m), decreasing = TRUE)
freq_df_adhd <- data.frame(word = names(word_freq), freq = word_freq, row.names = NULL)
cat("Top 20 most frequent terms:\n")## Top 20 most frequent terms:
# count word frequencies by period
freq_by_period <- adhd_combined %>%
unnest_tokens(word, post) %>%
mutate(word = str_replace_all(word, "[’']", "")) %>%
mutate(word = str_trim(word)) %>%
anti_join(stop_words, by = "word") %>%
filter(!word %in% c("adhd", "im", "dont", "can", "just", "like", "get", "amp", "ve", "ive", "cant", "wont", "didnt", "x200b")) %>%
filter(!str_detect(word, "^[0-9]*$")) %>%
count(period, word, sort = TRUE) %>%
ungroup()
cat("Top frequencies calculated by period.\n")## Top frequencies calculated by period.
# bar plot of top 20 terms
top_20_adhd <- freq_df_adhd %>%
slice_max(freq, n = 20) %>%
mutate(word = fct_reorder(word, freq))
ggplot(top_20_adhd, aes(x = freq, y = word)) +
geom_col(fill = "#2a9d8f", width = 0.7) +
geom_text(aes(label = freq), hjust = -0.2, size = 3.5) +
labs(
title = "Top 20 most frequent terms in ADHD posts on Reddit",
subtitle = "Jan 1 to April 20 of: 2018, 2019, and 2020",
x = "Frequency",
y = "Term"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 14),
axis.text.y = element_text(size = 11)
)# Top 20 terms for each period
top_20_by_period <- freq_by_period %>%
group_by(period) %>%
slice_max(n, n = 20) %>%
ungroup() %>%
mutate(word = reorder_within(word, n, period))
# plot
ggplot(top_20_by_period, aes(x = n, y = word, fill = period)) +
geom_col(show.legend = FALSE, width = 0.7) +
geom_text(aes(label = n), hjust = -0.1, size = 3) +
facet_wrap(~ period, scales = "free_y") +
scale_y_reordered() +
scale_fill_manual(values = c("#2a9d8f", "#e9c46a", "#f4a261")) +
labs(
title = "Top 20 most frequent terms by period",
x = "Frequency",
y = NULL
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 14),
strip.text = element_text(face = "bold", size = 12)
)# Word cloud
wordcloud(words = freq_df_adhd$word,
freq = freq_df_adhd$freq,
min.freq = 50,
max.words = 100,
random.order = FALSE,
colors = brewer.pal(8, "Dark2"),
scale = c(4, 0.5))
title("ADHD posts - most frequent terms")# set up plotting area
par(mfrow = c(1, 3))
periods <- c("control_2018", "control_2019", "mid_pandemic")
for(p in periods) {
subset_data <- freq_by_period %>%
filter(period == p)
wordcloud(words = subset_data$word,
freq = subset_data$n,
min.freq = 50,
max.words = 80,
random.order = FALSE,
colors = brewer.pal(8, "Dark2"),
scale = c(3, 0.5))
title(p, line = -2)
}# Term-frequency matrix with TF-IDF
dtm_tfidf_adhd <- DocumentTermMatrix(corpus_adhd_stemmed,
control = list(weighting = function(x) weightTfIdf(x, normalize = FALSE)))
cat("TF-IDF DTM dimensions:\n")## TF-IDF DTM dimensions:
## <<DocumentTermMatrix (documents: 28935, terms: 34524)>>
## Non-/sparse entries: 1718786/997233154
## Sparsity : 100%
## Maximal term length: 477
## Weighting : term frequency - inverse document frequency (tf-idf)
# reduce sparsity
dtm_tfidf_sparse_adhd <- removeSparseTerms(dtm_tfidf_adhd, sparse = 0.99)
cat("\nReduced TF-IDF DTM dimensions:\n")##
## Reduced TF-IDF DTM dimensions:
## <<DocumentTermMatrix (documents: 28935, terms: 1019)>>
## Non-/sparse entries: 1378978/28105787
## Sparsity : 95%
## Maximal term length: 12
## Weighting : term frequency - inverse document frequency (tf-idf)
# TF-IDF word frequencies
# count TF-IDF weighted frequencies
tfidf_freq <- sort(colSums(dtm_tfidf_adhd_m), decreasing = TRUE)
tfidf_df_adhd <- data.frame(word = names(tfidf_freq), tfidf = tfidf_freq, row.names = NULL)
cat("Top 20 terms by TF-IDF:\n")## Top 20 terms by TF-IDF:
## word tfidf
## 1 feel 32683.99
## 2 work 29957.17
## 3 take 28230.61
## 4 time 28131.17
## 5 thing 27890.66
## 6 day 27374.80
## 7 medic 26283.24
## 8 year 24878.83
## 9 help 24817.34
## 10 know 24811.29
## 11 want 24778.27
## 12 tri 24392.35
## 13 start 24197.78
## 14 now 23004.74
## 15 make 22964.58
## 16 even 22924.27
## 17 school 22852.51
## 18 think 22837.56
## 19 peopl 22663.41
## 20 med 22639.03
# Word cloud - TF-IDF
wordcloud(words = tfidf_df_adhd$word,
freq = tfidf_df_adhd$tfidf,
min.freq = 50,
max.words = 100,
random.order = FALSE,
colors = brewer.pal(8, "Dark2"),
scale = c(4, 0.5))junk_words <- c("x200b", "row.two", "timedelta", "self.task", "iâ", "iâ t", "l")
# count words per Period (TF)
period_words <- adhd_combined %>%
unnest_tokens(word, post) %>%
anti_join(stop_words, by = "word") %>%
filter(!word %in% junk_words) %>%
filter(!str_detect(word, "^www")) %>%
filter(!str_detect(word, ".com$|.net$")) %>%
filter(!str_detect(word, "[^a-z']")) %>%
count(period, word, sort = TRUE) %>%
ungroup()
# calculate TF-IDF
total_words <- period_words %>%
group_by(period) %>%
summarize(total = sum(n))
period_words <- left_join(period_words, total_words)
period_tfidf <- period_words %>%
bind_tf_idf(word, period, n)
# the most distinctive words for 2020 (mid-pandemic), which appear in 2020 but not in 2018 or 2019
cat("Most distinctive words for mid-pandemic (high TF-IDF):\n")## Most distinctive words for mid-pandemic (high TF-IDF):
period_tfidf %>%
filter(period == "mid_pandemic") %>%
arrange(desc(tf_idf)) %>%
select(period, word, n, tf_idf) %>%
head(15) %>%
print()## period word n tf_idf
## 1 mid_pandemic quarantine 301 4.892618e-04
## 2 mid_pandemic covid 210 3.413455e-04
## 3 mid_pandemic pandemic 127 2.064327e-04
## 4 mid_pandemic coronavirus 116 1.885527e-04
## 5 mid_pandemic corona 87 1.414146e-04
## 6 mid_pandemic lockdown 71 4.259339e-05
## 7 mid_pandemic zoom 57 3.419470e-05
## 8 mid_pandemic outbreak 19 3.088364e-05
## 9 mid_pandemic wfh 19 3.088364e-05
## 10 mid_pandemic racket 15 2.438182e-05
## 11 mid_pandemic telehealth 15 2.438182e-05
## 12 mid_pandemic discord 39 2.339637e-05
## 13 mid_pandemic youtuber 14 2.275637e-05
## 14 mid_pandemic etip 12 1.950546e-05
## 15 mid_pandemic terry 11 1.788000e-05
# plot the highest TF-IDF words for each period
period_tfidf %>%
group_by(period) %>%
slice_max(tf_idf, n = 10) %>%
ungroup() %>%
mutate(word = reorder_within(word, tf_idf, period)) %>%
ggplot(aes(word, tf_idf, fill = period)) +
geom_col(show.legend = FALSE) +
labs(title = "Most distinctive words by period (TF-IDF)",
x = NULL, y = "TF-IDF") +
facet_wrap(~period, scales = "free") +
coord_flip() +
scale_x_reordered() +
theme_minimal()# Hierarchical Clustering
# calculate distance matrix
dist_matrix_adhd <- dist(t(dtm_tfidf_adhd_m), method = "euclidean")
# Hierarchical Clustering using Ward's method
hc_adhd <- hclust(dist_matrix_adhd, method = "ward.D2")
# plot dendrogram
plot(hc_adhd, hang = -1,
main = "Hierarchical Clustering of ADHD Terms (Ward's Method)",
xlab = "Terms",
sub = "",
cex = 0.6)
# draw rectangles around 5 clusters
rect.hclust(hc_adhd, k = 5, border = "red")# Inspect clusters
# cut tree into 5 clusters
clusters_5 <- cutree(hc_adhd, k = 5)
# show terms in each cluster
for (i in 1:5) {
cat("\n--- Cluster", i, "---\n")
cluster_terms <- names(clusters_5[clusters_5 == i])
cat(paste(cluster_terms[1:min(20, length(cluster_terms))], collapse = ", "), "\n")
}##
## --- Cluster 1 ---
## ’ll, ’re, activ, adult, advic, affect, ago, alarm, almost, alreadi, anger, angri, anoth, answer, anxious, anymor, anyon, anyway, articl, ask
##
## --- Cluster 2 ---
## ’ve, abl, actual, add, also, alway, anxieti, anyth, appoint, around, assign, back, bad, better, book, brain, cant, class, colleg, come
##
## --- Cluster 3 ---
## abil, absolut, abus, academ, accept, accomplish, account, achiev, across, act, action, addict, addit, adhder, adjust, admit, advanc, afford, afraid, afternoon
##
## --- Cluster 4 ---
## adderal, concerta, day, dose, effect, hour, med, medic, night, ritalin, side, sleep, take, vyvans, work
##
## --- Cluster 5 ---
## ampxb
# K-Means Clustering
# set number of clusters
k <- 4
kmeans_adhd <- kmeans(dtm_tfidf_adhd_m, centers = k, nstart = 25)
# summary
cat("K-means clustering with k =", k, "\n")## K-means clustering with k = 4
## Cluster sizes:
##
## 1 2 3 4
## 19887 5426 2683 939
# visualize K-means clusters
fviz_cluster(list(data = dtm_tfidf_adhd_m, cluster = kmeans_adhd$cluster),
geom = "point",
ellipse.type = "convex",
main = paste("K-means clustering of ADHD posts (k =", k, ")"),
subtitle = "Based on TF-IDF weighted document-term matrix",
ggtheme = theme_minimal(base_size = 14))# top words per cluster
cluster_info <- lapply(1:k, function(i) {
cluster_docs_idx <- which(kmeans_adhd$cluster == i)
cluster_docs <- dtm_tfidf_adhd_m[cluster_docs_idx, , drop = FALSE]
word_freq <- sort(colSums(cluster_docs), decreasing = TRUE)
top_words <- paste(names(word_freq)[1:10], collapse = ", ")
data.frame(
Cluster = i,
Number_of_documents = length(cluster_docs_idx),
Top_10_words = top_words,
stringsAsFactors = FALSE
)
})
cluster_info_df <- do.call(rbind, cluster_info)
datatable(cluster_info_df,
caption = "K-means clusters",
rownames = FALSE,
options = list(pageLength = 10, dom = 't'))# word clouds for each cluster
par(mfrow = c(2, 2), mar = c(2, 2, 3, 2))
for (i in 1:k) {
cluster_docs_idx <- which(kmeans_adhd$cluster == i)
cluster_docs <- dtm_tfidf_adhd_m[cluster_docs_idx, , drop = FALSE]
word_freq <- colSums(cluster_docs)
wordcloud(names(word_freq), freq = word_freq,
max.words = 30,
colors = brewer.pal(8, "Dark2"),
scale = c(3, 0.5))
title(paste("Cluster", i))
}# centroid vectors for each period - average discussion in each time period
is_2018 <- adhd_combined$period == "control_2018"
is_2019 <- adhd_combined$period == "control_2019"
is_2020 <- adhd_combined$period == "mid_pandemic"
# calculate column means (centroids) for each period
vec_2018_adhd <- colMeans(dtm_tfidf_adhd_m[is_2018, ])
vec_2019_adhd <- colMeans(dtm_tfidf_adhd_m[is_2019, ])
vec_2020_adhd <- colMeans(dtm_tfidf_adhd_m[is_2020, ])
# calculate pairwise cosine similarities
sim_2018_2019 <- cosine(vec_2018_adhd, vec_2019_adhd)
sim_2018_2020 <- cosine(vec_2018_adhd, vec_2020_adhd)
sim_2019_2020 <- cosine(vec_2019_adhd, vec_2020_adhd)
cat("Cosine similarity between periods:\n")## Cosine similarity between periods:
## 2018 vs 2019: 0.9891
## 2018 vs mid-pandemic 2020: 0.9918
## 2019 vs mid-pandemic 2020: 0.9923
## Values close to 1 indicate similar discussion patterns across periods
## 1.0 = identical discussions
## 0.0 = completely different discussions
# 4. VECTORIZATION (INTEGER WORD COUNTS)
# LDA requires raw counts, not TF-IDF
# Remove documents with zero terms (required for LDA)
row_totals <- apply(dtm_sparse_adhd, 1, sum)
dtm_clean_adhd <- dtm_sparse_adhd[row_totals > 0, ]
cat("DTM for LDA:\n")## DTM for LDA:
## <<DocumentTermMatrix (documents: 28931, terms: 1019)>>
## Non-/sparse entries: 1378978/28101711
## Sparsity : 95%
## Maximal term length: 12
## Weighting : term frequency (tf)
# APPLY LDA TOPIC MODELING
# set number of topics k = 4
k <- 4
set.seed(1234)
lda_model_adhd <- LDA(dtm_clean_adhd, k = k, method = "Gibbs",
control = list(seed = 1234, iter = 1000, burnin = 500))
cat("LDA model fitted with k =", k, "topics\n")## LDA model fitted with k = 4 topics
# Show top keywords per topic k = 4
# extract topic-word probabilities
lda_topics_adhd <- tidy(lda_model_adhd, matrix = "beta")
# prepare top terms per topic
top_terms_adhd <- lda_topics_adhd %>%
group_by(topic) %>%
slice_max(beta, n = 10) %>%
ungroup() %>%
arrange(topic, desc(beta))
# visualize top words per topic
ggplot(top_terms_adhd, aes(reorder_within(term, beta, topic), beta, fill = as.factor(topic))) +
geom_col(show.legend = FALSE) +
facet_wrap(~ topic, scales = "free", labeller = label_both) +
coord_flip() +
scale_x_reordered() +
scale_fill_brewer(palette = "Dark2") +
theme_minimal(base_size = 12) +
labs(title = "Top 10 words per topic (LDA with k = 4)",
subtitle = "ADHD posts",
x = NULL,
y = "Word importance (beta)") +
theme(
plot.title = element_text(face = "bold", size = 14),
strip.text = element_text(face = "bold", size = 11)
)# set the number of topics k = 6
k <- 6
set.seed(1234)
lda_model_adhd_6 <- LDA(dtm_clean_adhd, k = k, method = "Gibbs",
control = list(seed = 1234, iter = 1000, burnin = 500))
cat("LDA model fitted with k =", k, "topics\n")## LDA model fitted with k = 6 topics
# show top keywords per topic k = 6
# extract topic-word probabilities
lda_topics_adhd_6 <- tidy(lda_model_adhd_6, matrix = "beta")
# prepare top terms per topic
top_terms_adhd_6 <- lda_topics_adhd_6 %>%
group_by(topic) %>%
slice_max(beta, n = 10) %>%
ungroup() %>%
arrange(topic, desc(beta))
# visualize
ggplot(top_terms_adhd_6, aes(reorder_within(term, beta, topic), beta, fill = as.factor(topic))) +
geom_col(show.legend = FALSE) +
facet_wrap(~ topic, scales = "free", labeller = label_both, ncol = 3) +
coord_flip() +
scale_x_reordered() +
scale_fill_brewer(palette = "Dark2") +
theme_minimal(base_size = 11) +
labs(title = "Top 10 words per topic (LDA with k = 6)",
subtitle = "ADHD posts across all periods",
x = NULL,
y = "Word importance (beta)") +
theme(
plot.title = element_text(face = "bold", size = 14),
strip.text = element_text(face = "bold", size = 10)
)# set the number of topics k = 8
k <- 8
set.seed(1234)
lda_model_adhd_8 <- LDA(dtm_clean_adhd, k = k, method = "Gibbs",
control = list(seed = 1234, iter = 1000, burnin = 500))
cat("LDA model fitted with k =", k, "topics\n")## LDA model fitted with k = 8 topics
# show top keywords per topic k = 8
# extract topic-word probabilities
lda_topics_adhd_8 <- tidy(lda_model_adhd_8, matrix = "beta")
# prepare top terms per topic
top_terms_adhd_8 <- lda_topics_adhd_8 %>%
group_by(topic) %>%
slice_max(beta, n = 10) %>%
ungroup() %>%
arrange(topic, desc(beta))
# visualize
ggplot(top_terms_adhd_8, aes(reorder_within(term, beta, topic), beta, fill = as.factor(topic))) +
geom_col(show.legend = FALSE) +
facet_wrap(~ topic, scales = "free", labeller = label_both, ncol = 3) +
coord_flip() +
scale_x_reordered() +
scale_fill_brewer(palette = "Dark2") +
theme_minimal(base_size = 11) +
labs(title = "Top 10 words per topic (LDA with k = 8)",
subtitle = "ADHD posts across all periods",
x = NULL,
y = "Word importance (beta)") +
theme(
plot.title = element_text(face = "bold", size = 14),
strip.text = element_text(face = "bold", size = 10)
)analyze_lda_k <- function(lda_model, k_val) {
adhd_topics <- tidy(lda_model, matrix = "beta")
top_terms <- adhd_topics %>%
group_by(topic) %>%
slice_max(beta, n = 10) %>%
ungroup() %>%
arrange(topic, -beta)
doc_topics <- posterior(lda_model)$topics
dominant_topic <- apply(doc_topics, 1, which.max)
total_docs <- nrow(doc_topics)
# summary table
lda_summary_df <- top_terms %>%
group_by(topic) %>%
summarise(
Num_docs = sum(dominant_topic == topic),
Percent_of_docs = paste0(round(100 * sum(dominant_topic == topic) / total_docs, 1), "%"),
Top_10_Words = paste(term[1:10], collapse = ", "),
.groups = "drop"
)
cat("\nSummary for k =", k_val, "\n")
print(
datatable(
lda_summary_df,
caption = paste("LDA Summary for k =", k_val),
options = list(dom = 't', paging = FALSE, autoWidth = TRUE),
rownames = FALSE,
class = "compact stripe hover"
)
)
# get dominant topic counts for plotting
lda_gamma <- tidy(lda_model, matrix = "gamma") %>%
group_by(document) %>%
slice_max(gamma, n = 1) %>%
ungroup()
p <- ggplot(lda_gamma, aes(x = factor(topic))) +
geom_bar(fill = "#2a9d8f", width = 0.6, alpha = 0.8) +
geom_text(stat = 'count', aes(label = after_stat(count)), vjust = -0.5, size = 4) +
labs(
title = paste("Number of posts per dominant topic (k =", k_val, ")"),
x = "Topic",
y = "Count of posts"
) +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold"))
print(p)
}
analyze_lda_k(lda_model_adhd, 4)##
## Summary for k = 4
##
## Summary for k = 6
##
## Summary for k = 8
# 6. EXPLORE TOPICS
# TEST MULTIPLE k VALUES
# set range of k
k_values <- seq(2, 10, 2)
cat("Running LDA for k =", k, "...\n")## Running LDA for k = 8 ...
# initialize vector of results
perplexities_adhd <- numeric(length(k_values))
# calculate perplexity for each model
for (i in seq_along(k_values)) {
k <- k_values[i]
cat("Running LDA for k =", k, "...\n")
# fit the model
model <- LDA(
dtm_clean_adhd,
k = k,
method = "Gibbs",
control = list(seed = 1234, iter = 1000, burnin = 500, thin = 100)
)
# calculate perplexity
ll <- as.numeric(logLik(model))
n_tokens <- sum(dtm_clean_adhd)
perplexities_adhd[i] <- exp(-ll / n_tokens)
}## Running LDA for k = 2 ...
## Running LDA for k = 4 ...
## Running LDA for k = 6 ...
## Running LDA for k = 8 ...
## Running LDA for k = 10 ...
# create data frame for visualization
perplexity_df_adhd <- data.frame(
k = k_values,
perplexity = perplexities_adhd
)# visualization
ggplot(perplexity_df_adhd, aes(x = k, y = perplexity)) +
geom_line(color = "#2a9d8f", linewidth = 1) +
geom_point(color = "#2a9d8f", size = 3) +
geom_text(aes(label = k), vjust = -1, size = 3.5) +
theme_minimal(base_size = 12) +
labs(
title = "LDA model fit by number of topics",
subtitle = "Perplexity diagnostic (lower is better)",
x = "Number of topics (k)",
y = "Perplexity"
) +
theme(
plot.title = element_text(face = "bold"),
panel.grid.minor = element_blank(),
panel.grid.major.x = element_blank()
)Number of topics between 6 and 8 appear to provide a good balance between model complexity and interpretability based on perplexity scores.
| Topic | Suggested label |
|---|---|
| Topic 1 | Social relationships and communication |
| Topic 2 | Medication management |
| Topic 3 | Academic/school life |
| Topic 4 | Employment and career |
| Topic 5 | Diagnosis and medical access |
| Topic 6 | Cognitive symptoms (focus/memory) |
| Topic 7 | Time management and routine |
| Topic 8 | Emotional regulation |
# extract topic probabilities
topics_gamma <- tidy(lda_model_adhd_8, matrix = "gamma")
topics_gamma <- topics_gamma %>%
mutate(document_id = as.integer(document)) %>%
mutate(period = adhd_combined$period[document_id])
topic_labels <- tibble(
topic = 1:8,
topic_name = c("1. Social/relationships",
"2. Medication management",
"3. School/academic",
"4. Employment/jobs",
"5. Diagnosis process",
"6. Cognitive symptoms",
"7. Routine/sleep",
"8. Emotional regulation")
)
plot_data <- topics_gamma %>%
left_join(topic_labels, by = "topic") %>%
mutate(period = factor(period, levels = c("control_2018", "control_2019", "mid_pandemic"))) %>%
mutate(topic_name = factor(topic_name, levels = topic_labels$topic_name))
plot_data %>%
group_by(period, topic_name) %>%
summarize(mean_gamma = mean(gamma), .groups = "drop") %>%
ggplot(aes(x = topic_name, y = mean_gamma, fill = period)) +
geom_col(position = position_dodge(width = 0.8), width = 0.7) +
scale_x_discrete(limits = rev(levels(plot_data$topic_name))) +
labs(title = "Shift of ADHD topics",
subtitle = "Comparison of topic prevalence (LDA, k=8)",
x = NULL,
y = "Topic prevalence (mean gamma)") +
coord_flip() +
theme_minimal() +
scale_fill_manual(values = c("control_2018" = "#66c2a5",
"control_2019" = "#fc8d62",
"mid_pandemic" = "#8da0cb")) +
theme(legend.position = "bottom")# For dictionary-based sentiment analysis:
# - no corpus object needed
# - no DTM needed
# - no stemming (must keep full words for lexicon matching)
# - minimal cleaning - remove stopwords
# custom stopwords
custom_stop_words_adhd <- stop_words
# domain-specific words to ignore
custom_stop_words_adhd <- bind_rows(
tibble(word = c("adhd", "im", "ive", "dont", "thats"),
lexicon = "custom"),
custom_stop_words_adhd
)# TOKENIZATION
# Tokenize using tidytext
# unnest_tokens():
# - lowercases text
# - removes punctuation
# - standardizes whitespace
# - extracts one-token-per-row
tidy_posts_adhd <- adhd_combined %>%
unnest_tokens(output = word, input = post)
cat("Number of tokens before stopword removal:", nrow(tidy_posts_adhd), "\n")## Number of tokens before stopword removal: 5071266
# remove stopwords
tidy_posts_adhd <- tidy_posts_adhd %>%
anti_join(custom_stop_words_adhd, by = "word")
cat("Number of tokens after stopword removal:", nrow(tidy_posts_adhd), "\n")## Number of tokens after stopword removal: 1610263
cat("Tokens removed:", nrow(adhd_combined %>% unnest_tokens(word, post)) - nrow(tidy_posts_adhd), "\n")## Tokens removed: 3461003
## 1. Bing: binary positive/negative classification
## # A tibble: 2 × 2
## sentiment n
## <chr> <int>
## 1 negative 4781
## 2 positive 2005
##
## 2. AFINN: numeric scale from -5 (negative) to +5 (positive)
afinn_summary <- get_sentiments("afinn") %>%
summarize(min_value = min(value), max_value = max(value), n_words = n())
print(afinn_summary)## # A tibble: 1 × 3
## min_value max_value n_words
## <dbl> <dbl> <int>
## 1 -5 5 2477
##
## 3. NRC: emotions + positive/negative
## # A tibble: 10 × 2
## sentiment n
## <chr> <int>
## 1 negative 3316
## 2 positive 2308
## 3 fear 1474
## 4 anger 1245
## 5 trust 1230
## 6 sadness 1187
## 7 disgust 1056
## 8 anticipation 837
## 9 joy 687
## 10 surprise 532
# Visualize lexicon distributions
# Bing sentiment counts
p1 <- get_sentiments("bing") %>%
count(sentiment) %>%
mutate(sentiment = fct_reorder(sentiment, n)) %>%
ggplot(aes(x = sentiment, y = n, fill = sentiment)) +
geom_col(width = 0.5, show.legend = FALSE) +
geom_text(aes(label = n), vjust = -0.5, size = 3.5) +
scale_fill_brewer(palette = "Dark2") +
labs(title = "Bing lexicon", x = NULL, y = "Word count") +
theme_minimal(base_size = 11)
# NRC sentiment counts
p2 <- get_sentiments("nrc") %>%
count(sentiment) %>%
mutate(sentiment = fct_reorder(sentiment, n)) %>%
ggplot(aes(x = sentiment, y = n, fill = sentiment)) +
geom_col(width = 0.7, show.legend = FALSE) +
coord_flip() +
labs(title = "NRC lexicon", x = NULL, y = "Word count") +
theme_minimal(base_size = 11)
# AFINN value distribution
p3 <- get_sentiments("afinn") %>%
count(value) %>%
mutate(value = fct_reorder(as.factor(value), value, .desc = FALSE)) %>%
ggplot(aes(x = value, y = n, fill = value)) +
geom_col(width = 0.7, show.legend = FALSE) +
coord_flip() +
labs(title = "AFINN lexicon", x = "Sentiment value", y = "Word count") +
theme_minimal(base_size = 11)
grid.arrange(p1, p2, p3, ncol = 3,
top = "Sentiment lexicon distributions")# Bing dictionary analysis
bing_dict <- get_sentiments("bing") %>%
distinct(word, .keep_all = TRUE)
bing_results_adhd <- tidy_posts_adhd %>%
inner_join(bing_dict, by = "word")
cat("Number of sentiment-bearing words found:", nrow(bing_results_adhd), "\n")## Number of sentiment-bearing words found: 251040
bing_word_counts_adhd <- bing_results_adhd %>%
count(word, sentiment, sort = TRUE)
cat("\nTop 10 positive words:\n")##
## Top 10 positive words:
## word sentiment n
## 1 pretty positive 3254
## 2 love positive 2593
## 3 super positive 1734
## 4 helped positive 1715
## 5 happy positive 1519
## 6 fine positive 1176
## 7 productive positive 1176
## 8 fast positive 1124
## 9 easy positive 1031
## 10 clean positive 996
##
## Top 10 negative words:
## word sentiment n
## 1 anxiety negative 5539
## 2 hard negative 5262
## 3 bad negative 4039
## 4 symptoms negative 3778
## 5 depression negative 3189
## 6 issues negative 2908
## 7 shit negative 2212
## 8 worse negative 2039
## 9 struggle negative 2028
## 10 tired negative 2023
# visualize top sentiment words
bing_top_words_adhd <- bing_word_counts_adhd %>%
group_by(sentiment) %>%
slice_max(n, n = 10) %>%
ungroup() %>%
mutate(word = reorder_within(word, n, sentiment))
ggplot(bing_top_words_adhd, aes(x = n, y = word, fill = sentiment)) +
geom_col(show.legend = FALSE) +
facet_wrap(~ sentiment, scales = "free_y") +
scale_y_reordered() +
scale_fill_manual(values = c("negative" = "#d7191c", "positive" = "#2b83ba")) +
labs(
title = "Top 10 sentiment words in ADHD posts",
subtitle = "Bing dictionary",
x = "Frequency",
y = NULL
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 14),
strip.text = element_text(face = "bold", size = 12)
)sentiment_by_period_adhd <- bing_results_adhd %>%
count(period, sentiment) %>%
pivot_wider(
names_from = sentiment,
values_from = n,
values_fill = 0
) %>%
mutate(
total = positive + negative,
net_sentiment = positive - negative,
sentiment_ratio = positive / (positive + negative)
)
print(sentiment_by_period_adhd)## # A tibble: 3 × 6
## period negative positive total net_sentiment sentiment_ratio
## <chr> <int> <int> <int> <int> <dbl>
## 1 control_2018 34212 14891 49103 -19321 0.303
## 2 control_2019 62516 27028 89544 -35488 0.302
## 3 mid_pandemic 79038 33355 112393 -45683 0.297
sentiment_plot_data_adhd <- sentiment_by_period_adhd %>%
pivot_longer(
cols = c(positive, negative),
names_to = "sentiment",
values_to = "count"
) %>%
mutate(
period = factor(period, levels = c("control_2018", "control_2019", "mid_pandemic")),
sentiment = factor(sentiment, levels = c("positive", "negative"))
)
# stacked bar chart
ggplot(sentiment_plot_data_adhd, aes(x = period, y = count, fill = sentiment)) +
geom_col(position = "stack", width = 0.6) +
scale_fill_manual(
values = c("positive" = "#2b83ba", "negative" = "#d7191c"),
labels = c("Positive", "Negative")
) +
labs(
title = "Sentiment distribution",
subtitle = "ADHD posts using Bing",
x = "Period",
y = "Count of words",
fill = "Sentiment"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 14),
legend.position = "right"
)# net sentiment by period
ggplot(sentiment_by_period_adhd, aes(x = period, y = net_sentiment)) +
geom_col(aes(fill = net_sentiment > 0), width = 0.6, show.legend = FALSE) +
geom_text(aes(label = net_sentiment), vjust = -0.5, size = 4) +
scale_fill_manual(values = c("TRUE" = "#2b83ba", "FALSE" = "#d7191c")) +
labs(
title = "Net sentiment score",
subtitle = "Positive minus negative (Bing)",
x = "Period",
y = "Net sentiment score"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 14)
)# AFINN dictionary analysis
afinn_results_adhd <- tidy_posts_adhd %>%
inner_join(get_sentiments("afinn"), by = "word", relationship = "many-to-one")
afinn_by_period_adhd <- afinn_results_adhd %>%
group_by(period) %>%
summarise(
mean_sentiment = mean(value),
median_sentiment = median(value),
total_sentiment = sum(value),
n_words = n(),
.groups = "drop"
) %>%
mutate(period = factor(period, levels = c("control_2018", "control_2019", "mid_pandemic")))
print(afinn_by_period_adhd)## # A tibble: 3 × 5
## period mean_sentiment median_sentiment total_sentiment n_words
## <fct> <dbl> <dbl> <dbl> <int>
## 1 control_2018 -0.632 -1 -28439 44965
## 2 control_2019 -0.592 -1 -48466 81832
## 3 mid_pandemic -0.617 -1 -63911 103635
# visualize AFINN sentiment
ggplot(afinn_by_period_adhd, aes(x = period, y = mean_sentiment)) +
geom_col(aes(fill = mean_sentiment > 0), width = 0.6, show.legend = FALSE) +
geom_text(aes(label = round(mean_sentiment, 2)), vjust = -0.5, size = 4) +
scale_fill_manual(values = c("TRUE" = "#2b83ba", "FALSE" = "#d7191c")) +
labs(
title = "Average sentiment score period",
subtitle = "AFINN lexicon (scale: -5 to +5)",
x = "Period",
y = "Mean sentiment score"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 14)
)nrc_results_adhd <- tidy_posts_adhd %>%
inner_join(get_sentiments("nrc"), by = "word", relationship = "many-to-many")
# count emotions by period
nrc_by_period_adhd <- nrc_results_adhd %>%
count(period, sentiment) %>%
mutate(period = factor(period, levels = c("control_2018", "control_2019", "mid_pandemic")))
cat("Emotion counts by period:\n")## Emotion counts by period:
## period sentiment n
## 1 control_2018 anger 12696
## 2 control_2018 anticipation 20099
## 3 control_2018 disgust 8874
## 4 control_2018 fear 15349
## 5 control_2018 joy 11923
## 6 control_2018 negative 29709
## 7 control_2018 positive 34615
## 8 control_2018 sadness 15569
## 9 control_2018 surprise 7002
## 10 control_2018 trust 21354
## 11 control_2019 anger 22624
## 12 control_2019 anticipation 37240
## 13 control_2019 disgust 15846
## 14 control_2019 fear 27488
## 15 control_2019 joy 22196
## 16 control_2019 negative 54079
## 17 control_2019 positive 63641
## 18 control_2019 sadness 28204
## 19 control_2019 surprise 13062
## 20 control_2019 trust 39785
## 21 mid_pandemic anger 28611
## 22 mid_pandemic anticipation 45663
## 23 mid_pandemic disgust 20303
## 24 mid_pandemic fear 35405
## 25 mid_pandemic joy 27575
## 26 mid_pandemic negative 68657
## 27 mid_pandemic positive 78296
## 28 mid_pandemic sadness 35655
## 29 mid_pandemic surprise 16298
## 30 mid_pandemic trust 48651
# visualize NRC emotions
ggplot(nrc_by_period_adhd, aes(x = sentiment, y = n, fill = period)) +
geom_col(position = "dodge", width = 0.7) +
coord_flip() +
scale_fill_brewer(palette = "Dark2") +
labs(
title = "Emotion distribution",
subtitle = "NRC emotion lexicon - ADHD posts",
x = "Emotion",
y = "Frequency",
fill = "Period"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 14),
legend.position = "right"
)# WORDCLOUD OF SENTIMENT WORDS
par(mfrow = c(1, 2))
# Positive words
positive_words_adhd <- bing_word_counts_adhd %>%
filter(sentiment == "positive")
wordcloud(words = positive_words_adhd$word,
freq = positive_words_adhd$n,
min.freq = 5,
max.words = 100,
random.order = FALSE,
colors = brewer.pal(8, "Blues")[4:8],
scale = c(4, 0.5))
title("Positive sentiment words")
# Negative words
negative_words_adhd <- bing_word_counts_adhd %>%
filter(sentiment == "negative")
wordcloud(words = negative_words_adhd$word,
freq = negative_words_adhd$n,
min.freq = 5,
max.words = 100,
random.order = FALSE,
colors = brewer.pal(8, "Reds")[4:8],
scale = c(4, 0.5))
title("Negative sentiment words")clean_comparison_data <- adhd_combined %>%
unnest_tokens(word, post) %>%
mutate(word = str_remove_all(word, "[[:punct:]]")) %>%
anti_join(stop_words, by = "word") %>%
filter(!word %in% c("adhd", "im", "dont", "can", "just", "like", "get", "amp", "ve", "ive", "cant", "wont", "didnt", "x200b", "ve", "ill", "cant", "adhd", "x200b", "amp", "im", "dont", "can", "ive", "its", "ive")) %>%
filter(!str_detect(word, "^[0-9]+$")) %>%
count(period, word) %>%
acast(word ~ period, value.var = "n", fill = 0)
comparison.cloud(clean_comparison_data,
max.words = 100,
scale = c(3.5, 0.5),
colors = c("#1b9e77", "#d95f02", "#7570b3"),
title.size = 1,
random.order = FALSE,
match.colors = TRUE)
title("Comparison word cloud by period", line = 1)# create bigrams (word pairs)
bigrams_adhd <- adhd_combined %>%
unnest_tokens(output = bigram,
input = post,
token = "ngrams",
n = 2) %>%
separate(bigram, into = c("word1", "word2"), sep = " ")
cat("Total bigrams created:", nrow(bigrams_adhd), "\n")## Total bigrams created: 5042331
# filter bigrams
# custom stopwords (keeping negators)
custom_stop_bigrams <- stop_words
# add domain-specific stopwords
custom_stop_bigrams <- bind_rows(
tibble(word = c("adhd", "i'm", "x200b", "tl", "dr", "hey", "guys", "ago"),
lexicon = "custom"),
custom_stop_bigrams
)
# filter bigrams: remove stopwords from both positions
bigrams_filtered_adhd <- bigrams_adhd %>%
filter(!word1 %in% custom_stop_bigrams$word,
!word2 %in% custom_stop_bigrams$word,
!str_detect(word1, "^[0-9]+$"),
!str_detect(word2, "^[0-9]+$"),
!is.na(word1),
!is.na(word2),
)
cat("Bigrams after filtering:", nrow(bigrams_filtered_adhd), "\n")## Bigrams after filtering: 383240
# count bigrams
# count most frequent bigrams
bigram_counts_adhd <- bigrams_filtered_adhd %>%
count(word1, word2, sort = TRUE)
cat("Top 20 bigrams:\n")## Top 20 bigrams:
## word1 word2 n
## 1 mental health 816
## 2 adderall xr 808
## 3 hard time 759
## 4 started taking 652
## 5 recently diagnosed 557
## 6 pay attention 553
## 7 fall asleep 418
## 8 video games 403
## 9 entire life 401
## 10 taking adderall 398
## 11 stopped taking 387
## 12 heart rate 334
## 13 paying attention 331
## 14 social anxiety 331
## 15 makes sense 290
## 16 adderall ir 285
## 17 fast forward 284
## 18 taking medication 282
## 19 taking meds 274
## 20 brain fog 272
# visualize most frequent bigrams
bigrams_top20_adhd <- bigram_counts_adhd %>%
slice_max(n, n = 20) %>%
mutate(
bigram = paste(word1, word2),
bigram = fct_reorder(bigram, n)
)
ggplot(bigrams_top20_adhd, aes(x = n, y = bigram)) +
geom_col(fill = "#2a9d8f", width = 0.7) +
geom_text(aes(label = n), hjust = -0.2, size = 3.5) +
labs(
title = "Most frequent bigrams in ADHD pstss",
x = "Frequency",
y = "Bigram"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 14)
)# create bigram network
# filter for bigrams
bigram_graph_adhd <- bigram_counts_adhd %>%
filter(n >= 100) %>% # appearing at least 100 times
graph_from_data_frame()
# create network visualization
ggraph(bigram_graph_adhd, layout = "fr") +
# EDGES
geom_edge_link(aes(edge_alpha = n, edge_width = n),
edge_colour = "cyan4",
show.legend = FALSE) +
# NODES
geom_node_point(color = "darkblue", size = 4) +
# LABELS
geom_node_text(aes(label = name),
vjust = 1.5, hjust = 0.5,
size = 3.5,
repel = TRUE) +
scale_edge_width(range = c(0.5, 3)) +
scale_edge_alpha(range = c(0.3, 0.8)) +
theme_void() +
labs(
title = "Bigram Network of ADHD posts",
subtitle = "Minimum 20 occurrences"
) +
theme(
plot.title = element_text(face = "bold", size = 16, hjust = 0.5),
plot.subtitle = element_text(size = 12, hjust = 0.5, margin = ggplot2::margin(b = 20))
)# start from the original text not to lose the year labels
bigrams_with_period <- adhd_combined %>%
unnest_tokens(bigram, post, token = "ngrams", n = 2) %>%
separate(bigram, c("word1", "word2"), sep = " ") %>%
# remove stop words and numbers
filter(!word1 %in% stop_words$word) %>%
filter(!word2 %in% stop_words$word) %>%
filter(!word1 %in% c("adhd", "amp", "x200b", "people", "time", "don't", "i'm")) %>%
filter(!word2 %in% c("adhd", "amp", "x200b", "people", "time", "don't", "i'm")) %>%
filter(!str_detect(word1, "^[0-9]+$")) %>%
filter(!str_detect(word2, "^[0-9]+$")) %>%
count(period, word1, word2, sort = TRUE)
plot_network <- function(data, period_name, min_n = 15) {
plot_data <- data %>%
filter(period == period_name) %>%
filter(n >= min_n) %>%
select(word1, word2, n)
if(nrow(plot_data) == 0) return(NULL)
# plot
graph_from_data_frame(plot_data) %>%
ggraph(layout = "fr") +
geom_edge_link(aes(edge_alpha = n, edge_width = n),
edge_colour = "cyan4", show.legend = FALSE) +
geom_node_point(color = "darkblue", size = 3) +
geom_node_text(aes(label = name), vjust = 1.5, size = 3, repel = TRUE) +
labs(title = paste("Network:", period_name),
subtitle = paste("Connections appearing >=", min_n, "times")) +
theme_void() +
theme(plot.title = element_text(face = "bold", hjust = 0.5))
}
p1 <- plot_network(bigrams_with_period, "control_2018", min_n = 15)
p2 <- plot_network(bigrams_with_period, "control_2019", min_n = 15)
p3 <- plot_network(bigrams_with_period, "mid_pandemic", min_n = 15)
print(p1)# ASSOCIATIONS – FINDING CO-OCCURRING WORDS
# create TDM for association analysis
tdm_adhd <- TermDocumentMatrix(corpus_adhd_stemmed)
# find words associated with key terms
cat("Associations with 'take':\n")## Associations with 'take':
## $take
## day
## 0.31
##
## Associations with 'effect':
## $effect
## side
## 0.62
##
## Associations with 'sleep':
## $sleep
## “sleep adenosine— boardcertifi drive— importance—
## 0.40 0.40 0.40 0.40 0.40
## neurologist’ proinflammatori society’ depriv wake
## 0.40 0.40 0.40 0.35 0.33
## advent sleepwak
## 0.30 0.30
# visualization of associations
target_word <- "take"
cor_limit <- 0.2
# calculate associations
associations_adhd <- findAssocs(tdm_adhd, target_word, corlimit = cor_limit)
assoc_vector_adhd <- associations_adhd[[target_word]]
# if no associations found
if (length(assoc_vector_adhd) > 0) {
assoc_sorted_adhd <- sort(assoc_vector_adhd, decreasing = TRUE)
assoc_df_adhd <- data.frame(
word = names(assoc_sorted_adhd),
score = assoc_sorted_adhd,
row.names = NULL
)
# lollipop chart
ggplot(assoc_df_adhd, aes(x = score, y = reorder(word, score), color = score)) +
geom_segment(aes(x = 0, xend = score, y = word, yend = word),
linewidth = 1.2) +
geom_point(size = 4) +
geom_text(aes(label = round(score, 2)),
hjust = -0.3, size = 3.5, color = "black") +
scale_color_gradient(low = "#a6bddb", high = "#08306b") +
scale_x_continuous(
limits = c(0, max(assoc_df_adhd$score) + 0.1),
expand = expansion(mult = c(0, 0.15))
) +
theme_minimal(base_size = 12) +
labs(
title = paste0("Word associations with '", target_word, "'"),
subtitle = paste0("Pearson correlation threshold r ≥ ", cor_limit),
x = "Correlation coefficient",
y = "Associated word",
color = "Association\nStrength"
) +
theme(
plot.title = element_text(face = "bold", size = 14),
legend.position = "right"
)
} else {
cat("No associations found for", target_word, "at threshold", cor_limit, "\n")
}# visualization of associations
target_word <- "effect"
cor_limit <- 0.2
# calculate associations
associations_adhd <- findAssocs(tdm_adhd, target_word, corlimit = cor_limit)
assoc_vector_adhd <- associations_adhd[[target_word]]
# if no associations found
if (length(assoc_vector_adhd) > 0) {
assoc_sorted_adhd <- sort(assoc_vector_adhd, decreasing = TRUE)
assoc_df_adhd <- data.frame(
word = names(assoc_sorted_adhd),
score = assoc_sorted_adhd,
row.names = NULL
)
# lollipop chart
ggplot(assoc_df_adhd, aes(x = score, y = reorder(word, score), color = score)) +
geom_segment(aes(x = 0, xend = score, y = word, yend = word),
linewidth = 1.2) +
geom_point(size = 4) +
geom_text(aes(label = round(score, 2)),
hjust = -0.3, size = 3.5, color = "black") +
scale_color_gradient(low = "#a6bddb", high = "#08306b") +
scale_x_continuous(
limits = c(0, max(assoc_df_adhd$score) + 0.1),
expand = expansion(mult = c(0, 0.15))
) +
theme_minimal(base_size = 12) +
labs(
title = paste0("Word associations with '", target_word, "'"),
subtitle = paste0("Pearson correlation threshold r ≥ ", cor_limit),
x = "Correlation coefficient",
y = "Associated word",
color = "Association\nStrength"
) +
theme(
plot.title = element_text(face = "bold", size = 14),
legend.position = "right"
)
} else {
cat("No associations found for", target_word, "at threshold", cor_limit, "\n")
}# visualization of associations
target_word <- "sleep"
cor_limit <- 0.2
# calculate associations
associations_adhd <- findAssocs(tdm_adhd, target_word, corlimit = cor_limit)
assoc_vector_adhd <- associations_adhd[[target_word]]
# if no associations found
if (length(assoc_vector_adhd) > 0) {
assoc_sorted_adhd <- sort(assoc_vector_adhd, decreasing = TRUE)
assoc_df_adhd <- data.frame(
word = names(assoc_sorted_adhd),
score = assoc_sorted_adhd,
row.names = NULL
)
# lollipop chart
ggplot(assoc_df_adhd, aes(x = score, y = reorder(word, score), color = score)) +
geom_segment(aes(x = 0, xend = score, y = word, yend = word),
linewidth = 1.2) +
geom_point(size = 4) +
geom_text(aes(label = round(score, 2)),
hjust = -0.3, size = 3.5, color = "black") +
scale_color_gradient(low = "#a6bddb", high = "#08306b") +
scale_x_continuous(
limits = c(0, max(assoc_df_adhd$score) + 0.1),
expand = expansion(mult = c(0, 0.15))
) +
theme_minimal(base_size = 12) +
labs(
title = paste0("Word associations with '", target_word, "'"),
subtitle = paste0("Pearson correlation threshold r ≥ ", cor_limit),
x = "Correlation coefficient",
y = "Associated word",
color = "Association\nStrength"
) +
theme(
plot.title = element_text(face = "bold", size = 14),
legend.position = "right"
)
} else {
cat("No associations found for", target_word, "at threshold", cor_limit, "\n")
}This analysis successfully applied multiple text mining techniques to examine mental health discussions on Reddit across different time periods. Key achievements:
Examied assumptions:
Key findings in ADHD discussions:
Methodological strengths:
Limitations: