Much Ado About Batman v. Superman

A lot has been and will be written about the box-office adventures of Batman vs. Superman, particularly day-to-day and weekend-to-weekend percentage drops in ticket sales. Robert Cain over at Forbes had a particularly interesting breakdown of the film’s opening weekend performance. But what does it mean for the long haul? If you’re looking for a movie that proves you can’t always predict future performance based on day-to-day totals or the percentage of critics who hate a film on Rotten Tomatoes, this is one of ’em.

Cain wrote at Forbes:

According to the figures I’ve compiled from Boxofficemojo.com, Batman v Superman has set a new record for the worst Friday-to-Sunday drop for a superhero movie release in modern North American box office history. In dropping 55% from its $82 million Friday debut to its $37 million gross on Sunday, it pummeled all prior records for weakness in theatrical staying power. It even beat the nearly universally reviled and now long-forgotten Fantastic Four reboot, which dropped a comparatively modest 48% across its opening weekend in the summer of 2015.

He included a chart that indicates “superhero movies that don’t hold up well over their first weekend tend not to sustain much energy at the box office over the longer course of their theatrical runs.” While he admitted the correlations are not perfect (between final domestic gross and the “opening weekend multiplier”), he went on to predict a final domestic box office gross of $409 million for the film. My curiosity was piqued.

His response:

But how can you make a prediction without a prediction model? And if we’re talking about the drop in ticket sales between Friday and Sunday, how is the opening weekend multiplier (the total domestic gross/opening weekend gross) a good indicator. For example, The Dark Knight Rises, an undeniably successful superhero film, has a lower multiplier than Big Hero 6, which made half the total of TDKR’s domestic run.

I decided to pull together my own data, partly based on his chart and other sources, and give it a shot.

The Data

Cain’s source was “compiled by a Pacific Bridge analysis of boxofficemojo.com data.” I threw out the 2016 films such as Deadpool because they are still finishing their box-office runs, and using Wikipedia’s list of superhero films by year, added 2012 films “Chronicle” and “Dredd.” [1] Because I removed a year and obviously needed more data points, I added films going back to 2008, the year of “Iron Man” and “The Dark Knight” — arguably, the beginning of the mega-blockbuster superhero film era. Opening weekend numbers were taken from the sites BoxOffice.com and The Numbers. I double-checked these stats with boxofficemojo.com where they were available. I also added Rotten Tomatoes scores, but more on that later.

Finally, because we’re talking about money, I adjusted all totals to 2015 dollars. Here’s what the data look like in Python. [2]

    :::python
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import statsmodels.formula.api as smf

    movies = ["Fantastic Four","The Dark Knight Rises","Kick-Ass 2", "Avengers: Age of Ultron", "Iron Man 3", "Captain America: Winter Soldier", "X-Men: First Class", "The Amazing Spider-Man 2", "Thor", "Ant-Man","Guardians of the Galaxy","Captain America: First Avenger","Thor: The Dark World","The Wolverine","TMNT (2014)", "Green Lantern","Marvel's The Avengers","X-Men: Days of Future Past","Green Hornet","Ghost Rider: Spirit of Vengeance","Big Hero 6","Man of Steel","Chronicle","Dredd","Kick-Ass","Jonah Hex","Iron Man 2","X-Men Origins: Wolverine","Watchmen","Push","Jumper","Iron Man","The Incredible Hulk","Hellboy II: The Golden Army","The Dark Knight","Punisher: War Zone","The Spirit"]

    release_year = ["2015","2012","2013","2015","2013","2014","2011","2014","2011","2015","2014","2011","2013","2013","2014","2011","2012","2014","2011","2012","2014","2013","2012","2012","2010","2010","2010","2009","2009","2009","2008","2008","2008","2008","2008","2008","2008"]

    first_fri = [11.28,75.8,5.83,84.4,68.9,36.9,21.4,35.2,25.5,22.6,37.8,25.7,31.9,20.7,25.6,21.4,80.8,35.5,11.1,6.9,15.8,44.03,8.65,2.2,7.6,1.9,51.2,34.4,24.5,3.5,6.6,35.2,21.4,13.7,67.1,1.6,3.8]

    first_sun = [5.83,40.2,3.31,50.3,43,23.4,14,23.3,16.9,15.1,25.5,17.4,21.8,14.3,17.8,15.1,57.1,26,9.8,6.5,16.4,36.3,3.25,1.6,4.9,1.6,31.1,21.3,12.3,2.4,10.6,26,15.5,9.2,43.5,1.1,2.1]

    opening = [25.6,160.8,13.3,191.2,174.1,95,55.1,91.6,65.7,57.2,94.3,65,85.7,53.1,65.5,53.1,207.4,90.8,33.5,22.1,56.2,116.6,22,6.27,19.8,5.37,128.1,85,55.2,10,27.3,98.6,55.4,34.5,158.4,4.2,6.4]

    final_gross = [56.1,448.1,28.8,459,409,259.8,146.4,202.9,181,180.2,333.2,176.7,206.4,132.6,191.2,116.6,623.4,233.9,98.8,51.8,222.5,291,64.5,13.4,48,10.5,312,179.8,107.5,31.8,80.1,318.4,134.8,75.9,534.8,8,19.8]

    critics = [.09,.87,.30,.75,.79,.89,.87,.53,.77,.80,.91,.79,.67,.70,.21,.26,.92,.91,.43,.17,.89,.56,.85,.78,.76,.12,.72,.38,.65,.23,.16,.94,.67,.85,.94,.27,.14]

    d = { "Release Year" : pd.Series(release_year, index=movies),
          "FirstFridayGross" : pd.Series(first_fri, index=movies),
          "FirstSundayGross" : pd.Series(first_sun, index=movies),
          "OpeningWeekend" : pd.Series(opening, index=movies),
          "FinalGross" : pd.Series(final_gross, index=movies),
          "CriticsScores": pd.Series(critics, index=movies)}

    df = pd.DataFrame(d)

An example of taking into account inflation:

    :::python
    # calculating earnings in 2015 dollars
    # inflation, 2008 = 10.1%, 2009 = 10.5%, 2010 = 8.7%, 2011 = 5.4%, 2012 = 3.2%, 2013 = 1.7%, 2014 = 0.1%

    # 2008
    df.ix[df['Release Year'] == '2008','FirstFridayGross'] = df.ix[df['Release Year'] == '2008','FirstFridayGross'] * 1.101
    df.ix[df['Release Year'] == '2008','FirstSundayGross'] = df.ix[df['Release Year'] == '2008','FirstSundayGross'] * 1.101
    df.ix[df['Release Year'] == '2008','OpeningWeekend'] = df.ix[df['Release Year'] == '2008','OpeningWeekend'] * 1.101
    df.ix[df['Release Year'] == '2008','FinalGross'] = df.ix[df['Release Year'] == '2008','FinalGross'] * 1.101

And finally, creating columns for the percentage of earnings drop from Friday to Sunday and the opening weekend mulitplier.

    :::python
    # friday to sunday percentage change
    df = df.assign(perc_change= ((df["FirstSundayGross"] - df["FirstFridayGross"])/df["FirstFridayGross"]).round(2))

    # opening weekend multiplier
    df = df.assign(multiplier=df["FinalGross"]/df["OpeningWeekend"])

Here’s the final chart, sorted by final domestic gross:

Critics’ Scores Final Domestic Gross (M)* First Friday Gross (M)* First Sunday Gross (M)* Opening Weekend (M)* Release Year Percent Change Multiplier
Marvel’s The Avengers 0.92 643.3488 83.38560 58.92720 214.03680 2012 -0.29 3.005786
The Dark Knight 0.94 588.8148 73.87710 47.89350 174.39840 2008 -0.35 3.376263
The Dark Knight Rises 0.87 462.4392 78.22560 41.48640 165.94560 2012 -0.47 2.786692
Avengers: Age of Ultron 0.75 459.0000 84.40000 50.30000 191.20000 2015 -0.40 2.400628
Iron Man 3 0.79 415.9530 70.07130 43.73100 177.05970 2013 -0.38 2.349225
Iron Man 0.94 350.5584 38.75520 28.62600 108.55860 2008 -0.26 3.229209
Iron Man 2 0.72 339.1440 55.65440 33.80570 139.24470 2010 -0.39 2.435597
Guardians of the Galaxy 0.91 333.5332 37.83780 25.52550 94.39430 2014 -0.33 3.533404
Man of Steel 0.56 295.9470 44.77851 36.91710 118.58220 2013 -0.18 2.495712
Captain America: Winter Soldier 0.89 260.0598 36.93690 23.42340 95.09500 2014 -0.37 2.734737
X-Men: Days of Future Past 0.91 234.1339 35.53550 26.02600 90.89080 2014 -0.27 2.575991
Big Hero 6 0.89 222.7225 15.81580 16.41640 56.25620 2014 0.04 3.959075
Thor: The Dark World 0.67 209.9088 32.44230 22.17060 87.15690 2013 -0.32 2.408401
The Amazing Spider-Man 2 0.53 203.1029 35.23520 23.32330 91.69160 2014 -0.34 2.215066
X-Men Origins: Wolverine 0.38 198.6790 38.01200 23.53650 93.92500 2009 -0.38 2.115294
TMNT (2014) 0.21 191.3912 25.62560 17.81780 65.56550 2014 -0.30 2.919084
Thor 0.77 190.7740 26.87700 17.81260 69.24780 2011 -0.34 2.754947
Captain America: First Avenger 0.79 186.2418 27.08780 18.33960 68.51000 2011 -0.32 2.718462
Ant-Man 0.80 180.2000 22.60000 15.10000 57.20000 2015 -0.33 3.150350
X-Men: First Class 0.87 154.3056 22.55560 14.75600 58.07540 2011 -0.35 2.656987
The Incredible Hulk 0.67 148.4148 23.56140 17.06550 60.99540 2008 -0.28 2.433213
The Wolverine 0.70 134.8542 21.05190 14.54310 54.00270 2013 -0.31 2.497175
Green Lantern 0.26 122.8964 22.55560 15.91540 55.96740 2011 -0.29 2.195857
Watchmen 0.65 118.7875 27.07250 13.59150 60.99600 2009 -0.50 1.947464
Green Hornet 0.43 104.1352 11.69940 10.32920 35.30900 2011 -0.12 2.949254
Jumper 0.16 88.1901 7.26660 11.67060 30.05730 2008 0.61 2.934066
Hellboy II: The Golden Army 0.85 83.5659 15.08370 10.12920 37.98450 2008 -0.33 2.200000
Chronicle 0.85 66.5640 8.92680 3.35400 22.70400 2012 -0.62 2.931818
Fantastic Four 0.09 56.1000 11.28000 5.83000 25.60000 2015 -0.48 2.191406
Ghost Rider: Spirit of Vengeance 0.17 53.4576 7.12080 6.70800 22.80720 2012 -0.06 2.343891
Kick-Ass 0.76 52.1760 8.26120 5.32630 21.52260 2010 -0.36 2.424242
Push 0.23 35.1390 3.86750 2.65200 11.05000 2009 -0.31 3.180000
Kick-Ass 2 0.30 29.2896 5.92911 3.36627 13.52610 2013 -0.43 2.165414
The Spirit 0.14 21.7998 4.18380 2.31210 7.04640 2008 -0.45 3.093750
Dredd 0.78 13.8288 2.27040 1.65120 6.47064 2012 -0.27 2.137161
Jonah Hex 0.12 11.4135 2.06530 1.73920 5.83719 2010 -0.16 1.955307
Punisher: War Zone 0.27 8.8080 1.76160 1.21110 4.62420 2008 -0.31 1.904762

* in 2015 dollars

The Correlations Really Aren’t Perfect

First I plotted “final domestic gross” versus “opening weekend multiplier.”

batman-supes-fig-1

 

The correlation between these two variables is 0.36, which statistically speaking is weakly positive — “positive” meaning that an increase in the multiplier reflects an increase in final gross, not positive as in “it looks good!” If you were looking at this relationship to fit a model, you would want to look at other variables. One major reason for this is the formula used to calculate the opening weekend multiplier contains the value you are trying to predict! It’s like trying to asking a question with the answer. Since your predictors in a regression model are supposed to be independent, we have to toss this out.

Next, I plotted “final domestic gross” versus the Friday-to-Sunday drop in ticket sales.

batman-supes-fig-2

 

The correlation between these two variables is -0.11, which is really weak and negative, suggesting a lower percentage drop equals a lower domestic gross. That’s confusing until you look at films like “Jonah Hex” and “Ghost Rider: Spirit of Vengeance.” Both performed miserably at the box office, but the Friday and Sunday earnings didn’t really differ that much. But again, don’t take much stock in this, because it’s an extremely weak correlation. However, something interesting happens when you group by critics’ scores.

batman-supes-fig-3

 

With the exception of outliers like “Dredd” and “Big Hero 6,” most films with high scores sit in a narrow band between the -40% and -20% Friday to Sunday percentage drop.

Which leads me to the last variable I want to explore: “final domestic gross” versus “critics’ scores.”

batman-supes-fig-4

 

The correlation between these two variables is 0.59, which is a lot stronger than the other variables explored. It also follows common sense: Films with better reviews usually are going to attract a wider audience.

Building a Model

Let’s say I wanted to build a linear regression model to predict final domestic gross. Based on the evidence above, a single variable is just not going to cut it. I could start with the critics’ scores, but my coefficient of determination (the \(R^{2}\) I asked Cain about) shows critics’ scores only explain about 35% of the variability in the data. We can’t use the opening weekend multiplier, so all that’s left is the opening weekend percentage change. A multivariate regression model using critics’ scores and the percentage change would also only explain 35% of the variability. The percentage change variable adds very little to the model.

But hey! This is a blog, not life and death. So let’s make a prediction using an imperfect model!

\[0.6953 + (\mbox{Critics’ Score})(337.8686) + (\mbox{Percentage Change})(33.7825)\]

\[ 0.6953 + (.29)(337.8686) + (.55)(33.7825) = $117M \]

That’s less than the film’s take on opening weekend. So here’s what went wrong…

The Rising Dark Knight Lifts All Capes

Yes, Cain’s response, that there are not enough data points, is one of the factors that prevents me from making an accurate prediction. But as I proved above, the variables we have for prediction also aren’t that great (for superhero films). Certainly with more data, they could improve, but it might be wise to look at other stats as well. Maybe we could take a look at viewers’ scores, instead of critics’ scores. Maybe movie length plays a part since a shorter movie will have more showtimes (and more opportunities to make money) then a movie that’s almost 3 hours long. Who knows.

Even if critics’ scores proved reliable, there’s also the little fact that Batman V. Superman has been universally reviled by critics. With a 29% Rotten Tomatoes score, it is an outlier that defies prediction.

How did Cain come up with the $409M prediction? He certainly has more experience reporting on the business than I do, so I’ll leave that to him and say his method is better than mine.


UPDATE, 6-8-2016: While working on a follow-up post, I noticed that I incorrectly stated Batman V. Superman’s opening weekend percentage change as positive, not negative, when using the model to predict the film’s final gross. The formula should actually read:

\[ 0.6953 + (.29)(337.8686) + (-.55)(33.7825) = $80M \]

That’s even less accurate than my slip up!

UPDATE, 4-11-2016: I replaced the hexbin charts with scatter plots because I realized the hexbins were probably not properly communicating the amount of data on the plot. Honestly, I used them because they looked cool, which is breaking a rule of data visualization. The scatter plots don’t look as cool, but they get the job done.

[1] I left out “The Amazing Spider-Man” and “Hancock” as those films were released mid-week and skew the whole Friday to Sunday model.

[2] This is my first time posting with Python and Markdown. Normally I run everything in R, but this time I thought I would switch things up. It also explains why it took me longer than usual to write this. If you’re interested, the GitHub repo for the Python script is here.