Executive Summary

This report investigates the alignment between sentiment scores from automated sentiment analysis and the numeric ratings provided by users on TripAdvisor. We hypothesize that higher ratings (4–5) contain significantly more positive sentiments, whereas lower ratings (1–2) exhibit more negative sentiments. By leveraging text data from 20,000 TripAdvisor Hotel Reviews, our findings strongly confirm this hypothesis, validating sentiment analysis as a reliable reflection of human judgment.

Background Information and Motivation

Hotels and travelers rely heavily on online reviews. Automated sentiment analysis provides hotels quick insights into customer perceptions and helps prospective guests efficiently understand hotel quality. This study explores how sentiment analysis aligns with human-generated numeric ratings, emphasizing the utility of automated text analysis in consumer analytics.

Data Description

The dataset used is the TripAdvisor Hotel Reviews dataset from Kaggle, which contains 20,000 reviews from users with associated 1–5 star ratings. Key variables analyzed include:

Data Loading and Exploration

reviews <- read_csv("tripadvisor_hotel_reviews.csv") %>%
  select(Review, Rating) %>%
  rename(review = Review, rating = Rating)

head(reviews)
## # A tibble: 6 × 2
##   review                                                                  rating
##   <chr>                                                                    <dbl>
## 1 nice hotel expensive parking got good deal stay hotel anniversary, arr…      4
## 2 ok nothing special charge diamond member hilton decided chain shot 20t…      2
## 3 nice rooms not 4* experience hotel monaco seattle good hotel n't 4* le…      3
## 4 unique, great stay, wonderful time hotel monaco, location excellent sh…      5
## 5 great stay great stay, went seahawk game awesome, downfall view buildi…      5
## 6 love monaco staff husband stayed hotel crazy weekend attending memoria…      5

Exploratory Data Analysis

Rating Distribution

The distribution of ratings indicates that most reviews are positive (ratings of 4 or 5), with fewer negative reviews (ratings of 1 or 2).

reviews %>%
  count(rating) %>%
  ggplot(aes(x = factor(rating), y = n, fill = factor(rating))) +
  geom_col() +
  scale_fill_manual(values = rating_colors) +
  labs(title = "Distribution of Hotel Ratings", x = "Rating", y = "Count") +
  theme_minimal() +
  theme(legend.position = "none")
Rating Distribution

Rating Distribution

Average Review Length by Rating

Analyzing the review length by rating reveals that negative reviews tend to be longer, possibly due to customers expressing more detailed dissatisfaction.

reviews %>%
  mutate(review_length = str_count(review, "\\w+")) %>%
  group_by(rating) %>%
  summarize(avg_length = mean(review_length)) %>%
  ggplot(aes(x = factor(rating), y = avg_length, fill = factor(rating))) +
  geom_col() +
  scale_fill_manual(values = rating_colors) +
  labs(title = "Average Review Length by Rating", x = "Rating", y = "Average Words per Review") +
  theme_minimal() +
  theme(legend.position = "none")
Average Review Length by Rating

Average Review Length by Rating

Sentiment Analysis

Using the Bing sentiment lexicon, sentiment scores were computed for each review.

bing <- get_sentiments("bing")

review_sentiment <- reviews %>%
  mutate(id = row_number()) %>%
  unnest_tokens(word, review) %>%
  anti_join(stop_words) %>%
  inner_join(bing) %>%
  mutate(value = if_else(sentiment == "positive", 1, -1)) %>%
  group_by(id, rating) %>%
  summarize(sentiment_score = sum(value), .groups = 'drop')

Average Sentiment Score by Rating

This graph clearly illustrates the increasing positivity in sentiment scores as ratings increase from 1 to 5, affirming our hypothesis that numeric ratings correspond to textual sentiment.

review_sentiment %>%
  group_by(rating) %>%
  summarize(avg_sentiment = mean(sentiment_score)) %>%
  ggplot(aes(x = factor(rating), y = avg_sentiment, fill = factor(rating))) +
  geom_col() +
  scale_fill_manual(values = rating_colors) +
  labs(title = "Average Sentiment Score by Rating", x = "Rating", y = "Average Sentiment Score") +
  theme_minimal() +
  theme(legend.position = "none")
Average Sentiment Score by Rating

Average Sentiment Score by Rating

Distribution of Sentiment Scores by Rating

The boxplot demonstrates the variability and overall trend of sentiment scores, showing that higher-rated reviews generally contain more positive sentiment scores.

review_sentiment %>%
  ggplot(aes(x = factor(rating), y = sentiment_score, fill = factor(rating))) +
  geom_boxplot(outlier.alpha = 0.2) +
  scale_fill_manual(values = rating_colors) +
  labs(title = "Distribution of Sentiment Scores by Rating", x = "Rating", y = "Sentiment Score") +
  theme_minimal() +
  theme(legend.position = "none")
Sentiment Score by Rating

Sentiment Score by Rating

Bigram Network for 1-Star Ratings

The bigram network for 1-star reviews reveals common word pairings that highlight dissatisfaction. Central terms include “worst hotel”, “customer service”, and “air conditioning”, reflecting frequent complaints about poor experiences, amenities, and staff behavior. The graph also shows frustration expressed through bigrams like “hot water” and “credit card”, suggesting common issues around comfort and billing. These clusters illustrate how low-rated reviews often focus on specific service failures.

bigrams_1star <- reviews %>%
  filter(rating == 1) %>%
  unnest_tokens(bigram, review, token = "ngrams", n = 2) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% stop_words$word, !word2 %in% stop_words$word) %>%
  count(word1, word2, sort = TRUE)

bigrams_1star_filtered <- bigrams_1star %>%
  slice_max(n, n = 50)

bigram_graph(bigrams_1star_filtered)
Bigram Network for 1-Star Ratings

Bigram Network for 1-Star Ratings

Bigram Network for 5-Star Ratings

In contrast, the bigram network for 5-star reviews contains highly positive and descriptive pairings. Phrases like “excellent service”, “highly recommend”, and “amazing stay” cluster around key concepts of satisfaction and delight. The graph includes terms related to luxury (e.g., “champagne breakfast”, “friendly staff”, “perfect location”), showing how top reviews emphasize both emotional tone and specific highlights. This reflects how guests not only rate highly, but provide detail-rich praise in their feedback.

bigrams_5star <- reviews %>%
  filter(rating == 5) %>%
  unnest_tokens(bigram, review, token = "ngrams", n = 2) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% stop_words$word, !word2 %in% stop_words$word) %>%
  count(word1, word2, sort = TRUE)

bigrams_5star_filtered <- bigrams_5star %>%
  slice_max(n, n = 50)

bigram_graph(bigrams_5star_filtered)
Bigram Network for 5-Star Ratings

Bigram Network for 5-Star Ratings

Word Clouds: Low vs High Ratings

The word clouds illustrate the distinct language used in low-rated and high-rated reviews, with negative terms like “bad” or “dirty” dominating lower ratings and positive terms like “excellent” and “wonderful” prevalent in higher ratings.

gradient1 <- colorRampPalette(c("#D3D3D3", rating_colors["1"]))(8)
gradient5 <- colorRampPalette(c("#D3D3D3", rating_colors["5"]))(8)

top_wc <- reviews %>%
  mutate(rating = factor(rating)) %>%
  unnest_tokens(word, review) %>%
  anti_join(stop_words, by = "word") %>%
  inner_join(bing, by = "word") %>%
  count(rating, word, sort = TRUE) %>%
  group_by(rating) %>%
  slice_max(n, n = 100) %>%
  ungroup()

wc1 <- top_wc %>% filter(rating == "1")
wc5 <- top_wc %>% filter(rating == "5")

par(mfrow = c(1, 2), mar = c(1, 1, 2, 1))
set.seed(2025)
wordcloud(words        = wc1$word,
          freq         = wc1$n,
          max.words    = 100,
          min.freq     = 2,
          scale        = c(4, 0.5),
          random.order = FALSE,
          colors       = gradient1)
title("1-star Reviews", line = 0.5)

wordcloud(words        = wc5$word,
          freq         = wc5$n,
          max.words    = 100,
          min.freq     = 2,
          scale        = c(4, 0.5),
          random.order = FALSE,
          colors       = gradient5)
title("5-star Reviews", line = 0.5)
Word Clouds: 1-star vs 5-star

Word Clouds: 1-star vs 5-star

par(mfrow = c(1, 1))

Conclusions

The analysis confirms our hypothesis. Higher-rated reviews consistently show positive sentiment scores, whereas lower-rated reviews yield predominantly negative sentiments. This indicates that sentiment analysis effectively captures user perceptions, providing valuable insights for consumer analytics and reputation management.

Acknowledgments

Citation:

Alam, M. H., Ryu, W.-J., & Lee, S. (2016). Joint multi-grain topic sentiment: modeling semantic aspects for online reviews. Information Sciences, 339, 206–223. DOI

Dataset Source: TripAdvisor Hotel Reviews on Kaggle