Data Source Description

This data comes from a YouTube video on the 2013 BMW 135is. The video is a review of the vehicle. The initial raw data is from the comments on the video. The cleaned data is filtered to only the authorDisplayName, textOriginal, publishedAt, likeCount, and id.

# Load packages
library(tuber)
library(dplyr)
library(readr)
library(tidytext)
library(stringr)
library(ggplot2)
library(ggwordcloud)
# Authorize youtube API
yt_oauth(
  app_id = Sys.getenv("YOUTUBE_APP_ID"), 
  app_secret = Sys.getenv("YOUTUBE_APP_SECRET")
)
# Set video variable to scrape
video_id <- "EvR3Xcy5OXg" # "The BMW 135is Is the Best BMW You Don’t Know About"
# Scrape video comments
comments_raw <- get_all_comments(video_id = video_id)
# View comments data
head(comments_raw)
glimpse(comments_raw)
# Clean comments data
comments1 <- comments_raw %>%
  as_tibble() %>%
  # 1. Remove duplicates (Your existing code)
  distinct(id, .keep_all = TRUE) %>%
  # 2. Select relevant columns
  select(authorDisplayName, textOriginal, publishedAt, likeCount, id) %>%
  # 3. Clean the textOriginal column
  mutate(
    # Remove URLs
    textOriginal = str_remove_all(textOriginal, "http[s]?://\\S+"),
    # Replace newline and carriage return characters with a space
    textOriginal = str_replace_all(textOriginal, "[\r\n]", " "),
    # Remove any stray HTML tags (sometimes they sneak into textOriginal)
    textOriginal = str_remove_all(textOriginal, "<.*?>"),
    # str_squish removes leading/trailing whitespace and replaces multiple spaces with a single space
    textOriginal = str_squish(textOriginal) 
  )
# Get working directory
getwd()
# Export data to CSV
write_csv(comments1, "comments1.csv")
# Load the pre-scraped data for the rest of the analysis
comments1 <- read_csv("comments1.csv")
# Clean up for analysis
tidy_comments <- comments1 %>%
  # 1. Tokenize: Break sentences into individual words
  # This also automatically converts everything to lowercase and strips punctuation
  unnest_tokens(word, textOriginal) %>%
  
  # 2. Remove stopwords (words like "the", "and", "is" that skew frequency counts)
  anti_join(stop_words, by = "word") %>%
  
  # 3. Keep only actual words (filter out loose numbers or weird symbols)
  filter(str_detect(word, "^[a-z]+$"))
# Get the top 20 most frequently used words
word_counts <- tidy_comments %>%
  count(word, sort = TRUE) %>%
  head(20)
# Build the bar chart of top 20 words
ggplot(word_counts, aes(x = n, y = reorder(word, n))) +
  geom_col(fill = "steelblue", alpha = 0.8) +
  labs(
    title = "Most Frequent Words in BMW 135is Comments",
    subtitle = "Top 20 words after removing stopwords",
    x = "Frequency",
    y = NULL # Hides the y-axis label since the words explain themselves
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", size = 14),
    axis.text.y = element_text(size = 10)
  )

# Create word cloud of top 20 words
ggplot(word_counts, aes(label = word, size = n, color = n)) +
  # This geometry creates the actual word cloud layout
  geom_text_wordcloud_area() +
  # This controls how big the words get; adjust max_size if words overlap or get cut off
  scale_size_area(max_size = 18) +
  # Adds a color gradient based on frequency (lighter = less frequent, darker = more frequent)
  scale_color_gradient(low = "steelblue", high = "midnightblue") +
  theme_minimal()

# Score the overall sentiment of the dataset
sentiment_bmw <- tidy_comments %>%
  # Match your words against the positive/negative dictionary
  inner_join(get_sentiments("bing"), by = "word") %>%
  # Count how many positive vs negative words exist
  count(sentiment, sort = TRUE)
# Plot the sentiment score
ggplot(sentiment_bmw, aes(x = sentiment, y = n, fill = sentiment)) +
  # geom_col creates the bars; show.legend = FALSE hides the redundant color legend
  geom_col(show.legend = FALSE) +
  # Assign specific colors to the sentiments
  scale_fill_manual(values = c("positive" = "#2ca25f", "negative" = "#de2d26")) +
  # Add labels
  labs(
    title = "Overall Sentiment in BMW 135is Comments",
    subtitle = "Based on bing lexicon word counts",
    x = "Sentiment",
    y = "Total Word Count"
  ) +
  # Clean up the visual style
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", size = 14),
    axis.text.x = element_text(size = 12, face = "bold")
  )

Insights

Reviewing the most frequent words in the comments quickly shows that the most common word in the comments is car. This is probably expected since the video is about a car but looking down the list reveals more about the car. In the top 20 are the words are love and fun. The sentiment analysis shows that there are more positive words in the comments. This could indicate that most people commenting have a positive view on the car. I was still surprised that there were a large number of negative words since a review of the top 20 words does not indicate that there would be many negative words.