library(tidyverse)
library(tidytext)
library(widyr)
library(igraph)
library(ggraph)
library(Matrix)

Word Co-occurrence Analysis — Athleisure YouTube Comments

This analysis uses the YouTube comments scraped in Lab 4, discussing athleisure brands (Lululemon, Gymshark, Fabletics). Each comment is treated as its own “document,” the same way each line was its own document in the tutorial’s SpaceX example. There are 749 comments found.

Step 1: Load the Data

athleisure_raw <- read_csv("athleisure_comments.csv")

athleisure_text <- athleisure_raw %>%
  mutate(id = row_number()) %>%
  select(id, text = textOriginal)

athleisure_text
## # A tibble: 749 × 2
##       id text                                                                   
##    <int> <chr>                                                                  
##  1     1 "There's two persons in this world. People who find a shirt that fits …
##  2     2 "Now everyone looks like they are hungover walking out to grab a break…
##  3     3 "I was already feeling sick of popular gym clothes and this gives me e…
##  4     4 "If food doesn't kill you. Your clothing will."                        
##  5     5 "200 pound \"whales\" in yoga pants scare me."                         
##  6     6 "😂"                                                                   
##  7     7 "Obesity."                                                             
##  8     8 "Underarmor is better then gymshark"                                   
##  9     9 "I'm not gonna lie: I fell for Gymshark. I haven't supported them the …
## 10    10 "Man, I’m trying to build a public playlist with all of your videos so…
## # ℹ 739 more rows

Step 2: Tokenize and Clean

Real-world text needs cleanup before analysis — punctuation, emojis, and numbers need to be handled, and generic filler words need to be removed as stop words.

Task: Add 1–2 of your own stop words below after reviewing the word counts — look for words that are frequent only because they show up in most comments, not because they carry meaning.

custom_stop_words <- bind_rows(
  stop_words,
  tibble(word = c("im", "ive", "dont", "didnt", "isnt", "lol", "ve", "don", "isn", "doesn", "video"), lexicon = "custom")
)

athleisure_words <- athleisure_text %>%
  mutate(text = str_replace_all(text, "[^[:alnum:][:space:]$]", " ")) %>%
  unnest_tokens(word, text, token = "words") %>%
  filter(!str_detect(word, "^[0-9]+$")) %>%
  anti_join(custom_stop_words, by = "word")

athleisure_words %>% count(word, sort = TRUE) %>% head(15)
## # A tibble: 15 × 2
##    word            n
##    <chr>       <int>
##  1 wear          261
##  2 gym           203
##  3 clothes       172
##  4 people        133
##  5 clothing      111
##  6 cotton         95
##  7 wearing        81
##  8 shorts         72
##  9 athleisure     64
## 10 buy            62
## 11 pants          62
## 12 comfortable    61
## 13 athletic       56
## 14 gymshark       52
## 15 stuff          52

Step 3: Count Word Pairs

athleisure_pairs <- athleisure_words %>%
  pairwise_count(word, id, sort = TRUE, upper = FALSE)

athleisure_pairs %>% head(15)
## # A tibble: 15 × 3
##    item1    item2           n
##    <chr>    <chr>       <dbl>
##  1 gym      wear           61
##  2 clothes  wear           46
##  3 gym      clothes        45
##  4 clothing wear           40
##  5 wear     cotton         33
##  6 people   wear           32
##  7 wear     athletic       31
##  8 people   gym            30
##  9 pants    wear           30
## 10 wear     athleisure     30
## 11 wear     shorts         28
## 12 wear     comfortable    28
## 13 wear     wearing        26
## 14 wear     workout        24
## 15 people   clothes        23

Step 4: Build the Co-occurrence Matrix

top_words <- athleisure_words %>%
  count(word, sort = TRUE) %>%
  slice_max(n, n = 15) %>%
  pull(word)

athleisure_matrix <- athleisure_pairs %>%
  filter(item1 %in% top_words, item2 %in% top_words) %>%
  bind_rows(athleisure_pairs %>% rename(item1 = item2, item2 = item1) %>%
              filter(item1 %in% top_words, item2 %in% top_words)) %>%
  cast_sparse(item1, item2, n) %>%
  as.matrix()

athleisure_matrix
##             wear clothes cotton athletic gym athleisure shorts comfortable
## gym           61      45     20       17   0         17     20          20
## clothes       46       0     21       13  45         16     12          11
## clothing      40      19     15       18  21         16      6           9
## wear           0      46     33       31  61         30     28          28
## people        32      23      7       11  30         19     16          12
## pants         30       8      5        5  15          7      4           9
## cotton        33      21      0       12  20          7     13          12
## athleisure    30      16      7        9  17          0      7          13
## shorts        28      12     13        5  20          7      0          11
## gymshark      10       9      1        1  14          4      4           3
## wearing       26      22     10       10  19          9     10          10
## buy           22      19     12        4  14          9      9           9
## comfortable   28      11     12        6  20         13     11           0
## athletic      31      13     12        0  17          9      5           6
## stuff         18       9      5        5   9          5      8           7
##             wearing clothing buy stuff pants gymshark people
## gym              19       21  14     9    15       14     30
## clothes          22       19  19     9     8        9     23
## clothing         20        0   9    14    11        4     23
## wear             26       40  22    18    30       10     32
## people           22       23  11    13     9        5      0
## pants            12       11   6     1     0        2      9
## cotton           10       15  12     5     5        1      7
## athleisure        9       16   9     5     7        4     19
## shorts           10        6   9     8     4        4     16
## gymshark          6        4   7     3     2        0      5
## wearing           0       20   8     5    12        6     22
## buy               8        9   0     7     6        7     11
## comfortable      10        9   9     7     9        3     12
## athletic         10       18   4     5     5        1     11
## stuff             5       14   7     0     1        3     13

Step 5: Visualize the Strongest Co-occurrences

Task: Try a different value of n in the filter below (Question 2 asks about this).

set.seed(580)

athleisure_pairs %>%
  filter(n > 5) %>%
  graph_from_data_frame() %>%
  ggraph(layout = "fr") +
  geom_edge_link(aes(edge_alpha = n, edge_width = n, color = n)) +
  scale_edge_color_gradient(low = "yellow", high = "darkorange") +
  geom_node_point(size = 3, color = "darkgreen") +
  geom_node_text(aes(label = name), repel = TRUE, size =4) +
  theme_void() +
  labs(
    title = "Word Co-occurrence Network: Athleisure YouTube Comments",
    subtitle = "Edge thickness and color = number of comments containing both words"
  )


Reflection Questions

Question 1: Which additional words did you add to the stopword list manually? Why does it make sense to remove those words as stopwords? Does the final network graph change after removing these additional stopwords? If so, briefly explain how and why.

After I analyzed the word co-occurence network, I added “ve”, “don”, “isn”, “doesn” to the stop words. These words do not provide meaningful insight since these are incomplete. I also added “video” since we are extracting comments from YouTube.

Question 2: Change n in filter(n > 1) in Step 5 to a different value. What is your final value of n? Will it give you a better result?

I had to change it to n > 5 because there were too many pairs and it became difficult to analyze. Making this adjustment provided me a cleaner visual. I tried to adjust the threshold to 8 and 10 but I feel like it was removing important insights. For example, I see another brand, nike, being mentioned since it is not one of three brands I extracted. If I adjusted it to 8, it disappears. Since another brand was mentioned, it is good to take note since most likely, the consumer is comparing their products to this brand.

Question 3: On the professor’s BlueSky post, there were two co-occurrence network graphs. Which one do you like better? Why?

I think each graph could provide its own importance so its a bit difficult to decide. The left one with a lower threshold can show a wide variety of patterns and word associations that consumers say, possibly towards the brand and not just a specific product. The one on the right doesn’t really give me much insight other than maybe the customer’s experience with the coffee product.

Question 4: Which word pairs surprised you? Did any positive/enthusiastic words cluster more than critical or skeptical ones?

I did not see any word pairs that were surprising or unsual when it comes to athleisure.

Question 5: Should we treat stock tickers like $SPCX and SpaceX as the same token instead of separate ones?

I think $SPCX and SpaceX should be treated as the same token if it has the same meaning. The only reason I would possible split them is maybe when it comes to investment where $SPCX is a digital token, whereas SPCX is the real NASDAQ stock.

Question 6: What’s the risk of drawing conclusions from a co-occurrence network built on only a small sample of comments? What is a good sample size? How would you communicate that limitation in a business report?

A small sample may provide incorrect insights, since there is not enough words for co-occurences. A lot of times when it comes to YouTube, people make random comments that may not necessarily be related to the brand/product. I think a good sample size would really depend on the company in regards to what questions they want answered. I would communicate this limitation in a business report by giving a disclaimer that this sample does not represent the consumer market as a whole and emphasize that this provides a small insight for further research

Question 7: Is there a way to analyze comments or reviews on topics that interest you in relation to a significant event? If so, how would you identify the event? Why is that event significant, and how might it influence the comments or reviews you analyze?

I think comparing comments before and after a significant event can provide insight about current consumer sentiments. For example, if a company experiences some sort of controversy, it can negatively affect the company’s image where the consumer’s focus is now on that negative event instead of the company’s products/services.