1. Executive summary

In the world of cinema, reviews are more than just opinions. They are fragments of emotion, voices from anonymous viewers — people who laughed out loud at a masterpiece or quietly hit pause in disappointment.

In this project, I explore 50,000 IMDB movie reviews to listen to those voices and ask: “How does the language in a ‘good’ review differ from that in a ‘bad’ one”? I apply modern text analysis techniques such as TF-IDF, word clouds, co-occurrence networks, and pairwise correlation analysis to peel back the layers of words that audiences leave behind.

The results reveal something fascinating: positive reviews tend to be lyrical, emotional, and personal, while negative reviews are often cold, judgmental, and at times, angry. I believe that in a silent movie theater, the loudest echoes are not made by applause — but by words.

Beyond academic curiosity, this analysis offers insights for building smarter recommendation systems, more empathetic marketing strategies, and a deeper understanding of how language reflects emotion. How we write is how we feel — and this project gave me a front-row seat to that truth.

2. Data background

The dataset comes from the IMDB Large Movie Review Dataset, a widely used benchmark in natural language processing (NLP) tasks, created by Andrew Maas and colleagues at Stanford University. It contains 50,000 English-language movie reviews labeled as either positive or negative, with an even split (25,000 per class). Each entry is a full free-text review from real users on IMDB, reflecting subjective opinions, emotional expression, and storytelling style. Reviews were pre-cleaned to remove rating scores and author information to avoid bias. This structured yet natural dataset provides a rich playground to explore how ordinary people use language to express satisfaction, anger, love, or regret about films. The binary classification format and large volume make it ideal for both exploratory text mining and supervised learning tasks.

3. Data loading, cleaning and preprocessing

##Data Preparation and Cleaning

I began by loading a set of R packages required for data import, cleaning, natural language processing, and visualization. These include tidyverse and tidytext for text mining, textclean for cleaning raw HTML and formatting issues, and ggplot2, igraph, and ggraph for visual exploration and network graphing.

After loading the IMDB movie review dataset using read_csv(), I created a filtered subset that only includes reviews labeled as “positive” or “negative” in the sentiment column. This ensures that all further analysis focuses only on clearly defined emotional polarities, excluding any undefined or ambiguous cases.

To prepare the text for analysis, I performed tokenization using unnest_tokens(), which breaks each review down into individual lowercase words (one per row). I then removed common stop words such as “the”, “is”, and “and” using the stop_words dataset from the tidytext package and anti_join(). This helps eliminate noise and retain only the content-rich words that better reflect emotional tone and thematic patterns in the reviews.

The result is a tidy dataset named words, where each row represents a meaningful word extracted from a positive or negative review — ready for further analysis such as frequency counting, tf-idf.

library(textclean)    
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.4     ✔ tidyr     1.3.1
## ✔ purrr     1.0.4     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(tidytext)
library(ggplot2)
library(ggraph)
library(igraph)
## 
## 다음의 패키지를 부착합니다: 'igraph'
## 
## The following objects are masked from 'package:lubridate':
## 
##     %--%, union
## 
## The following objects are masked from 'package:dplyr':
## 
##     as_data_frame, groups, union
## 
## The following objects are masked from 'package:purrr':
## 
##     compose, simplify
## 
## The following object is masked from 'package:tidyr':
## 
##     crossing
## 
## The following object is masked from 'package:tibble':
## 
##     as_data_frame
## 
## The following objects are masked from 'package:stats':
## 
##     decompose, spectrum
## 
## The following object is masked from 'package:base':
## 
##     union
library(widyr)
library(wordcloud)
## 필요한 패키지를 로딩중입니다: RColorBrewer
# Load the data
IMDB <- read_csv("IMDB Dataset.csv")
## Rows: 50000 Columns: 2
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (2): review, sentiment
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
reviews_subset <- IMDB %>%
  filter(sentiment %in% c("positive", "negative"))
reviews_subset
## # A tibble: 50,000 × 2
##    review                                                              sentiment
##    <chr>                                                               <chr>    
##  1 "One of the other reviewers has mentioned that after watching just… positive 
##  2 "A wonderful little production. <br /><br />The filming technique … positive 
##  3 "I thought this was a wonderful way to spend time on a too hot sum… positive 
##  4 "Basically there's a family where a little boy (Jake) thinks there… negative 
##  5 "Petter Mattei's \"Love in the Time of Money\" is a visually stunn… positive 
##  6 "Probably my all-time favorite movie, a story of selflessness, sac… positive 
##  7 "I sure would like to see a resurrection of a up dated Seahunt ser… positive 
##  8 "This show was an amazing, fresh & innovative idea in the 70's whe… negative 
##  9 "Encouraged by the positive comments about this film on here I was… negative 
## 10 "If you like original gut wrenching laughter you will like this mo… positive 
## # ℹ 49,990 more rows
#Tokenization and Stop Word Removal
data("stop_words")

words <- reviews_subset %>%
  select(sentiment, review) %>%
  unnest_tokens(word, review) %>%
  anti_join(stop_words)
## Joining with `by = join_by(word)`
words
## # A tibble: 4,601,629 × 2
##    sentiment word     
##    <chr>     <chr>    
##  1 positive  reviewers
##  2 positive  mentioned
##  3 positive  watching 
##  4 positive  1        
##  5 positive  oz       
##  6 positive  episode  
##  7 positive  hooked   
##  8 positive  happened 
##  9 positive  br       
## 10 positive  br       
## # ℹ 4,601,619 more rows

Figure 1: Bar Chart of Top 10 Most Frequent Words by Sentiment

To better understand how language differs between positive and negative movie reviews, I conducted a sentiment-specific word frequency analysis using the Bing Lexicon. This allowed to assign a polarity (positive or negative) to each word in the dataset. This analysis helps illustrate how word choice serves as an emotional signal, and supports automatic sentiment classification or recommendation systems based on user feedback.

data("stop_words")
bing <- get_sentiments("bing")

#Adds a unique review_id to each review in the IMDB dataset for easier processing
IMDB <- IMDB %>%
  mutate(review_id = row_number())  

tidy_reviews <- IMDB %>%
  unnest_tokens(word, review) %>%
  anti_join(stop_words, by = "word") %>%
  select(review_id, sentiment, word)

#Assigns each word a label of "positive" or "negative" if found in the bing sentiment dictionary
review_words_sentiment <- tidy_reviews %>%
  inner_join(bing, by = "word")  
## Warning in inner_join(., bing, by = "word"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1268687 of `x` matches multiple rows in `y`.
## ℹ Row 5781 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.
#Count the Most Common Sentiment Words
top_sentiment_words <- review_words_sentiment %>%
  rename(sentiment_bing = sentiment.y) %>%  
  filter(sentiment_bing %in% c("positive", "negative")) %>%
  count(sentiment_bing, word, sort = TRUE) %>%
  group_by(sentiment_bing) %>%
  slice_max(n, n = 10) %>%
  ungroup()

#Plot Bar Chart Comparing Positive and Negative Words
ggplot(top_sentiment_words, aes(x = reorder_within(word, n, sentiment_bing),
                                y = n,
                                fill = sentiment_bing)) +
  geom_col(show.legend = FALSE) +
  coord_flip() +
  facet_wrap(~ sentiment_bing, scales = "free") +
  scale_x_reordered() +
  labs(title = "Top 10 Words in Positive and Negative IMDB Reviews",
       x = NULL, y = "Word Frequency") +
  theme_minimal()

I chose a faceted horizontal bar chart because it clearly shows the differences in word usage between the two sentiment groups. Bar charts are ideal for comparing quantities — in this case, word frequency — across categories. Faceting the plot by sentiment allows viewers to compare both groups side by side in an intuitive and readable layout. This bar chart effectively communicates the core idea of this part of the analysis: certain words appear significantly more often in positive or negative reviews, and these patterns are central to understanding sentiment in natural language.

Words like “bad,” “plot,” “worst,” “boring,” and “wrong” frequently appear in negative reviews. These terms indicate dissatisfaction with the story structure and overall execution. Interestingly, emotionally heavy words such as “death” and “dead” also appear, suggesting that films with darker or more serious themes tend to evoke more negative reactions. Additionally, words like “poor” and “hard” suggest disappointment with performance or possibly complexity in narrative structure that hindered viewer engagement. Words like “plot,” “worst,” and “boring” show that narrative dissatisfaction is a major driver for negative feedback.

In contrast, words such as “love,” “pretty,” “fun,” “beautiful,” and “excellent” dominate positive reviews. These words evoke emotions of joy, admiration, and aesthetic appreciation. “Enjoy,” “classic,” and “top” further reflect personal satisfaction and value perception. The presence of words like “fun” and “pretty” suggests that entertainment and visual appeal significantly influence positive reception. Positive reviewers tend to use more emotionally expressive adjectives such as “beautiful” and “excellent,” pointing to higher affective involvement.

Figure 2: Top 10 Words by TF-IDF in Positive vs. Negative Reviews

To identify emotionally meaningful vocabulary in movie reviews, I implemented a TF-IDF-based analysis combined with the Bing sentiment lexicon. The goal was to move beyond raw word frequency and instead focus on words that are both statistically significant and emotionally interpretable. The original dataset uses a column named sentiment to indicate the review’s label (positive or negative). Since the Bing sentiment dictionary also contains a column named sentiment, I renamed the review label to review_sentiment to avoid column name conflicts when joining the data sets.

#Load Bing Lexicon
bing <- get_sentiments("bing")

#Rename Column to Avoid Conflicts
words <- words %>% rename(review_sentiment = sentiment)

#Compute TF-IDF Scores
tfidf_sentiment <- words %>%
  count(review_sentiment, word, sort = TRUE) %>%
  bind_tf_idf(word, review_sentiment, n) %>%
  inner_join(get_sentiments("bing"), by = "word") #Join with Bing Lexicon 
## Warning in inner_join(., get_sentiments("bing"), by = "word"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 53249 of `x` matches multiple rows in `y`.
## ℹ Row 4621 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.
#Select Top Words by Sentiment
top_tfidf_sentiment <- tfidf_sentiment %>%
  group_by(sentiment) %>%
  slice_max(tf_idf, n = 10, with_ties = FALSE) %>%
  ungroup()

#Visualize with ggplot2
ggplot(top_tfidf_sentiment, aes(x = reorder_within(word, tf_idf, sentiment),
                                y = tf_idf,
                                fill = sentiment)) +
  geom_col(show.legend = FALSE) +
  coord_flip() +
  facet_wrap(~ sentiment, scales = "free") +
  scale_x_reordered() +
  labs(title = "Top 10 Emotionally Significant TF-IDF Words in IMDB Reviews",
       x = NULL, y = "TF-IDF Score") +
  theme_minimal()

The most distinct words in negative reviews include: “uncreative”, “harrow”, “mashed”, “hissing”, “incoherently”, “irredeemably”. These terms evoke strong disapproval, either about creativity (uncreative), narrative clarity (incoherently), or emotional discomfort (harrow, hissing). The word “dragoon”—less commonly used—might reflect frustration with forceful or contrived storytelling.Several of these words suggest not just dissatisfaction, but deep emotional detachment or even contempt, with words like irredeemably and smother signaling irreversibly negative reactions. Unlike neutral complaints like “bad” or “boring,” words like incoherently and irredeemably reflect judgmental finality. Viewers aren’t just unimpressed—they’re declaring the experience a lost cause.

On the positive side, we see words like: “openness”, “contentment”, “enchant”, “jubilant”, “intriguingly”, “refunded”. These words project emotional depth and appreciation, often extending beyond surface-level praise. Words like “openness” and “contentment” are emotionally nuanced, indicating that positive reviews may emphasize personal resonance or emotional growth, not just entertainment value.Notably, “refunded” appears in the positive list—possibly in a humorous or ironic context. Positive reviews use more abstract, internal-emotion language (contentment, openness, jubilant) rather than superficial praise (nice, good). This may suggest that highly positive reviews stem from emotionally invested experiences.

Without TF-IDF, most frequent words tend to be generic (good, bad, funny). TF-IDF allows us to surface less frequent but more expressive vocabulary.

Figure 3: Word Cloud Visualization for Sentiment Analysis

After identifying the top 10 most frequent words by sentiment through TF-IDF analysis, I used wordcloud visualizations to highlight the broader emotional vocabulary patterns in both positive and negative reviews. While the tables focused on the most statistically weighted words, the wordclouds provide a visual overview of the wider sentiment distribution.

Word clouds offer an intuitive and immediate way to identify dominant sentiment-bearing vocabulary, highlighting words by their relative frequency in a given context. In this case, separate word clouds were generated for positive and negative reviews using the Bing sentiment lexicon, enabling a direct visual comparison of the emotional language employed in each sentiment group. This method serves not only as a visually engaging summary but also reinforces prior statistical findings with qualitative textual evidence.

#Load necessary libraries
library(tidyverse)
library(tidytext)
library(wordcloud)
#Tokenization and Stopword Removal
words <- IMDB %>%
  unnest_tokens(word, review) %>%
  anti_join(stop_words, by = "word")

set.seed(1234)
#Word Cloud for Positive Sentiment Words
words %>%
  inner_join(get_sentiments("bing") %>% filter(sentiment == "positive"), by = "word") %>%
  count(word, sort = TRUE) %>%
  with(wordcloud(word, n, max.words = 50, colors = "steelblue"))  #The color steelblue visually emphasizes positivity

#Tokenization and Stopword Removal
words <- IMDB %>%
  unnest_tokens(word, review) %>%
  anti_join(stop_words, by = "word")
set.seed(1234)
#Word Cloud for Negative Sentiment Words
words %>%
  inner_join(get_sentiments("bing") %>% filter(sentiment == "negative"), by = "word") %>%
  count(word, sort = TRUE) %>%
  with(wordcloud(word, n, max.words = 50, colors = "firebrick"))  #The resulting word cloud shows the most common negative expressions using a firebrick (reddish) tone, symbolizing emotional negativity

The first word cloud (in steelblue) highlights vocabulary frequently used in positive movie reviews. Terms like “love,” “beautiful,” “classic,” “recommend,” “perfect,” “fun,” and “entertaining” appear prominently. These words reflect emotionally charged praise and signal strong viewer engagement and satisfaction. Words such as love, amazing, and beautiful point to emotional enjoyment and visual admiration. Terms like excellent, masterpiece, and top suggest reviewers are evaluating the film’s quality based on narrative, performance, or production. Words like recommend, worth, and fun indicate that positive reviewers often encourage others to watch, reflecting confidence and enthusiasm. Good reviews frequently use emotionally expressive adjectives and positive judgments.

In contrast, the second word cloud (in brickfire) reveals the vocabulary used in negative reviews. Words such as “plot,” “worst,” “boring,” “terrible,” “funny” (ironically used), “death,” “stupid,” and “poor” dominate the space. Plot, story, line, and slow suggest that narrative construction is a frequent point of dissatisfaction. Words like worst, waste, annoying, and disappointed express strong discontent. Terms like stupid, boring, death, crap, and hell reflect a direct, blunt style typical of negative evaluations. The word funny may be used sarcastically in contexts like “not funny” or “tries to be funny,” indicating failed expectations.

Positive reviews express joy, admiration, and delight through descriptive and often poetic language, while negative reviews show frustration, anger, or disappointment in a more direct and coarse manner.These contrasting word clouds provide clear evidence of how sentiment influences not only word choice but also tone and communicative purpose. Good reviews tend to highlight what the film gave, while bad reviews emphasize what the film failed to deliver.

Figure 4: The co-occurrence patterns of emotionally charged words

This script analyzes the co-occurrence patterns of emotionally charged words in IMDB movie reviews to investigate how language differs between ‘good’ (positive) and ‘bad’ (negative) reviews.

Rather than focusing solely on word frequency, the co-occurrence analysis reveals how sentiment-laden words cluster and interact within reviews. By visualizing these patterns, I can observe distinct linguistic structures: for instance, positive reviews often group words expressing joy, admiration, or satisfaction, whereas negative reviews may cluster around themes of disappointment, frustration, or criticism.

This network-based approach allows to uncover semantic associations and emotional framing that differ between positive and negative reviews, offering deeper insight into how language reflects sentiment.

#Load necessary libraries
library(text2vec)
## 
## 다음의 패키지를 부착합니다: 'text2vec'
## The following object is masked from 'package:igraph':
## 
##     normalize
library(tidyverse)
library(tidytext)
library(widyr)
library(tidygraph)
## 
## 다음의 패키지를 부착합니다: 'tidygraph'
## The following object is masked from 'package:igraph':
## 
##     groups
## The following object is masked from 'package:stats':
## 
##     filter
library(ggraph)

#Load the IMDB dataset and assign unique review IDs
IMDB <- read_csv("IMDB Dataset.csv") %>%
  mutate(review_id = row_number())  # thêm ID duy nhất
## Rows: 50000 Columns: 2
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (2): review, sentiment
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
#Tokenize text and remove stopwords
review_tidy <- IMDB %>%
  unnest_tokens(word, review) %>%
  filter(!word %in% c("br")) %>%
  anti_join(stop_words, by = "word") %>%
  select(review_id, sentiment, word)

#Add sentiment labels using the Bing lexicon
bing <- get_sentiments("bing")

review_sentiment <- review_tidy %>%
  inner_join(bing, by = "word")
## Warning in inner_join(., bing, by = "word"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1212158 of `x` matches multiple rows in `y`.
## ℹ Row 5781 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.
#Split into positive and negative sentiment subsets
positive_words <- review_sentiment %>% filter(sentiment.y == "positive")
negative_words <- review_sentiment %>% filter(sentiment.y == "negative")

#Compute word pair co-occurrences
positive_pair <- positive_words %>%
  pairwise_count(item = word, feature = review_id, sort = TRUE)

negative_pair <- negative_words %>%
  pairwise_count(item = word, feature = review_id, sort = TRUE)

#Convert co-occurrence data to graph objects
positive_graph <- positive_pair %>%
  filter(n >= 200) %>%
  as_tbl_graph()

negative_graph <- negative_pair %>%
  filter(n >= 200) %>%
  as_tbl_graph()

# Visualize the word co-occurrence networks(positive)
set.seed(1234)   # fix a random number

ggraph(positive_graph, layout = "fr") +
  geom_edge_link(color = "steelblue", alpha = 0.5) +
  geom_node_point(color = "skyblue", size = 4) +
  geom_node_text(aes(label = name), repel = TRUE, size = 4) +
  theme_graph() +
  labs(title = "Positive Word Co-occurrence Network")
## Warning in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): 윈도우즈
## 폰트데이터베이스에서 찾을 수 없는 폰트페밀리입니다
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## 윈도우즈 폰트데이터베이스에서 찾을 수 없는 폰트페밀리입니다
## Warning: ggrepel: 1 unlabeled data points (too many overlaps). Consider
## increasing max.overlaps
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## 윈도우즈 폰트데이터베이스에서 찾을 수 없는 폰트페밀리입니다

# Visualize the word co-occurrence networks(negative)
set.seed(1234)   # fix a random number

ggraph(negative_graph, layout = "fr") +
  geom_edge_link(color = "firebrick", alpha = 0.5) +
  geom_node_point(color = "red", size = 4) +
  geom_node_text(aes(label = name), repel = TRUE, size = 4) +
  theme_graph() +
  labs(title = "Negative Word Co-occurrence Network")
## Warning: ggrepel: 8 unlabeled data points (too many overlaps). Consider increasing max.overlaps
## 윈도우즈 폰트데이터베이스에서 찾을 수 없는 폰트페밀리입니다

Positive Word Co-occurrence Network represents the most frequently co-occurring words that have positive emotional valence in the IMDB movie reviews. Words such as “love”, “beautiful”, “amazing”, and “perfect” are positioned centrally, connecting to many other positive words like “wonderful,” “classic,” “talent,” “enjoyed,” “recommend,” and “excellent.”

Interpretation: These words indicate that users frequently express positive emotions by complimenting the story, visual quality, or acting performance.The connection between “recommend” → “excellent” → “worth” reveals a pattern where satisfaction leads to advocacy. The overall tone is optimistic, appreciative, and emotionally expressive(“hilarious,” “romantic,” “memorable,” and “masterpiece”)

Negative Word Co-occurrence Network shows the structure of how negative words are used together in low-rated or critical reviews. Words like “bad”, “worst”, “disappointed”, “boring”, “plot,” “awful,” and “garbage” form dense central nodes. Words such as “disappointed,” “disappointment,” “terrible,” “waste,” and “crap” suggest user frustration and emotional rejection. Words like “confusing,” “cheesy,” “trash,” and “slow” are often linked with narrative or production quality. The repetition of “kills,” “murder,” “zombie,” “died,” etc., may reflect not only horror genre themes but also metaphoric criticism of the film’s quality.

Emotion tone: Negative reviews are characterized by anger, frustration, and dissatisfaction.

Good reviews emphasize enjoyment, aesthetics, and emotional connection, while bad reviews center around disappointment, boredom, and narrative failure. In my observation, the contrast between positive and negative word networks reveals not only emotional polarity but also depth of viewer engagement. Viewers who loved a movie used emotionally nuanced words (e.g., “heartwarming,” “stunning”), while negative reviewers used blunt, emotionally charged criticisms (e.g., “trash,” “worst,” “garbage”). This indicates that emotional vocabulary not only expresses sentiment but also encodes the reviewer’s level of investment and expectation in the viewing experience.

Figure 5: Top Correlated Words by Target Word

In this section, I examine how emotionally charged words are contextually used in movie reviews by analyzing their co-occurring terms through pairwise correlation. Specifically, the target words “boring”, “funny”, “bad”, and “worth” were selected as representative emotional cues commonly found in either positive or negative reviews.

The purpose of this analysis is to uncover how language usage differs between ‘good’ and ‘bad’ reviews by looking at the semantic neighborhoods around these key words. For instance, identifying which words most frequently co-occur with “boring” in negative reviews or “funny” in positive reviews reveals how opinions are linguistically framed depending on sentiment.

This insight contributes to understanding not only what emotional words are used, but also how they are supported, intensified, or contextualized in different sentiment settings. It offers a deeper perspective on the ways in which users express appreciation or dissatisfaction in film critique language.

# Text Tokenization and Cleaning
review_tidy <- IMDB %>%
  unnest_tokens(word, review) %>%
  filter(!word %in% c("br")) %>%
  anti_join(stop_words, by = "word") %>%
  select(review_id, sentiment, word)

#Compute pairwise correlation between words appearing in the same review
word_cors <- review_tidy %>%
  add_count(word) %>%
  filter(n >= 250) %>%    # Only consider words with sufficient frequency
  pairwise_cor(item = word, feature = review_id, sort = TRUE)

#Define the target words for correlation analysis
target_words <- c("boring", "funny", "bad", "worth")

#Get the top 8 most correlated words for each target word
top_cors <- word_cors %>%
  filter(item1 %in% target_words) %>%
  group_by(item1) %>%
  slice_max(correlation, n = 8)
top_cors
## # A tibble: 32 × 3
## # Groups:   item1 [4]
##    item1  item2    correlation
##    <chr>  <chr>          <dbl>
##  1 bad    acting        0.166 
##  2 bad    worst         0.158 
##  3 bad    worse         0.134 
##  4 bad    guy           0.131 
##  5 bad    guys          0.129 
##  6 bad    awful         0.127 
##  7 bad    terrible      0.120 
##  8 bad    movie         0.114 
##  9 boring dull          0.107 
## 10 boring minutes       0.0814
## # ℹ 22 more rows
#Set factor levels to control display order in the plot
top_cors$item1 <- factor(top_cors$item1, levels = target_words)

#Create a bar plot for top correlated words
ggplot(top_cors, aes(x = reorder_within(item2, correlation, item1),
                     y = correlation,
                     fill = item1)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~ item1, scales = "free") +
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Top Correlated Words by Target Word",
       x = "Associated Word", y = "Correlation")

Words like “boring” and “bad” are strongly linked to concrete experiences (e.g., acting, slow pacing), indicating that viewers often provide detailed critiques when expressing dissatisfaction. On the other hand, “funny” pulls in a highly cohesive semantic field, emphasizing genre alignment and audience engagement. The case of “worth” shows the evaluative flexibility of language in reviews—it can be used in both positive and negative contexts depending on adjacent terms like price or renting.

This analysis demonstrates that sentiment is not merely a matter of labeling words as positive or negative but rather understanding how contextual associations amplify or clarify emotional expression. The pairwise correlation method reveals the latent structure of viewer feedback, offering rich linguistic insight that goes beyond surface-level word counts.

4. Conclusion

In this project, I employed a range of text mining and sentiment analysis techniques to explore the language used in IMDB movie reviews. By counting the most frequently used words within each sentiment class, I was able to observe dominant themes and lexical choices that define the tone of each review type. By leveraging the TF-IDF method, I identified emotionally significant words that differentiate positive and negative reviews. I further visualized language patterns using word clouds, co-occurrence networks, and correlation-based analysis to uncover how specific words are used in context. Finally, through pairwise correlation analysis, we discovered strong lexical associations surrounding key evaluative words such as “boring”, “funny”, “bad”, and “worth”.

Overall, the analysis confirms that negative reviews tend to be more detailed, emotionally charged, and evaluative in tone, while positive reviews often emphasize enjoyment, humor, and aesthetic value. These findings offer practical insights for natural language processing tasks such as sentiment classification, as well as for marketing and recommendation systems aiming to better understand user perception.