# Load the data
reviews <- read_csv("Hotel_Reviews.csv")
## Rows: 515738 Columns: 17
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (8): Hotel_Address, Review_Date, Hotel_Name, Reviewer_Nationality, Negat...
## dbl (9): Additional_Number_of_Scoring, Average_Score, Review_Total_Negative_...
## 
## ℹ 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.
top_hotels <- reviews %>%
  filter(Reviewer_Score == max(Reviewer_Score)) %>%
  select(Hotel_Name, Reviewer_Score, Positive_Review, Negative_Review)

bottom_hotels <- reviews %>%
  filter(Reviewer_Score == min(Reviewer_Score)) %>%
  select(Hotel_Name, Reviewer_Score, Positive_Review, Negative_Review)

# Combine positive and negative reviews with labels
top_hotels <- top_hotels %>%
  mutate(Review = case_when(
    Positive_Review != "No Positive" ~ Positive_Review,
    Negative_Review != "No Negative" ~ Negative_Review,
    TRUE ~ NA_character_
  ),
  Sentiment = case_when(
    Positive_Review != "No Positive" ~ "positive",
    Negative_Review != "No Negative" ~ "negative",
    TRUE ~ NA_character_
  )) %>%
  filter(!is.na(Review))

bottom_hotels <- bottom_hotels %>%
  mutate(Review = case_when(
    Positive_Review != "No Positive" ~ Positive_Review,
    Negative_Review != "No Negative" ~ Negative_Review,
    TRUE ~ NA_character_
  ),
  Sentiment = case_when(
    Positive_Review != "No Positive" ~ "positive",
    Negative_Review != "No Negative" ~ "negative",
    TRUE ~ NA_character_
  )) %>%
  filter(!is.na(Review))

Executive summary

My main question is “Comparison of the Relative Importance of Positive/Negative Words”

This project analyzes a data set of 515,000 hotel reviews in Europe to learn about customer experience and satisfaction in luxury hotels across Europe. There are various data fields, but among them, I wanted to compare the highest ranked hotels with those that did not. Through various textual analyses in the reviews, we wanted to find out which words appeared a lot in the reviews of hotels with high ratings and where customers were satisfied with the words, and the hotels with the lowest ratings wanted to compare why customers wrote reviews through these words. We focused on intuitively representing what is important through word crowds, bar graphs showing the top 20, and log odds ratios.

Through this, the goal of my project is to eventually find out why hotel operators evaluate hotels positively and negatively, so that they can work more on certain areas.

Data background

Explain where the data came from, what agency or company made it, how it is structured, what it shows, etc.

The dataset, titled “515K Hotel Reviews Data in Europe,” is sourced from Kaggle and contains reviews from 1,493 luxury hotels across Europe. It includes several variables such as the hotel name, reviewer nationality, review date, positive review, negative review, review ratings, and more. This comprehensive dataset allows us to perform a detailed text analysis and extract meaningful patterns from the reviews.

Data loading, cleaning and preprocessing

We wanted to organize and preprocess the review data of the top and bottom rating hotels. First, various analysis techniques could be applied by improving the quality of text data by using the word-oriented tokenization and the function str_to_lower(), str_replace_all(), and str_replace_all() required for preprocessing. Special characters could change the meaning of words or act as noise during the analysis process. Therefore, we tried to clarify the meaning of words by removing special characters and to increase the accuracy of the analysis. If multiple blank characters are included, it is replaced with a single blank to create a consistent data structure. This can prevent errors that may occur during subsequent analysis and increase efficiency.

Above all, when I was processing hotel reviews, I found that the word hotel was included in the review a lot. The hotel was intended to be excluded because it did not belong to any positive or negative word in terms of the word itself, and could be included due to the nature of the hotel review. Therefore, before excluding the hotel, the hotel was ranked first in the negative word, but after excluding it, the data results that fit more negative reviews came out.

# Clean the text data
top_hotels_clean <- top_hotels %>%
  mutate(Review = str_to_lower(Review)) %>%
  mutate(Review = str_replace_all(Review, "[^[:alnum:]]", " ")) %>%
  mutate(Review = str_replace_all(Review, "\\s+", " "))

bottom_hotels_clean <- bottom_hotels %>%
  mutate(Review = str_to_lower(Review)) %>%
  mutate(Review = str_replace_all(Review, "[^[:alnum:]]", " ")) %>%
  mutate(Review = str_replace_all(Review, "\\s+", " "))

# Add "hotel" to stop words
custom_stop_words <- bind_rows(stop_words, tibble(word = c("hotel")))

# Tokenize the text and remove stop words
top_tokens <- top_hotels_clean %>%
  unnest_tokens(word, Review) %>%
  anti_join(custom_stop_words)
## Joining with `by = join_by(word)`
bottom_tokens <- bottom_hotels_clean %>%
  unnest_tokens(word, Review) %>%
  anti_join(custom_stop_words)
## Joining with `by = join_by(word)`

Text data analysis

Textual analysis reveals what was focused on reviews of hotels with high ratings and those that did not. In both hotels, the staff was chosen as an important word. It suggests that depending on the attitude of the staff, it can be a positive or negative review. In addition to this, you can check various frequencies of words.

Individual analysis and figures

  1. word colud
  2. the top 20 most used words in the reviews bar graph
  3. The Log Odds Ratio

Anaysis and Figure 1

The code uses a word cloud technique to visualize the most used words in reviews of top and bottom rated hotels.

Word cloud is an effective visualization method that intuitively shows important words in text data. The size of a word indicates the frequency of that word, making it easy to identify the most prominent words in the data.

In this code, the word clouds of the top and bottom rated hotels are arranged side by side for comparison. This allows users to see at a glance the difference between the words highlighted in the reviews of the two hotel types.

For color selection, blue was used for the top-rated hotel and red was used for the bottom-rated hotel to visually distinguish between positive and negative tendencies. It is an intuitive representation of the characteristics of the data. In particular, the most frequent words are darkened, and the lower the frequency, the weaker they are so that they can be seen at a glance.

Overall, this word cloud visualization effectively shows the most important words in the hotel review data, and clearly reveals the difference between the top and bottom rating hotels, which can help with data analysis.

# Create word clouds for top and bottom hotels
top_words <- top_tokens %>%
  count(word, sort = TRUE)

bottom_words <- bottom_tokens %>%
  count(word, sort = TRUE)

# Set up plot area for side-by-side word clouds
par(mfrow = c(1, 2), mar = c(1, 1, 2, 1)) 

# Top-rated hotels word cloud
wordcloud(words = top_words$word, freq = top_words$n, max.words = 100, 
          colors = brewer.pal(9, "Blues"), scale = c(3, 0.6), random.order = FALSE)
title("Top Rated Hotels", col.main = "blue", cex.main = 1.5)

# Bottom-rated hotels word cloud
wordcloud(words = bottom_words$word, freq = bottom_words$n, max.words = 100, 
          colors = brewer.pal(9, "Reds"), scale = c(3, 0.6), random.order = FALSE)
title("Bottom Rated Hotels", col.main = "red", cex.main = 1.5)

Anaysis and Figure 2

This visualized the top 20 most used words in the reviews of the top and bottom rating hotels as barts.

The bar graph is selected because it allows you to intuitively show the frequency of words. You can place them horizontally to clearly show the words, and the length of the bar in the vertical direction makes it easy to compare the relative frequencies of words.

For color selection, blue was used for the top rating hotel and red was used for the bottom rating hotel to visually distinguish between positive and negative tendencies. We chose this because blue is commonly used for affirmation and red is used for negation.

The overall configuration allows for comparison by placing the word frequencies of the upper and lower rating hotels side by side. This allows users to see at a glance the difference between the words highlighted in the reviews of the two hotel types.

This visualization effectively communicates the key characteristics of the data, and can help analyze the data by clearly showing the difference between the top and bottom rating hotels. Overall, this visualization can be seen as effectively communicating the truthfulness of the data.

# Calculate word frequencies
top_words_freq <- top_tokens %>%
  count(word, sort = TRUE)

bottom_words_freq <- bottom_tokens %>%
  count(word, sort = TRUE)

# Select top 20 words for each group
top_20_words <- top_words_freq %>%
  top_n(20, n)

bottom_20_words <- bottom_words_freq %>%
  top_n(20, n)

# Plot most frequent words for top-rated hotels
top_plot <- top_20_words %>%
  ggplot(aes(x = reorder(word, n), y = n)) +
  geom_col(fill = "blue") +
  coord_flip() +
  labs(title = "Top-Rated Hotels",
       x = "Words",
       y = "Frequency") +
  theme_minimal()

# Plot most frequent words for bottom-rated hotels
bottom_plot <- bottom_20_words %>%
  ggplot(aes(x = reorder(word, n), y = n)) +
  geom_col(fill = "red") +
  coord_flip() +
  labs(title = "Bottom-Rated Hotels",
       x = "Words",
       y = "Frequency") +
  theme_minimal()

# Arrange the two plots side by side with a common title
grid.arrange(top_plot, bottom_plot, ncol = 2,
              top = "Top 20 Most Frequent Words in Hotels")

Anaysis and Figure 3

The Log Odds Ratio is an indicator of the relative likelihood of occurrence of a particular event between two groups. In this graph, we compare the log odds ratios of the words that show the greatest positive/negative differences in the reviews of the top-rated and bottom-rated hotels.

A word with a positive log odds ratio is a relatively more used positive word in a top rated hotel. Words with negative log odds ratios are negative words that are relatively more commonly used in lower rating hotels.

The greater the absolute value of log odds ratio, the stronger the positive/negative propensity of the word, so the larger the absolute value, the more important it plays in distinguishing between the top rated hotel and the bottom rated hotel.

Even with the same word, you can see that the frequency of use is different between the top rating hotel and the bottom rating hotel. This allows customers to identify key words that distinguish between top-rated and bottom-rated hotels. Therefore, this graph effectively shows the words that show the biggest difference in the reviews of the top and bottom rating hotels, allowing us to understand the differences in customers’ perceptions.

# Calculate log odds ratio
top_tokens_counts <- top_tokens %>% 
  count(word, sentiment = tolower(Sentiment))

bottom_tokens_counts <- bottom_tokens %>%
  count(word, sentiment = tolower(Sentiment))

# Combine top and bottom hotels
log_odds_df <- bind_rows(
  top_tokens_counts %>%
    pivot_wider(names_from = sentiment, values_from = n, values_fill = 0) %>%
    mutate(log_odds_ratio = log((positive + 1) / (negative + 1)),
           hotel_type = "Top-Rated Hotels"),
  bottom_tokens_counts %>%
    pivot_wider(names_from = sentiment, values_from = n, values_fill = 0) %>%
    mutate(log_odds_ratio = log((positive + 1) / (negative + 1)),
           hotel_type = "Bottom-Rated Hotels")
)

# Filter top 10 words by absolute log odds ratio
log_odds_df <- log_odds_df %>%
  group_by(hotel_type) %>%
  top_n(10, abs(log_odds_ratio)) %>%
  ungroup()

# Visualize the results
ggplot(log_odds_df, aes(x = reorder(word, log_odds_ratio), 
                       y = log_odds_ratio, 
                       fill = hotel_type)) +
  geom_col(position = "dodge") +
  coord_flip() +
  labs(x = "Word",
       y = "Log Odds Ratio",
       title = "Top 10 Positive and Negative Words in Hotel Reviews",
       fill = "Hotel Type") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5),
        axis.text.x = element_text(angle = 45, hjust = 1))