Ultimate Endings

Looking into how different shows wrap up

There is so much choice for watching TV, people sometimes ask if a show is “worth watching”. Well, let’s take a look at some of the shows that have garnered a lot of praise, and see how they wrap up.

No spoilers ahead. Just ratings.

Go Get Some Shows

First, we gotta grab a bunch of shows. I went off a list I found, picked ones that were no longer airing, cherry picking a little from that group. Below, we have a dataframe of different shows, their IMDb codes, and their season spans (ignored a few seasons so things don’t break).

show_list = tribble(
  ~title, ~show_id, ~seasons,
  "Psych", "tt0491738", c(1:8),
  "24", "tt0285331", c(1:8),
  "Scrubs", "tt0285403", c(1:9),
  "Homeland", "tt1796960", c(1:8),
  "Prison Break", "tt0455275", c(1:5),
  "Lost", "tt0411008", c(1:6),
  "How I Met Your Mother", "tt0460649", c(1:9),
  "The Americans", "tt2149175", c(1:6),
  "Stargate SG-1", "tt0118480", c(1:10),
  "The Legend of Korra", "tt1695360", c(1:4),
  "Entourage", "tt0387199", c(1:8),
  "Person of Interest", "tt1839578", c(1:5),
  "Futurama", "tt0149460", c(1:5, 7:10), # ignore movies
  "Fringe", "tt1119644", c(1:5),
  "Flight of the Conchords", "tt0863046", c(1:2),
  "Silicon Valley", "tt2575988", c(1:6),
  "Big Little Lies", "tt3920596", c(1:2),
  "Boardwalk Empire", "tt0979432", c(1:5),
  "Community", "tt1439629", c(1:6),
  "Hannibal", "tt2243973", c(1:3),
  "Mr. Robot", "tt4158110", c(1:4),
  "Adventure Time", "tt1305826", c(2:10), # first season breaks
  "Deadwood", "tt0348914", c(1:10),
  "Star Trek: The Next Generation", "tt0092455", c(1:7),
  "The Newsroom", "tt1870479", c(1:3),
  "Parks and Recreation", "tt1266020", c(1:7),
  "Mad Men", "tt0804503", c(1:7),
  "Sons of Anarchy", "tt1124373", c(1:7),
  "Daredevil", "tt3322312", c(1:3),
  "Dexter", "tt0773262", c(1:8),
  "Friday Night Lights", "tt0758745", c(1:5),
  "The Shield", "tt0286486", c(1:7),
  "Oz", "tt0118421", c(1:6),
  "Bojack Horseman", "tt3398228", c(1:6),
  "Six Feet Under", "tt0248654", c(1:5),
  "Battlestar Galactica", "tt0407362", c(1:4),
  "House", "tt0412142", c(1:8),
  "The West Wing", "tt0200276", c(1:7),
  "Freaks and Geeks", "tt0193676", c(1:1),
  "Seinfeld", "tt0098904", c(1:9),
  "Arrested Development", "tt0367279", c(1:5),
  "Narcos", "tt2707408", c(1:3),
  "The Office", "tt0386676", c(1:9),
  "House of Cards", "tt1856010", c(1:6),
  "Gravity Falls", "tt1865718", c(1:2),
  "Friends", "tt0108778", c(1:10),
  "Firefly", "tt0303461", c(1:1),
  "Avatar: The Last Airbender", "tt0417299", c(1:3),
  "The Sopranos", "tt0141842", c(1:6),
  "The Wire", "tt0306414", c(1:5),
  "Game of Thrones", "tt0944947", c(1:8),
  "Breaking Bad", "tt0903747", c(1:5)
)

Here we make a function that unpacks the dataframe made above and sends the info over to a script I made to get IMDb data. I have used it a bunch, here modded it slightly to printout some status updates.

# function to loop through df and grab rating data
grab_shows <- function(show_df) {
  df_list = list()
  
  for (row in 1:nrow(show_df)) {
    show_name = show_df[[row, "title"]]
    show_id = show_df[[row, "show_id"]]
    seasons = show_df[[row, "seasons"]][[1]]
    
    df <- grab_imdb_ratings_v3(show_id, seasons)
    
    df_list[[show_name]] <- df
  }
  
  return(bind_rows(df_list))
}
# grab all shows 
# prints some warnings for missing air dates, suppressed
shows <- grab_shows(show_list)

Running this, we end up with 52 shows. Let’s see how they end.

Which Shows End Perfectly?

We demand the best, or at least what most people agree is the best. Wanted to see which shows have a series finale that was near-perfect, defined as 9.7 or above.

perfect_finale_shows <- shows %>% 
  group_by(show) %>% 
  mutate(episode_number = row_number()) %>% 
  top_n(1, episode_number) %>% 
  filter(rating >= 9.7) %>% 
  select(show) %>% 
  ungroup() %>% 
  as.list()

perfect_finale_shows <- perfect_finale_shows$show

I took this list and then grabbed the last 25 episodes from each show to see the rating path that led to the perfect dismount, as it were.

last_episodes <- shows %>% 
  group_by(show) %>% 
  mutate(episode_number = row_number()) %>% 
  top_n(25, episode_number) %>% 
  filter(show %in% perfect_finale_shows) %>% 
  mutate(last_episode_number = row_number())
main_color <- "seagreen"

finales <- last_episodes %>% 
  group_by(show) %>% 
  filter(last_episode_number ==  max(last_episode_number))

last_episodes %>% 
  ggplot(aes(last_episode_number, rating)) +
    geom_text(data = finales, aes(label = rating), 
              hjust = 1, vjust = 0,
              x = 25, 
              y = 7, 
              size = 5, 
              alpha = 0.2, color = main_color) +
    geom_smooth(se = FALSE, color = main_color) +
    geom_line(color = main_color, alpha = 0.2, size = 0.5, linetype = "solid") +
    geom_point(alpha = 0.4, color = main_color, size = 1) +
    facet_wrap(~ show) +
    theme(axis.text.x=element_blank(),
        axis.ticks.x=element_blank()) +
    labs(title = "Ending Perfectly",
         subtitle = "Final 25 Episode Ratings for Shows that Ended with an Above-9.7 Series Finale",
         caption = plot_caption,
         x = "Final 25 Episodes",
         y = "Rating")

All the shows pictured had very strong finishes, with most having a progression of episodes in the eights and nines.

Now what about the other end?

Which Shows End Terribly?

Let’s do the same thing, but now for shows that ended with poorly-rated finales. Now we only keep shows that ended with an episode rated below five.

terrible_finale_shows <- shows %>% 
  group_by(show) %>% 
  mutate(episode_number = row_number()) %>% 
  top_n(1, episode_number) %>% 
  filter(rating < 5) %>% 
  select(show) %>% 
  ungroup() %>% 
  as.list()

terrible_finale_shows <- terrible_finale_shows$show

Grabbing episodes that led up to the finale…

last_episodes_bad <- shows %>% 
  group_by(show) %>% 
  mutate(episode_number = row_number()) %>% 
  top_n(25, episode_number) %>% 
  filter(show %in% terrible_finale_shows) %>% 
  mutate(last_episode_number = row_number())

And plot ’em.

main_color_bad <- "firebrick"

finales <- last_episodes_bad %>% 
  group_by(show) %>% 
  filter(last_episode_number ==  max(last_episode_number))

last_episodes_bad %>% 
  ggplot(aes(last_episode_number, rating)) +
    geom_text(data = finales, aes(label = rating), 
              hjust = 1, vjust = 0,
              x = 24, 
              y = 2.5, 
              size = 5, 
              alpha = 0.2, color = main_color_bad) +
    geom_smooth(se = FALSE, color = main_color_bad) +
    geom_line(color = main_color_bad, alpha = 0.2, size = 0.5, linetype = "solid") +
    geom_point(alpha = 0.4, color = main_color_bad, size = 1) +
    facet_wrap(~ show) +
    theme(axis.text.x=element_blank(),
        axis.ticks.x=element_blank()) +
    labs(title = "Sour Finish",
         subtitle = "Final 25 Episode Ratings for Shows that Ended with a Below-5 Series Finale",
         caption = plot_caption,
         x = "Final 25 Episodes",
         y = "Rating")

Out of all the shows collected, only three ended with a rating below five.

Which Shows Have Perfect Episodes?

To end on a perfect note, let’s look at the shows that have a near-perfect episode. This time, we’ll be strict and say 9.9 or above is all we will let in to this elite group.

shows %>% 
  filter(rating >= 9.9) %>% 
  arrange(desc(rating), show)
## # A tibble: 14 x 7
##    show             season episode air_date   title                rating  votes
##    <chr>             <int>   <int> <date>     <chr>                 <dbl>  <dbl>
##  1 Breaking Bad          5      14 2013-09-15 Ozymandias             10   120303
##  2 Avatar: The Las…      3      21 2008-07-19 Sozin's Comet, Part…    9.9   5946
##  3 BoJack Horseman       6      15 2020-01-31 The View from Halfw…    9.9   9221
##  4 Breaking Bad          4      13 2011-10-09 Face Off                9.9  41496
##  5 Breaking Bad          5      16 2013-09-29 Felina                  9.9  85311
##  6 Game of Thrones       3       9 2013-06-02 The Rains of Castam…    9.9  88566
##  7 Game of Thrones       5       8 2015-05-31 Hardhome                9.9  88408
##  8 Game of Thrones       6       9 2016-06-19 Battle of the Basta…    9.9 187288
##  9 Game of Thrones       6      10 2016-06-26 The Winds of Winter     9.9 130471
## 10 Hannibal              2      13 2014-05-23 Mizumono                9.9  15472
## 11 Mr. Robot             4       7 2019-11-17 407 Proxy Authentic…    9.9  18604
## 12 Mr. Robot             4      13 2019-12-22 Hello, Elliot           9.9  11704
## 13 Person of Inter…      4      11 2015-01-06 If-Then-Else            9.9  10799
## 14 Six Feet Under        5      12 2005-08-21 Everyone's Waiting      9.9   7413

We see some familiar series finales, like Breaking Bad and Six Feet Under, as well as some other shows that had perfect episodes during their run, but ended with a lower-rated finale.

Some interesting things:

  • Game of Thrones had several perfect episodes, but the progression of the final season led to a poor finish.
  • Game of Thrones has four perfectly-rated episodes in the collection, with Breaking Bad also scoring high with three.
  • Breaking Bad is the only show with an episode rated a perfect ten (data collected cuts off at tenth decimal). Interestingly, it is the antepenultimate episode, two from the last (and yeah, this post might be one big excuse to use that word in context).

Wield the Remote

Quit hoggin’ the remote, I wanna choose! You got it.

Here’s a look at the final episode progression for all the shows collected.

Now, if you’ll excuse me, I have to go watch some TV (strictly research, you understand).

Till next time!

Learning

  • Most of the head-banging involved getting R to pass the vectors for seasons into the looping function. I needed to use double brackets to pass the value I wanted along.
  • Tribbles are loverly. They made it easy to write out a dataframe from scratch. Also, apparently they’re a Star Trek thing as well.
  • The geom_text() labels turned out just okay. Found it a little challenging to place as well as plot the desired value. Played around with average across final episodes, but didn’t like the result.
  • Creating a list to then filter on with %in% was a useful trick. A little wrangling required to get it down just to a vector.
  • Started dplyr work to get the final episodes with some grouping and other stuff. But top_n came in to save the day, fit the bill perfectly.
  • The scraper I made “breaks” when a show has an episode with no ratings, which happens for a variety of reasons (pilot, duplication, etc.). Would be nice to somehow ignore these entries, since right now the best I can do is ignore the offending season entirely.
  • Getting the Shiny app up and running was painless. Brett Ory’s post on the topic was super helpful. Am a little disappointed the layout is a little wonky, will have to learn how to fill the page better or move things around with more control. But gettin something on the screen is a win in my book.

Image Credit

starstruck by Zach Bogart from the Noun Project

Zach Bogart
Zach Bogart
Data Explorer

Science, Design, & Data. I’ll know it when I see it.

Related