Assignment 8

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.2     ✔ tibble    3.3.0
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.1.0     
── 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(lubridate) 
library(tidytext)  
Warning: package 'tidytext' was built under R version 4.5.2
library(textdata)  
Warning: package 'textdata' was built under R version 4.5.2
library(widyr)     
Warning: package 'widyr' was built under R version 4.5.2
library(igraph)    
Warning: package 'igraph' was built under R version 4.5.2

Attaching package: '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(ggraph)
Warning: package 'ggraph' was built under R version 4.5.2
library(dplyr)
library(gutenbergr)
Warning: package 'gutenbergr' was built under R version 4.5.2
library(ggwordcloud)
Warning: package 'ggwordcloud' was built under R version 4.5.2

Introduction

Battlefield and Call of Duty have been competitors in the first-person shooter (FPS) genre for many years. Battlefield 6 was released on October 10, 2025, and Call of Duty Black Ops 7 was just released on November 14, 2025. Despite Battlefield being out for an additional month than Black Ops 7, I was interested to see how the games were received by the gaming community and to compare the results.

To analyze the gaming community’s reception of both of these games, I went to Steam and scraped reviews from both games’ sites. I pulled 100 reviews for each game, and believe that this population will be representative of the general opinion. I scraped the data in separate R scripts, saved them as static datasets in a csv, which I have then uploaded to this document.

Uploading the Data

battlefield <- 
  read_csv("https://myxavier-my.sharepoint.com/:x:/g/personal/olsonp2_xavier_edu/IQDW9UJWZuEvT4nxsQ6i7VaWAYxfONKMRXNdvcQGFRjwtRo?download=1")
Rows: 100 Columns: 4
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (4): reviewer_name, reviewer_products, review_date, review_text

ℹ 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.
blackops <- 
  read_csv("https://myxavier-my.sharepoint.com/:x:/g/personal/olsonp2_xavier_edu/IQDVDSeUVbMjSZ8syhYWYEPnAcFzdHwokLdSKYyM9TWSHbU?download=1")
Rows: 100 Columns: 4
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (4): reviewer_name, reviewer_products, review_date, review_text

ℹ 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.

Viewing Battlefield 6 Data

view(battlefield)

Viewing Black Ops 7 Data

view(blackops)

Data Cleaning

battlefield <- battlefield %>%
  mutate(review_date = mdy(paste0(review_date, " 2025")))

blackops <- blackops %>%
  mutate(review_date = mdy(paste0(review_date, " 2025")))

While not all too important to the analysis we will be doing, this code changes our review_date column to be formatted correctly.

Battlefield Tidying

battlefield <- 
  battlefield %>% 
  arrange(review_date) %>% 
  mutate(review_id = row_number())

tidy_battlefield <- 
  battlefield %>% 
  unnest_tokens(word, review_text) %>% 
  anti_join(stop_words)
Joining with `by = join_by(word)`
tidy_battlefield %>% 
  group_by(reviewer_name, word) %>% 
  summarize(n = n()) %>% 
  arrange(-n)
`summarise()` has grouped output by 'reviewer_name'. You can override using the
`.groups` argument.
# A tibble: 3,424 × 3
# Groups:   reviewer_name [98]
   reviewer_name    word            n
   <chr>            <chr>       <int>
 1 Rikimaru         game           12
 2 Rooftop Korean#1 aed            10
 3 JuggernautTyson  game            9
 4 Kurajberovic     game            9
 5 TehF0cus         battlefield     9
 6 Ace-Primed       battlefield     8
 7 Rikimaru         battlefield     8
 8 Rikimaru         bf              8
 9 Rikimaru         games           8
10 TehF0cus         maps            8
# ℹ 3,414 more rows

Black Ops Tidying

blackops <- 
  blackops %>% 
  arrange(review_date) %>% 
  mutate(review_id = row_number())

tidy_blackops <- 
  blackops %>% 
  unnest_tokens(word, review_text) %>% 
  anti_join(stop_words)
Joining with `by = join_by(word)`
tidy_blackops %>% 
  group_by(reviewer_name, word) %>% 
  summarize(n = n()) %>% 
  arrange(-n)
`summarise()` has grouped output by 'reviewer_name'. You can override using the
`.groups` argument.
# A tibble: 1,336 × 3
# Groups:   reviewer_name [94]
   reviewer_name   word         n
   <chr>           <chr>    <int>
 1 Cannedair       requires    15
 2 Cannedair       restart     15
 3 Cannedair       update      15
 4 JamesBeBuilding game         9
 5 DialKyle        cod          6
 6 AFatalPapercut  bosses       5
 7 ForealTJ        game         5
 8 Zxcarys         game         5
 9 ChristStrongX   potato       4
10 I'mLove         cod          4
# ℹ 1,326 more rows

These commands tidy up the review text that we have stored for both games. From this point, we will be able to conduct further analysis on the text in terms of frequency and, later on, sentiment analysis.

Introducing Sentiments

afinn <- 
  get_sentiments("afinn")

bing <- 
  get_sentiments("bing")

nrc <- 
  get_sentiments("nrc")

These commands are what will allow us to carry out sentiment analysis

Battlefield Visual by Word Count

tidy_battlefield %>%
  group_by(word) %>% 
  summarize(n=n()) %>% 
  filter(n >= 20) %>% 
  ggplot(aes(y = reorder(word, n), 
             x = n)) +
  geom_col() +
  labs(title = "Word counts for Battlefield Reviews",
       subtitle = "For words appearing at least 20 times",
       x = "Number of times the word appears",
       y = "Word")

This column chart does not provide any valuable insight, as almost none of the words that have appeared a minimum of 20 times provide any insight into the nature of the reviews.

Battlefield Word Cloud

tidy_battlefield %>%
  group_by(word) %>% 
  summarize(n=n()) %>% 
  filter(n >= 5) %>% 
  ggplot(aes(label = word, size = n)) +
  geom_text_wordcloud() +
  theme_minimal()

This word cloud provides little to no valuable insights as the most prominent words shown have no sentimental/emotional value. Therefore, we cannot make any conclusions about the reviews of Battlefield 6 from this visualization. From this point, we will do sentimental analysis, therefore, only analyzing words with emotional value.

Battlefield Evaluation Using Sentiments

battlefield_counts <-
  tidy_battlefield %>% 
  group_by(word) %>%
  summarise(n=n()) %>% 
  inner_join(bing)
Joining with `by = join_by(word)`

This command adds sentiments to word counts.

Battlefield Sentiment Visualization

battlefield_counts %>%
  filter(n > 7) %>%
  filter(!word == 'tank') %>%
  filter(!word == 'tanks') %>%
  filter(!word == 'destruction') %>%
  filter(!word == 'free') %>%
  mutate(n = ifelse(sentiment == "negative", -n, n)) %>%
  mutate(word = reorder(word, n)) %>% 
  ggplot(aes(word, n, fill = sentiment)) +
  geom_col() +
  coord_flip() +
  scale_fill_brewer(palette="Set1") +
  labs(title = "Battlefield Sentiment Scores by Word",
       subtitle = "Scorable words appearing at least 7 times",
       x = "Word",
       y = "Contribution to sentiment")

This visualization adds the most insight compared to the other visualizations we’ve had. As we can see, we limited the visualization to terms with emotional value that have appeared 7 or more times in our scraped reviews. The distribution appears to be 6 positive words to 4 negative words. When we look at the frequency for these terms, “fun” has the highest frequency by a significant margin compared to the most frequent negative term (“issues”). After this, however, the frequencies even out. After seeing these insights, it will be interesting to see how it looks for Black Ops 7.

Black Ops Visual by Word Count

tidy_blackops %>%
  group_by(word) %>% 
  summarize(n=n()) %>% 
  filter(n >= 10) %>% 
  ggplot(aes(y = reorder(word, n), 
             x = n)) +
  geom_col() +
  labs(title = "Word counts for Battlefield Reviews",
       subtitle = "For words appearing at least 10 times",
       x = "Number of times the word appears",
       y = "Word")

Similar to the Battlefield column chart, this does not provide any valuable insight to our analysis as none of these terms have any emotional value. The reason I changed the minimum to 10x is because there were only 3 words at the minimum of 20x: game, cod, and play.

Black Ops Word Cloud

tidy_blackops %>%
  group_by(word) %>% 
  summarize(n=n()) %>% 
  filter(n >= 5) %>% 
  ggplot(aes(label = word, size = n)) +
  geom_text_wordcloud() +
  theme_minimal()

Just like the column chart, we cannot make any conclusions from this word cloud. We will now move on to our analysis using sentiments.

Black Ops Evaluation Using Sentiments

blackops_counts <-
  tidy_blackops %>% 
  group_by(word) %>%
  summarise(n=n()) %>% 
  inner_join(bing)
Joining with `by = join_by(word)`

Black Ops Sentiment Visualization

blackops_counts %>%
  filter(n > 7) %>%
  mutate(n = ifelse(sentiment == "negative", -n, n)) %>%
  mutate(word = reorder(word, n)) %>% 
  ggplot(aes(word, n, fill = sentiment)) +
  geom_col() +
  coord_flip() +
  scale_fill_brewer(palette="Set1") +
  labs(title = "Black Ops Sentiment Scores by Word",
       subtitle = "Scorable words appearing at least 7 times",
       x = "Word",
       y = "Contribution to sentiment")

With a minimum of 7 occurrences, we have an equal split between negative and positive terms. However, somewhat to the contrary of the Battlefield reviews, the negative terms have a higher frequency than that of the positive words. The difference in frequency is not as significant as the Battlefield reviews, but still worthy of noting.

Conclusion

After conducting our analysis, we can see that there is a slight difference in the distribution of positive and negative reviews for Battlefield 6 and Call of Duty Black Ops 7. The difference being the distribution of positive to negative words for Battlefield 6 was 3:2, whereas for Black Ops 7 it was 1:1. Additionally, the frequency of the positive words was significantly higher than the negative words for the Battlefield reviews. The case was the opposite for the Black Ops reviews, although the difference was not as significant.

If we were to make general assumptions from analysis, we would conclude that the reviews for Battlefield 6 were slightly more positive than those for Black Ops 7. Furthermore, we would conclude that the gaming community was more receptive to Battlefield 6.