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.
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.
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:
Review: Guest textual review.Rating: Guest numeric rating (1 = Poor, 5 =
Excellent).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
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
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
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')
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
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
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
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
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
par(mfrow = c(1, 1))
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.
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