Game of Thrones Ratings Per Episode Per Season

⚠ Warning: This Website Contains Spoilers

1 Background and Research Question

“How have the ratings of Game of Thrones episodes evolved over time across different seasons?”

Game of Thrones is a globally renowned television series that aired for eight seasons, captivating millions of viewers with its complex narrative and unexpected twists. Throughout the show’s run, the ratings for each episode varied, reflecting audience reactions to both character development and major plot events. The purpose of this analysis is to explore how the ratings evolved over time, particularly across the different seasons of the show. The goal is to understand whether there are significant trends, such as:

➸ A consistent increase or decrease in ratings across seasons.

➸ Sharp drops or rises in ratings following key plot developments (e.g., deaths of major characters or unexpected storylines).

➸ Whether certain seasons or episodes stand out in terms of audience reception.

Understanding these patterns can provide insights into how Game of Thrones engaged its audience throughout its run and how specific plot choices impacted viewer satisfaction.

The purpose of the code is to process and analyse the ratings data for each episode of the show. This includes:

➸ Cleaning and transforming the data for use in visualisation.

➸ Plotting trends to visualise how ratings evolved from season to season and episode to episode.

➸ Identifying anomalies or significant drops in ratings, which could correspond to specific events in the storyline, such as controversial plot twists or character deaths.

➸ Comparing different seasons to understand if ratings improved or declined, and potentially correlate these changes with story arcs or audience reception.

By answering this question, the analysis provides a quantitative look at the evolution of the show’s reception, offering insights that may reflect broader trends in viewer preferences and engagement with long-running TV series.

2 Data Source

The raw data was obtained from the IMDB website which is publicly accessible via this link:

https://www.imdb.com/title/tt0944947/ratings/

The data used in this analysis was extracted from the ‘Ratings by episode’ section of the Game of Thrones page.

I created an excel spreadsheet which perfectly replicated the grid shown on the IMDB page showing each episode rating, making the data accessible for wrangling and visualisation.

3 Data Preparation

3.1 Package Versions

Library and Version Purpose
tidyverse_2.0.0 for handling data
here_1.0.1 for easy file and directory referencing
readxl_1.4.3 for reading excel files
knitr_1.49 for combining R code with text to create dynamic reports
dplyr_1.1.4 for data manipulation tasks
jpeg_0.1.10 for reading jpeg files
gganimate_1.0.9 for creating animated plots
plotly_4.10.4 for an interactive plot
gifski_1.32.0.1 for saving gif files

3.2 Load Packages

library(tidyverse)
library(here)
library(readxl)
library(knitr)
library(dplyr)
library(jpeg)
library(gganimate)
library(plotly)
library(gifski)

3.3 Import the Data

#load data from excel file
rawdata <- read_excel(here::here("raw_data", "raw_data.xlsx"))
## New names:
## • `` -> `...1`

R has automatically assigned any empty values in the table to now say “…1”. First, I am going to print the data to see how it looks initially after importing.

#this is a sanity check to inspect the data
print(rawdata)
## # A tibble: 8 × 12
##   ...1     e1    e2    e3    e4    e5    e6    e7    e8    e9   e10   e11
##   <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 s1      8.9   8.6   8.5   8.6   9     9.1   9.1   8.9   9.6   9.4   9.2
## 2 s2      8.6   8.3   8.7   8.6   8.6   8.9   8.8   8.6   9.7   9.3  NA  
## 3 s3      8.6   8.4   8.7   9.5   8.9   8.7   8.6   8.9   9.9   9.1  NA  
## 4 s4      9     9.7   8.7   8.7   8.6   9.7   9     9.7   9.6   9.7  NA  
## 5 s5      8.3   8.3   8.3   8.5   8.5   7.9   8.8   9.8   9.4   9.1  NA  
## 6 s6      8.4   9.2   8.6   9     9.7   8.3   8.5   8.3   9.9   9.9  NA  
## 7 s7      8.5   8.8   9.1   9.7   8.7   9     9.4  NA    NA    NA    NA  
## 8 s8      7.6   7.9   7.5   5.5   5.9   4    NA    NA    NA    NA    NA

The data has successfully imported, the next step is to wrangle the data to convert it into a form that can be easily visualised.

4 Data Wrangling

4.1 Renaming the columns

#rename the first column after automatic assignment of "...1"
colnames(rawdata) <- ifelse(colnames(rawdata) == "...1", "Season", colnames(rawdata))

#rename the columns, excluding the first which I have just renamed to remain empty
colnames(rawdata)[-1] <- c("Episode 1", "Episode 2", "Episode 3", "Episode 4", "Episode 5", "Episode 6", "Episode 7", "Episode 8", "Episode 9", "Episode 10", "Episode 11")

#this is a sanity to check to make sure the column headers changed
head(rawdata, n = 1)
## # A tibble: 1 × 12
##   Season `Episode 1` `Episode 2` `Episode 3` `Episode 4` `Episode 5` `Episode 6`
##   <chr>        <dbl>       <dbl>       <dbl>       <dbl>       <dbl>       <dbl>
## 1 s1             8.9         8.6         8.5         8.6           9         9.1
## # ℹ 5 more variables: `Episode 7` <dbl>, `Episode 8` <dbl>, `Episode 9` <dbl>,
## #   `Episode 10` <dbl>, `Episode 11` <dbl>
#change the values in the first column
#removing the 's' just to clean up to view of the table
rawdata$Season <- sub("s", "", rawdata$Season) 

#render the table with kable
kable(rawdata, format = "markdown")
Season Episode 1 Episode 2 Episode 3 Episode 4 Episode 5 Episode 6 Episode 7 Episode 8 Episode 9 Episode 10 Episode 11
1 8.9 8.6 8.5 8.6 9.0 9.1 9.1 8.9 9.6 9.4 9.2
2 8.6 8.3 8.7 8.6 8.6 8.9 8.8 8.6 9.7 9.3 NA
3 8.6 8.4 8.7 9.5 8.9 8.7 8.6 8.9 9.9 9.1 NA
4 9.0 9.7 8.7 8.7 8.6 9.7 9.0 9.7 9.6 9.7 NA
5 8.3 8.3 8.3 8.5 8.5 7.9 8.8 9.8 9.4 9.1 NA
6 8.4 9.2 8.6 9.0 9.7 8.3 8.5 8.3 9.9 9.9 NA
7 8.5 8.8 9.1 9.7 8.7 9.0 9.4 NA NA NA NA
8 7.6 7.9 7.5 5.5 5.9 4.0 NA NA NA NA NA

The above table shows a much cleaner version of the data, however, it is not ready for visualisation yet. Before I take the data and plot it, first I am going to remove the final column containing ‘Episode 11’. The reason for this is that this data is not required in the analysis as this is the unaired original pilot. Audiences never saw this episode and was simply an alternate to the official pilot episode that was released. Therefore, ‘Episode 11’ was excluded from the final dataset.

4.2 Reshaping the data

# Reshape the data to long format for a more flexible structure for visualising, analysing, and modeling data. This is easier for ggplot2 to handle.
rawdata_long <- rawdata %>%
  pivot_longer(cols = starts_with("Episode"),   # Select columns that start with "Episode"
               names_to = "Episode",            # Create a new column "Episode"
               values_to = "Rating")            # Create a new column "Rating"

# Exclude Episode 11 from the data
data <- rawdata_long %>%
  filter(str_replace(Episode, "Episode ", "") != "11")

#this is a sanity check to make sure the data is now in a long format
head(data)
## # A tibble: 6 × 3
##   Season Episode   Rating
##   <chr>  <chr>      <dbl>
## 1 1      Episode 1    8.9
## 2 1      Episode 2    8.6
## 3 1      Episode 3    8.5
## 4 1      Episode 4    8.6
## 5 1      Episode 5    9  
## 6 1      Episode 6    9.1
# Save the data as a CSV file, which can be opened in excel.
write.csv(data, "cleaned_data/data.csv", row.names = FALSE)

5 Visualisations

5.1 Basic Visualisation

First, I made a basic plot using the cleaned data. This was to get to grips with the coding process and understand different aspects of the code. Additionally, it gave me insight into which direction to take the plot. For example, what colours should teh plot lines be? Does the text need changing? What further additons would benefit the visualisation?

#Create a basic line plot with minimal customisation
p <- ggplot(data, aes(x = as.integer(str_replace(Episode, "Episode ", "")),  # Convert episode to numeric
                         y = Rating, 
                         color = factor(Season))) +  # Use Season for different lines
  geom_line() +                          # Draw lines
  geom_point() +                         # Add points for each episode
  labs(x = "Episode Number",             # Label for x-axis
       y = "Episode Rating",             # Label for y-axis
       color = "Season Number") +        # Label for the legend
  theme_minimal() +                      # Use a minimal theme for a clean look
  scale_color_viridis_d() +              # Add color scale for different lines
  theme(legend.position = "right")       # Place the legend at the right

#view the plot as a sanity check to assess what direction to take the customisations.
print(p)

#Save the plot to the 'figures' folder
ggsave(here("figures", "basic_visualisation.png"))

5.2 Customisation

The x axis has automated to appear in intervals of 2.5. This needs recoding so that it shows the numeric episode numbers. To do this, I need to ‘mutate’ the data to remove “Episode” from the string of numbers. This converts the remaining number (e.g., “1”, “2”) into an integer; creating a new column called ‘EpisodeNumber’.

# Preprocess the Episode column
data1 <- data %>%
  mutate(EpisodeNumber = as.integer(str_replace(Episode, "Episode ", "")))

# This is a sanity check to view the new column
print(data1)
## # A tibble: 80 × 4
##    Season Episode    Rating EpisodeNumber
##    <chr>  <chr>       <dbl>         <int>
##  1 1      Episode 1     8.9             1
##  2 1      Episode 2     8.6             2
##  3 1      Episode 3     8.5             3
##  4 1      Episode 4     8.6             4
##  5 1      Episode 5     9               5
##  6 1      Episode 6     9.1             6
##  7 1      Episode 7     9.1             7
##  8 1      Episode 8     8.9             8
##  9 1      Episode 9     9.6             9
## 10 1      Episode 10    9.4            10
## # ℹ 70 more rows

Next, I wanted to add a more personal customisation to the colours on the visualisation. To do this, I assigned a family house sigil to each of the seasons based on major plot points:

Season Number House Major Plot Point
Season 1 Stark Only time all the Starks are together & Death of Eddard Stark
Season 2 Baratheon Death of Renly Baratheon & Battle of Blackwater
Season 3 Lannister Jaime Lannister loses his hand & The Red Wedding
Season 4 Martell Death of Oberyn Martell
Season 5 Tyrell Margaery Tyrell manipulates King’s Landing
Season 6 Arryn Saved the day at the Battle of the Bastards
Season 7 Greyjoy Yara Greyjoy declares herself Queen of the Iron Islands
Season 8 Targaryen Daenerys Targaryen gets the Iron Throne
# Convert the numeric 'Season' column to a factor with appropriate labels
data1$Season <- factor(data1$Season, 
                       levels = 1:8, 
                       labels = c("Season 1", "Season 2", "Season 3", "Season 4", "Season 5", "Season 6", "Season 7", "Season 8"))

# Assign custom colors to each line based on the season
custom_colors <- c(
  "Season 1" = "#7f7f7f",   # Grey for Season 1, House Stark
  "Season 2" = "#ffc406",   # Yellow for Season 2, House Baratheon
  "Season 3" = "#B03060",   # Maroon for Season 3, House Lannister
  "Season 4" = "#ED7014",   # Orange for Season 4, House Martell
  "Season 5" = "#006400",   # Green for Season 5, House Tyrell
  "Season 6" = "#023E8A",   # Blue for Season 6, House Arryn
  "Season 7" = "#000000",   # Black for Season 7, House Greyjoy
  "Season 8" = "#ff0000"    # Red for Season 8, House Targaryen
)

Season 1 : #7f7f7f

Season 2 : #ffc406

Season 3 : #B03060

Season 4 : #ED7014

Season 5 : #006400

Season 6 : #023E8A

Season 7 : #000000

Season 8 : #ff0000

5.3 Customised Visualisation

# Create the plot with new customisations
p1 <- ggplot(data1, aes(x = EpisodeNumber, y = Rating, color = factor(Season))) +  
  geom_line(size = 0.8) +                        
  geom_point() +                       
  labs(x = "Episode Number",            
       y = "Episode Rating",            
       color = "",                                        # Label for the legend
       caption = "Source: IMDB.com") +                    # Add source text at the bottom
  ggtitle("Game of Thrones Episode Ratings Per Season") + # Add a title
  theme_minimal() +                                       # Clean, minimal theme
  scale_color_manual(values = custom_colors) +            # Apply custom colors for lines
  scale_x_continuous(
    breaks = seq(1, max(data1$EpisodeNumber), by = 1)) +  # Set x-axis breaks
  scale_y_continuous(
    breaks = seq(4, 10, by = 0.5),                        # Set y-axis breaks
    limits = c(4, 10),                                    # Set y-axis limits
    expand = c(0, 0)                                      # Remove extra padding
  ) + 
  theme(legend.position = "right",                        # Position the legend on the right
        text = element_text(family = "serif"),            # Add font serif
        plot.title = element_text(size = 20, face = "bold"),      # Adjust title size and style
        axis.line.x = element_line(color = "black", size = 1),    # Add border to the x-axis
        axis.line.y = element_line(color = "black", size = 1),    # Add border to the y-axis
        plot.background = element_rect(fill = "white"),           # Set plot background to white
        panel.background = element_rect(fill = "white")           # Set panel background to white
  ) +
    guides(color = guide_legend(
    keywidth = 2,                           # Adjust the size of the legend key
    keyheight = 2,                          # Adjust the size of the legend key
    override.aes = list(size = 5)           # Increase the size of the color circles inside the legend
  ))

# Display the plot
print(p1)

#Save the plot to the 'figures' folder
ggsave(here("figures", "visualisation.png"))

6 Interactive Visualisation

To further build on the plot, I incorporated an interactive aspect that allows viewers to select different seasons to see them individually, select multiple to compare, or see all collectively. Additionally, hovering over plot points provides insight into the Season, episode number, and its IMDB rating. This plot allows for individual analysis of plot points and direct comparison.

interactive_plot <- ggplotly(p2)   # Adds interactive aspect to plot

interactive_plot

7 Animated Visualisations

7.1 Animated Visualisation 1

An alternative to the interactive plot is an animated plot. I incorporated an animation to my plot so that the episode points were revealed gradually, making the visualisation more engaging and visually appealing. Specifically, the major difference between Season 8 ratings compared to the rest is quite pleasing to see being steadily revealed.

anim <- p1 + 
  geom_point() +
  transition_manual(EpisodeNumber, cumulative = TRUE) +
  labs(
    subtitle = "Episode: {frame}"  # Add a dynamic subtitle that changes with each frame
  )

#display the plot, this is a sanity check to see how the animation looks
#I have chosen to leave this in for the PDF output as it has only 10 frames and represents the key frames of the animations output with the relevant subtitle
anim

I really like the animation of the above plot, however, I think it could be improved by making the animation much smoother. Rather than the lines on the graph appearing at every point, I would like it to gradually move across the graph. This will look much more visually appealing and encourage human readability. At the moment, it is quite difficult to focus on the details of the plot and the information it is providing because of how jumpy it in in appearance.

To change this, I needed to change the “transition_manual” command to “transition_reveal”.

Initially, the subtitle was thrown off as the command “subtitle =”Episode: {frame}“” follows every frame of the animation to provide the subtitle, therefore the plot was showing that there were 100 episodes as there is 100 frames to the animation. After lots of trial and error, I used the command “subtitle =”Episode: {frame_along}“” to ensure that the subtitle reflected the episode number on the x axis.

7.2 Animated Visualisation 2

anim2 <- p1 + 
  geom_point() +
  transition_reveal(EpisodeNumber) +
  labs(
    subtitle = "Episode: {frame_along}"  # Ensure the subtitle is in line with the x axis
  )
#this is a new chunk as the plot has 100 frames, therefore adds 100 images to a pdf document
#display the plot
anim2

#Save the plot to the 'figures' folder
anim_save(here("figures" , "animated_plot.gif"), anim2, renderer = gifski_renderer())

8 Conclusion

8.1 Insights and Interpretations

“How have the ratings of Game of Thrones episodes evolved over time across different seasons?”

The ratings for Game of Thrones episodes in seasons 1 to 7 generally follow a similar pattern, with most seasons showing steady ratings throughout, reflecting consistent viewer engagement and satisfaction. However, season 5 stands out as an anomaly, with a marked variation in its ratings. The season starts with relatively low ratings, it hits a noticeable low point in the middle, and then sharply rises towards a peak as the season progresses. This represents a possible influence of key plot developments or pivotal episodes. In stark contrast, season 8, the final season, is the lowest rated overall, with a steep decline in viewer satisfaction. This may be attributed to the controversial handling of the series’ conclusion. This sharp drop represents the general reception of the last season, where many fans expressed dissatisfaction with the resolution of key storylines and character arcs.

Generally, It seems that highly rated episodes of Game of Thrones are often linked to major plot events, particularly the deaths of significant characters and dramatic battles. For example, the three highest rated episodes include Season 3, Episode 9, titled “The Rains of Castamere” which included the plotline commonly referred to as “The Red Wedding”. It is one of the most highly rated episodes, largely due to its shocking and brutal mass slaughter of key characters. Similarly, Season 6, Episodes 9 and 10 are titled “Battle of the Bastards” and “The Winds of Winter”, respectively. “Battle of the Bastards” is praised for its epic combat and strategic depth, while “The Winds of Winter” delivers a shocking and dramatic conclusion to several major storylines. These pivotal episodes often evoke strong emotional reactions from viewers, contributing to higher ratings as they deliver unexpected twists and intense, memorable moments that become defining features of the series.

8.2 Evaluation

This animation successfully conveys the complex relationship between Game of Thrones episode ratings and the progression of its seasons. The use of animation helps to visualise the change in public perception over time, giving the viewer a dynamic understanding of the series’ reception. Additionally, the animated plot allows for the dramatic reveal of drastic changes in the data, specifically, it is quite visually engaging to watch the poorly rated season 8 being revealed against the other season ratings. However, it should be noted that while the ratings reflect general enjoyment, they may not fully capture the nuances of audience reactions. The animation itself does not include critical review scores or contextual factors, like episode descriptions or key plot points.

Moreover, the graph relies solely on IMDB ratings, which, while representative, might not capture the full spectrum of viewer experiences. This represents a possible bias within the data as it is solely based on IMDB ratings, which represent the opinions of a specific group of viewers- those who are motivated to rate the show. To improve on this analysis, the inclusion of other sources, such as audience surveys or social media analysis, could provide additional insights.

8.3 Where Next?

Moving forward, a deeper dive into the factors driving the fluctuating ratings could be explored. For instance, examining the ratings in relation to key plot events or character arcs (e.g., the Battle of the Bastards or the final episode) would provide more detailed insights into what specifically influenced viewers’ satisfaction.

I particularly think the animated aspect works for this plot kind, but it may be beneficial to add an interactive aspect to be able to hover over episode points to reveal a plot synopsis and IMDB rating. The combination of the two may provide beneficial insights.

An additional follow-up could be in relation to critic ratings against viewer ratings. For example, it may be insightful to investigate the comparison of Game of Thrones IMDB ratings with critical reviews (e.g., from Rotten Tomatoes, Metacritic, or TV critics), social media reactions (e.g., Twitter or Reddit discussions), and fan sentiment over time. It could explore discrepancy between what critics and fans thought of certain episodes, especially the controversial final seasons. This extension would explore whether public opinion diverges from expert analysis.