In October 2015, a data journalist named Walt Hickey analyzed movie ratings data published an article (https://fivethirtyeight.com/features/fandango-movies-ratings/) saying that he had found strong evidence to suggest that Fandango's rating system was biased and dishonest (Fandango is an online movie ratings aggregator). He found that Fandago's system rounded ratings to the nearest whole star e. 4.5 to 5 stars. When he talked to the Fandago team, they claimed that this unrealistic rounding off was caused by a bug and promised to fix the problem.
The focus of our analysis will be on the recent ratings from the Fandago site to establish if they fixed the issue as promised.
We shall work on the site by looking at the features of thedata set that Hickey worked on (https://github.com/fivethirtyeight/data/tree/master/fandango) and compare it to a recent data set worked on by a member of the Dataquest team (https://github.com/mircealex/Movie_ratings_2016_17).
import pandas as pd
hickey = pd.read_csv("fandango_score_comparison.csv")
dataquest = pd.read_csv("movie_ratings_16_17.csv")
hickey.head()
FILM | RottenTomatoes | RottenTomatoes_User | Metacritic | Metacritic_User | IMDB | Fandango_Stars | Fandango_Ratingvalue | RT_norm | RT_user_norm | ... | IMDB_norm | RT_norm_round | RT_user_norm_round | Metacritic_norm_round | Metacritic_user_norm_round | IMDB_norm_round | Metacritic_user_vote_count | IMDB_user_vote_count | Fandango_votes | Fandango_Difference | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Avengers: Age of Ultron (2015) | 74 | 86 | 66 | 7.1 | 7.8 | 5.0 | 4.5 | 3.70 | 4.3 | ... | 3.90 | 3.5 | 4.5 | 3.5 | 3.5 | 4.0 | 1330 | 271107 | 14846 | 0.5 |
1 | Cinderella (2015) | 85 | 80 | 67 | 7.5 | 7.1 | 5.0 | 4.5 | 4.25 | 4.0 | ... | 3.55 | 4.5 | 4.0 | 3.5 | 4.0 | 3.5 | 249 | 65709 | 12640 | 0.5 |
2 | Ant-Man (2015) | 80 | 90 | 64 | 8.1 | 7.8 | 5.0 | 4.5 | 4.00 | 4.5 | ... | 3.90 | 4.0 | 4.5 | 3.0 | 4.0 | 4.0 | 627 | 103660 | 12055 | 0.5 |
3 | Do You Believe? (2015) | 18 | 84 | 22 | 4.7 | 5.4 | 5.0 | 4.5 | 0.90 | 4.2 | ... | 2.70 | 1.0 | 4.0 | 1.0 | 2.5 | 2.5 | 31 | 3136 | 1793 | 0.5 |
4 | Hot Tub Time Machine 2 (2015) | 14 | 28 | 29 | 3.4 | 5.1 | 3.5 | 3.0 | 0.70 | 1.4 | ... | 2.55 | 0.5 | 1.5 | 1.5 | 1.5 | 2.5 | 88 | 19560 | 1021 | 0.5 |
5 rows × 22 columns
hickey.shape
(146, 22)
hickey.isnull().sum()
FILM 0 RottenTomatoes 0 RottenTomatoes_User 0 Metacritic 0 Metacritic_User 0 IMDB 0 Fandango_Stars 0 Fandango_Ratingvalue 0 RT_norm 0 RT_user_norm 0 Metacritic_norm 0 Metacritic_user_nom 0 IMDB_norm 0 RT_norm_round 0 RT_user_norm_round 0 Metacritic_norm_round 0 Metacritic_user_norm_round 0 IMDB_norm_round 0 Metacritic_user_vote_count 0 IMDB_user_vote_count 0 Fandango_votes 0 Fandango_Difference 0 dtype: int64
hickey.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 146 entries, 0 to 145 Data columns (total 22 columns): FILM 146 non-null object RottenTomatoes 146 non-null int64 RottenTomatoes_User 146 non-null int64 Metacritic 146 non-null int64 Metacritic_User 146 non-null float64 IMDB 146 non-null float64 Fandango_Stars 146 non-null float64 Fandango_Ratingvalue 146 non-null float64 RT_norm 146 non-null float64 RT_user_norm 146 non-null float64 Metacritic_norm 146 non-null float64 Metacritic_user_nom 146 non-null float64 IMDB_norm 146 non-null float64 RT_norm_round 146 non-null float64 RT_user_norm_round 146 non-null float64 Metacritic_norm_round 146 non-null float64 Metacritic_user_norm_round 146 non-null float64 IMDB_norm_round 146 non-null float64 Metacritic_user_vote_count 146 non-null int64 IMDB_user_vote_count 146 non-null int64 Fandango_votes 146 non-null int64 Fandango_Difference 146 non-null float64 dtypes: float64(15), int64(6), object(1) memory usage: 25.2+ KB
dataquest.head()
movie | year | metascore | imdb | tmeter | audience | fandango | n_metascore | n_imdb | n_tmeter | n_audience | nr_metascore | nr_imdb | nr_tmeter | nr_audience | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 10 Cloverfield Lane | 2016 | 76 | 7.2 | 90 | 79 | 3.5 | 3.80 | 3.60 | 4.50 | 3.95 | 4.0 | 3.5 | 4.5 | 4.0 |
1 | 13 Hours | 2016 | 48 | 7.3 | 50 | 83 | 4.5 | 2.40 | 3.65 | 2.50 | 4.15 | 2.5 | 3.5 | 2.5 | 4.0 |
2 | A Cure for Wellness | 2016 | 47 | 6.6 | 40 | 47 | 3.0 | 2.35 | 3.30 | 2.00 | 2.35 | 2.5 | 3.5 | 2.0 | 2.5 |
3 | A Dog's Purpose | 2017 | 43 | 5.2 | 33 | 76 | 4.5 | 2.15 | 2.60 | 1.65 | 3.80 | 2.0 | 2.5 | 1.5 | 4.0 |
4 | A Hologram for the King | 2016 | 58 | 6.1 | 70 | 57 | 3.0 | 2.90 | 3.05 | 3.50 | 2.85 | 3.0 | 3.0 | 3.5 | 3.0 |
dataquest.shape
(214, 15)
dataquest.isnull().sum()
movie 0 year 0 metascore 0 imdb 0 tmeter 0 audience 0 fandango 0 n_metascore 0 n_imdb 0 n_tmeter 0 n_audience 0 nr_metascore 0 nr_imdb 0 nr_tmeter 0 nr_audience 0 dtype: int64
dataquest.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 214 entries, 0 to 213 Data columns (total 15 columns): movie 214 non-null object year 214 non-null int64 metascore 214 non-null int64 imdb 214 non-null float64 tmeter 214 non-null int64 audience 214 non-null int64 fandango 214 non-null float64 n_metascore 214 non-null float64 n_imdb 214 non-null float64 n_tmeter 214 non-null float64 n_audience 214 non-null float64 nr_metascore 214 non-null float64 nr_imdb 214 non-null float64 nr_tmeter 214 non-null float64 nr_audience 214 non-null float64 dtypes: float64(10), int64(4), object(1) memory usage: 25.2+ KB
Both sets have no nulls, and are of consistent type
hickey_rates = hickey[['FILM', 'Fandango_Stars', 'Fandango_Ratingvalue', 'Fandango_votes', 'Fandango_Difference']]
dataquest_rates = dataquest[['movie', 'year', 'fandango']]
dataquest_rates.shape
(214, 3)
Our focus in this analysis will be on the ratings that the Fandago site gave to movies in 2016 and 2017. We have a sample by the Dataquest team and it contains movie ratings data for 214 of the most popular movies (with a significant number of votes) released in 2016 and 2017.
We also have sample of 146 movies from 2014 -2015 also worked upon by hickey, all the movies in the sample have a rating with Rotten Tomatoes rating, a RT User rating, a Metacritic score, a Metacritic User score, and IMDb score, and at least 30 fan reviews on Fandango.
The sampling is not random but they had to fit the criteria as given by the people that created the data and might not be very representative of the population.
Since the sampling is not random making it unlikely that the samples are not representative of our focus population, there is need for us to change the goal because we can't really draw conclusions from the data we have.
Therefore, we changed our goal to ascertaining whether there is a difference in the way Fandago rated popular movies in 2015 and 2016-2017.
This gives us to focal points:
Hickey gives a criteria of a popular movie as one that has atleast 30 viewer votes while the dataquest data doesn't have so we will choose a random sample from the data we have and compare it with data from fandago's site to see if they also have a similar number of votes
dataquest_rates.sample(10, random_state = 2)
movie | year | fandango | |
---|---|---|---|
23 | Bitter Harvest | 2017 | 4.5 |
155 | Table 19 | 2017 | 3.0 |
207 | Whiskey Tango Foxtrot | 2016 | 3.5 |
13 | Arsenal | 2017 | 3.5 |
64 | Gold | 2016 | 3.5 |
131 | Pele: Birth of a Legened | 2016 | 4.5 |
141 | Sausage Party | 2016 | 3.5 |
193 | The Secret Life of Pets | 2016 | 4.0 |
93 | Kung Fu Panda 3 | 2016 | 4.5 |
30 | Cafe Society | 2016 | 3.5 |
Comparing the same movies on the Fandago site to see if they have the same number of votes. By the time I checked the site, rotten tomatoes had abandoned its rating and adopted the Rotten Tomatoes rating system. However, as long as a movie is popular, there are high chances that it will be popular on all sites so, sought out the Rotten Tomatoes site and indeed all movies listed are popular as shown below
comp = {"Bitter Harvest": 1509, "Table 19": 7167, "Whiskey Tango Foxtrot": 22424, "Arsenal": 282, "Gold": 856, "Pele: Birth of a Legened": 1540, "Sausage Party": 64819, "The Secret Life of Pets": 82469, "Kung Fu Panda 3": 99897, "Cafe Society": 13733}
df = pd.DataFrame(list(comp.items()),columns = ['Movie','Votes'])
df
Movie | Votes | |
---|---|---|
0 | The Secret Life of Pets | 82469 |
1 | Table 19 | 7167 |
2 | Kung Fu Panda 3 | 99897 |
3 | Cafe Society | 13733 |
4 | Gold | 856 |
5 | Pele: Birth of a Legened | 1540 |
6 | Bitter Harvest | 1509 |
7 | Sausage Party | 64819 |
8 | Arsenal | 282 |
9 | Whiskey Tango Foxtrot | 22424 |
From the analysis, all the movies have more than 30 votes which is way larger than even Hickey's threshhold
hickey_rates.head(3)
FILM | Fandango_Stars | Fandango_Ratingvalue | Fandango_votes | Fandango_Difference | |
---|---|---|---|---|---|
0 | Avengers: Age of Ultron (2015) | 5.0 | 4.5 | 14846 | 0.5 |
1 | Cinderella (2015) | 5.0 | 4.5 | 12640 | 0.5 |
2 | Ant-Man (2015) | 5.0 | 4.5 | 12055 | 0.5 |
hickey_rates['Year'] = hickey_rates['FILM'].str[-5:-1]
hickey_rates.head(3)
/dataquest/system/env/python3/lib/python3.4/site-packages/ipykernel/__main__.py:1: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy if __name__ == '__main__':
FILM | Fandango_Stars | Fandango_Ratingvalue | Fandango_votes | Fandango_Difference | Year | |
---|---|---|---|---|---|---|
0 | Avengers: Age of Ultron (2015) | 5.0 | 4.5 | 14846 | 0.5 | 2015 |
1 | Cinderella (2015) | 5.0 | 4.5 | 12640 | 0.5 | 2015 |
2 | Ant-Man (2015) | 5.0 | 4.5 | 12055 | 0.5 | 2015 |
hickey_final = hickey_rates[hickey_rates["Year"] == "2015"]
hickey_final.head(3)
FILM | Fandango_Stars | Fandango_Ratingvalue | Fandango_votes | Fandango_Difference | Year | |
---|---|---|---|---|---|---|
0 | Avengers: Age of Ultron (2015) | 5.0 | 4.5 | 14846 | 0.5 | 2015 |
1 | Cinderella (2015) | 5.0 | 4.5 | 12640 | 0.5 | 2015 |
2 | Ant-Man (2015) | 5.0 | 4.5 | 12055 | 0.5 | 2015 |
dataquest_rates.head(3)
movie | year | fandango | |
---|---|---|---|
0 | 10 Cloverfield Lane | 2016 | 3.5 |
1 | 13 Hours | 2016 | 4.5 |
2 | A Cure for Wellness | 2016 | 3.0 |
dataquest_rates["year"].value_counts()
2016 191 2017 23 Name: year, dtype: int64
dataquest_final = dataquest_rates[dataquest_rates["year"] == 2016]
dataquest_final["year"].value_counts()
2016 191 Name: year, dtype: int64
dataquest_2017 = dataquest_rates[dataquest_rates["year"] == 2017]
import matplotlib.pyplot as plt
from numpy import arange
%matplotlib inline
plt.style.use('fivethirtyeight')
hickey_final['Fandango_Stars'].plot.kde(label = '2015', legend = True, figsize = (8,5.5))
dataquest_final['fandango'].plot.kde(label = '2016', legend = True)
plt.title("Comparing distribution shapes for Fandango's ratings\n(2015 vs 2016)",
y = 1.07) # the `y` parameter pads the title upward
plt.xlabel('Stars')
plt.xlim(0,5) # because ratings start at 0 and end at 5
plt.xticks(arange(0,5.1,.5))
plt.show()
They both take a negatively tailed distribution, with the 2016 ratings taking a a negatively skewed normal distribution
The ratings by Fandango changed in 2016 and now most movies have a rating between 3.5 and 4.5, values lower than the 2015 ratings which are similar to the ratings of the aither moview review sites.
print(2015, "\n", "--------------------------")
hickey_final["Fandango_Stars"].value_counts().sort_index()
2015 --------------------------
3.0 11 3.5 23 4.0 37 4.5 49 5.0 9 Name: Fandango_Stars, dtype: int64
print(2015, "\n", "--------------------------")
hickey_final["Fandango_Stars"].value_counts(normalize = True).sort_index()*100
2015 --------------------------
3.0 8.527132 3.5 17.829457 4.0 28.682171 4.5 37.984496 5.0 6.976744 Name: Fandango_Stars, dtype: float64
print(2016, "\n", "--------------------------")
dataquest_final["fandango"].value_counts().sort_index()
2016 --------------------------
2.5 6 3.0 14 3.5 46 4.0 77 4.5 47 5.0 1 Name: fandango, dtype: int64
dataquest_final["fandango"].value_counts(normalize = True).sort_index()*100
2.5 3.141361 3.0 7.329843 3.5 24.083770 4.0 40.314136 4.5 24.607330 5.0 0.523560 Name: fandango, dtype: float64
Both the abasolute frequency and relative frequence produce a similar result but it's the relative frequencies that give a clear distiction in the direction of the ratings.
In 2015, the highest ratings were 4.5 stars and the lowest rating was 3.0 no movie was rated below this taing. While, in 2016 3.5 and4.5 stars shared the highest rating, some movies were also rated as low 2.5 which seems realistic.
mean_2015 = hickey_final["Fandango_Stars"].mean()
mean_2016 = dataquest_final["fandango"].mean()
median_2015 = hickey_final["Fandango_Stars"].median()
median_2016 = dataquest_final["fandango"].median()
mode_2015 = hickey_final["Fandango_Stars"].mode()[0]
mode_2016 = dataquest_final["fandango"].mode()[0]
summary_stats = pd.DataFrame({"mean": [mean_2015, mean_2016],
"median":[median_2015, median_2016],
"mode": [mode_2015, mode_2016]}, index=['2015', '2016']).T
summary_stats
2015 | 2016 | |
---|---|---|
mean | 4.085271 | 3.887435 |
median | 4.000000 | 4.000000 |
mode | 4.500000 | 4.000000 |
plt.style.use('fivethirtyeight')
summary_stats['2015'].plot.bar(color = '#0066FF', align = 'center', label = '2015', width = .25)
summary_stats['2016'].plot.bar(color = '#CC0000', align = 'edge', label = '2016', width = .25,
rot = 0, figsize = (8,5))
plt.title('Comparing summary statistics: 2015 vs 2016', y = 1.07)
plt.ylim(0,5.5)
plt.yticks(arange(0,5.1,.5))
plt.ylabel('Stars')
plt.legend(framealpha = 0, loc = 'upper center')
plt.show()
There is a slight decrease in the Fandago's movie ratings compared to the way it was before in 2014 and 2016. It can't be conclunsive from the analysis this analysis that the bug in their site was fixed. However one thing we can say fo sure is that Fandago also decided to adopt the Rotten Tomato ratings as of now as per the desclaimer on their site which could mean that they have really taken time and looked at the accuracy of their ratings(https://www.fandango.com/table-19-193118/movie-reviews).