├── static ├── test.txt └── favicon.ico ├── templates ├── test.txt ├── viz.html ├── index.html └── base.html ├── passenger_wsgi.py ├── README.md └── app.py /static/test.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /templates/test.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thepiratex/tvshow/HEAD/static/favicon.ico -------------------------------------------------------------------------------- /passenger_wsgi.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | # sys.path.insert(0, os.path.dirname(__file__)) 5 | 6 | 7 | # def application(environ, start_response): 8 | # start_response('200 OK', [('Content-Type', 'text/plain')]) 9 | # message = 'It works!\n' 10 | # version = 'Python %s\n' % sys.version.split()[0] 11 | # response = '\n'.join([message, version]) 12 | # return [response.encode()] 13 | 14 | 15 | 16 | 17 | from app import app as application -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TV Show 2 | An app to display insights for a tv show such as heatmap, trend line, best episode, worst episode etc. Have suggestion? Let me know 3 | 4 | ## Libraries Used 5 | 6 | * [IMDBPY](https://imdbpy.readthedocs.io/en/latest/) 7 | To pull ratings, name, and other information for all the tv shows from IMDB 8 | 9 | * [FLask](https://flask.palletsprojects.com/en/1.1.x/) 10 | A microframework to basically connect our python code with web interface. 11 | 12 | * [Plot.Ly](https://plot.ly/python) 13 | A visualization library for python which supports both python and javascript. 14 | 15 | ### [Click here to check the Live App](https://dataiszen.com/app/tvshow) 16 | 17 | ## Screenshots of the app 18 | ![Demo_Image_1](https://i.imgur.com/ocxdoPx.png) 19 | ![Demo_Image_1](https://i.imgur.com/MrFoHSC.png) 20 | ![Demo_Image_1](https://i.imgur.com/AgH6PFt.png) 21 | 22 | 23 | ## Bottleneck 24 | IMDBPY api primarily takes the majority of time in retrieving the data but hey, it's a free service and provides everything for free. Donate a little there if possible. 25 | 26 | ## Author 27 | I built this as a way of displaying my technical abilities to companies looking to hire a data analyst with a curious mindset 28 | 29 | ## Looking for more? 30 | 31 | A better UI & faster version developed by [Ian](https://github.com/ianthekid) can be found [here](https://tvchart.ianray.com/) with the full [code here](https://github.com/ianthekid/tvcharts) 32 | -------------------------------------------------------------------------------- /templates/viz.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} TV Series Rating Heatmap {% endblock %} 4 | 5 | {% block head %} 6 | {{ super() }} 7 | 8 | {% endblock %} 9 | 10 | {% block content %} 11 | 12 |
13 |

{{ title }}

14 | 15 |
16 |
17 |
18 |
19 | 20 |
21 |
22 | 26 |
27 |
28 | 29 |
30 |
31 |
32 |
33 | 34 |
35 |
36 | 40 |
41 |
42 |
43 |
44 |

Heatmap

45 |
46 |
47 |
48 | {{ hm | safe }} 49 |
50 |
51 |

Trendline

52 |
53 |
54 |
55 | {{ lc | safe }} 56 |
57 |
58 |
59 | 62 |
63 |
64 | {% endblock %} 65 | 66 | {% block scripts %} 67 | {{ super() }} 68 | {% endblock %} -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %} TV Series Rating Heatmap {% endblock %} 5 | 6 | {% block head %} 7 | {{ super() }} 8 | 22 | {% endblock %} 23 | 24 | {% block content %} 25 | 26 | 27 | 28 |
29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 |
37 |

Pick a TV Show to see a detailed analysis

38 |
39 |
40 | 41 | 42 |
43 |
44 |
45 | {{ form.hidden_tag() }} 46 |
47 | {{ form.option(class='form-control') }} 48 |
49 | 50 | 51 |
52 | {# #} 54 | 55 | 56 | 57 |
58 |
59 | 60 |
61 |
62 | Complete Code on Github 63 |

64 | This webapp is hosted on a student budget. It might take more than a few seconds. Please hold your horses. 65 |

66 |
67 | 68 |
69 | 70 |
71 | {% endblock %} 72 | 73 | {% block scripts %} 74 | {{ super() }} 75 | 76 | 82 | {% endblock %} -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | {% block doc -%} 2 | 3 | 4 | {%- block html %} 5 | 6 | {%- block head %} 7 | 8 | 9 | 16 | 17 | 18 | {% block title %}{{ title|default }}{% endblock title %} 19 | 20 | {%- block metas %} 21 | 22 | 23 | 24 | {%- endblock metas %} 25 | 26 | {%- block styles %} 27 | 28 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | {%- endblock styles %} 42 | {%- endblock head %} 43 | 44 | 45 | {% block body -%} 46 | {% block navbar %} 47 | 48 | 63 | 64 | 65 | {%- endblock navbar %} 66 | {% block content -%} 67 | {%- endblock content %} 68 | 69 | {% block scripts %} 70 | 71 | 72 | 73 | 76 | 79 | 80 | {%- endblock scripts %} 81 | {%- endblock body %} 82 | 83 | {%- endblock html %} 84 | 85 | {% endblock doc -%} -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, session, request, redirect, url_for, jsonify 2 | from flask_bootstrap import Bootstrap 3 | import os 4 | os.environ["OMP_NUM_THREADS"] = "1" 5 | import numpy as np 6 | import plotly 7 | import plotly.figure_factory as ff 8 | from imdb import IMDb 9 | from flask_wtf import FlaskForm 10 | from wtforms import SelectField, SubmitField 11 | import plotly.graph_objects as go 12 | app = Flask(__name__) 13 | app.config['SECRET_KEY'] = 'ABCD' 14 | Bootstrap(app) 15 | # pip install Werkzeug==0.16.0 will work. Other version might give error 16 | figures = {} 17 | 18 | 19 | def draw_heatmap(ID): 20 | ia = IMDb() 21 | series = ia.get_movie(ID) 22 | ia.update(series, 'episodes') 23 | 24 | def get_ratings(series): 25 | fin_list = [] 26 | for season in sorted(series['episodes']): 27 | ep_list = [] 28 | for episode in sorted(series['episodes'][season]): 29 | result = series['episodes'][season][episode] 30 | try: 31 | rating = round(result.get('rating'), 2) 32 | except TypeError: 33 | rating = 0 34 | ep_list.append(rating) 35 | fin_list.append(ep_list) 36 | return fin_list 37 | 38 | fin_list = get_ratings(series) 39 | 40 | def get_max_number_of_episodes(fin_list): 41 | temp = [] 42 | for l in fin_list: 43 | temp.append(len(l)) 44 | return max(temp) 45 | 46 | max_episode = get_max_number_of_episodes(fin_list) 47 | max_episode += 1 48 | 49 | def get_heatmap_array(max_episode): 50 | fin_list = [] 51 | for season in sorted(series['episodes']): 52 | ep_list = [0] * max_episode 53 | for episode in sorted(series['episodes'][season]): 54 | result = series['episodes'][season][episode] 55 | try: 56 | rating = round(result.get('rating'), 2) 57 | except TypeError: 58 | rating = 0 59 | for i, values in enumerate(ep_list): 60 | if i == episode: 61 | ep_list[i] = rating 62 | fin_list.append(ep_list) 63 | fin_array = np.array([np.array(xi) for xi in fin_list]) 64 | x_episodes = np.arange(0, max_episode) 65 | x_episodes = x_episodes.tolist() 66 | y_seasons = sorted(series['episodes']) 67 | return x_episodes, y_seasons, fin_array 68 | 69 | x_episodes, y_seasons, fin_array = get_heatmap_array(max_episode) 70 | 71 | def plotly_heatmap(x_episodes, y_seasons, fin_array): 72 | if len(y_seasons) <= 10: 73 | adj_height = 500 74 | elif len(y_seasons) >= 10: 75 | adj_height = 800 76 | 77 | colorscale = [[0.0, 'rgb(255,255,255)'], [.2, 'rgb(239,154,154)'], 78 | [.4, 'rgb(198,40,40)'], [.6, 'rgb(183,28,28)'], 79 | [.8, 'rgb(55,71,79)'], [1.0, 'rgb(38,50,56)']] 80 | 81 | fig = ff.create_annotated_heatmap(z=fin_array, x=x_episodes, y=y_seasons, 82 | zmin=0, zmax=10, colorscale=colorscale, 83 | hoverongaps=False, font_colors=['white'], 84 | hovertemplate='Season: %{y}' + 85 | '
Episode: %{x}' + 86 | '
Rating: %{z}' + 87 | '', 88 | annotation_text=fin_array, showlegend=False) 89 | 90 | fig.update_layout( 91 | height=adj_height, 92 | xaxis_title="Episode #", 93 | yaxis_title="Season #", 94 | font=dict( 95 | family="Roboto", 96 | size=14, 97 | color="#7f7f7f" 98 | ), 99 | paper_bgcolor='rgba(0,0,0,0)', 100 | plot_bgcolor='rgba(0,0,0,0)', 101 | xaxis=dict(tickmode='linear', fixedrange=True), 102 | yaxis=dict(autorange="reversed", fixedrange=True), 103 | hoverlabel=dict(bgcolor="white"), showlegend=False 104 | ) 105 | 106 | fig['data'][0]['showscale'] = True 107 | # fig.show(config=dict(displayModeBar=False)) 108 | fig_json = fig.to_json() 109 | return fig_json 110 | 111 | heatmap = plotly_heatmap(x_episodes, y_seasons, fin_array) 112 | 113 | def get_linechart(): 114 | y_rating = [] 115 | x_season_episode = [] 116 | ep_name = [] 117 | 118 | for season in sorted(series['episodes']): 119 | for episode in sorted(series['episodes'][season]): 120 | result = series['episodes'][season][episode] 121 | try: 122 | rating = round(result.get('rating'), 2) 123 | except TypeError: 124 | rating = None 125 | x_season_episode.append('S{}E{}'.format(season, episode)) 126 | y_rating.append(rating) 127 | ep_name.append(result) 128 | 129 | best_ep_num = x_season_episode[y_rating.index(np.nanmax(np.array(y_rating, dtype=np.float64)))] 130 | best_ep_name = ep_name[y_rating.index(np.nanmax(np.array(y_rating, dtype=np.float64)))] 131 | worst_ep_num = x_season_episode[y_rating.index(np.nanmin(np.array(y_rating, dtype=np.float64)))] 132 | worst_ep_name = ep_name[y_rating.index(np.nanmin(np.array(y_rating, dtype=np.float64)))] 133 | 134 | fig = go.Figure(data=go.Scatter(x=x_season_episode, y=y_rating, hovertemplate='Episode: %{x}' + 135 | '
Rating: %{y}' + 136 | '', 137 | line=dict(color='firebrick', width=4) 138 | 139 | )) 140 | fig.update_xaxes(tickangle=0, nticks=10, 141 | showline=True, 142 | showgrid=False, 143 | showticklabels=True, 144 | linecolor='rgb(204, 204, 204)', 145 | linewidth=1) 146 | fig.update_yaxes(rangemode="tozero", 147 | showticklabels=True, ticks="outside", tickwidth=1, nticks=11, showline=True, 148 | linecolor='rgb(204, 204, 204)') 149 | 150 | fig.update_layout( 151 | xaxis_title="Episode", 152 | yaxis_title="Rating", 153 | font=dict( 154 | family="Roboto", 155 | size=17, 156 | color="#7f7f7f" 157 | ), 158 | paper_bgcolor='rgba(0,0,0,0)', 159 | plot_bgcolor='rgba(0,0,0,0)', 160 | hoverlabel=dict(bgcolor="white") 161 | ) 162 | fig_json = fig.to_json() 163 | return fig_json, best_ep_num, best_ep_name, worst_ep_num, worst_ep_name 164 | 165 | line_chart, best_ep_num, best_ep_name, worst_ep_num, worst_ep_name = get_linechart() 166 | 167 | title = series['title'] 168 | 169 | return heatmap, line_chart, title, best_ep_num, best_ep_name, worst_ep_num, worst_ep_name 170 | 171 | 172 | choice_list = [('0306414', 'The Wire'), 173 | ('0141842', 'The Sporanos'), 174 | ('0903747', 'Breaking Bad '), 175 | ('3032476', 'Better Call Saul'), 176 | ('0944947', 'Game of Thrones'), 177 | ('1475582', 'Sherlock'), 178 | ('0108778', 'Friends'), 179 | ('0412142', 'House'), 180 | ('2149175', 'The Americans'), 181 | ('2442560', 'PeakyBlinders'), 182 | ('2802850', 'Fargo'), 183 | ('0460649', 'How I Met Your Mother'), 184 | ('0411008', 'Lost'), 185 | ('0804503', 'Mad Men'), 186 | ('2356777', 'True Detective'), 187 | ('0303461', 'Firefly'), 188 | ('0979432', 'Boardwalk Empire'), 189 | ('0436992', 'Doctor Who'), 190 | ('1474684', 'Luther'), 191 | ('0904208', 'Californication'), 192 | ('0098936', 'Twin Peaks'), 193 | ('0106179', 'The X-Files'), 194 | ('2401256', 'The Night of'), 195 | ('2085059', 'Black Mirror'), 196 | ('1856010', 'House of Cards'), 197 | ('0773262', 'Dexter'), 198 | ('2575988', 'Silicon Valley'), 199 | ('1119644', 'Fringe'), 200 | ('4158110', 'Mr. Robot'), 201 | ('0472954', 'It\'s Always Sunny in Philadelphia'), 202 | ('1839578', 'Person of Interest'), 203 | ('1219024', 'Castle'), 204 | ('4288182', 'Atlanta'), 205 | ('1586680', 'Shameless'), 206 | ('2467372', 'Brookylen Nine-Nine'), 207 | ('1520211', 'The Walking Dead'), 208 | ('1103987', 'Leverage'), 209 | ('3322312', 'Daredevil'), 210 | ('0898266', 'The Big Bang Theory'), 211 | ('4574334', 'Stranger Things'), 212 | ('2707408', 'Narcos'), 213 | ('2243973', 'Hannibal'), 214 | ('0455275', 'Prison Break'), 215 | ('0475784', 'West World'), 216 | ('1442437', 'Modern Family'), 217 | ('1844624', 'American Horror Story'), 218 | ('0121955', 'South Park'), 219 | ('3398228', 'Bojack Horsemen'), 220 | ('2861424', 'Rick & Morty')] 221 | 222 | choice_list = sorted(choice_list, key=lambda tup: tup[1]) 223 | 224 | 225 | class DropDownForm(FlaskForm): 226 | option = SelectField(u'Pick a TV Show', choices=choice_list) 227 | submit = SubmitField('Sign Up') 228 | 229 | 230 | @app.route('/', methods=['GET', 'POST']) 231 | def index(): 232 | form = DropDownForm() 233 | if form.validate_on_submit(): 234 | print(form.option.data) 235 | heat_map, line_chart, title, best_ep_num, best_ep_name, worst_ep_num, worst_ep_name = draw_heatmap( 236 | form.option.data) 237 | figures['heat_map'] = heat_map 238 | figures['line_chart'] = line_chart 239 | figures['title'] = title 240 | figures['best_ep_num'] = best_ep_num 241 | figures['best_ep_name'] = best_ep_name 242 | figures['worst_ep_num'] = worst_ep_num 243 | figures['worst_ep_name'] = worst_ep_name 244 | return redirect(url_for('viz')) 245 | 246 | return render_template('index.html', form=form) 247 | 248 | 249 | @app.route('/viz', methods=['GET', 'POST']) 250 | def viz(): 251 | heat_map = plotly.io.from_json(figures['heat_map'], output_type='Figure', skip_invalid=False) 252 | heat_map = plotly.offline.plot(heat_map, config={"displayModeBar": False}, show_link=False, 253 | include_plotlyjs=False, output_type='div') 254 | 255 | line_chart = plotly.io.from_json(figures['line_chart'], output_type='Figure', skip_invalid=False) 256 | line_chart = plotly.offline.plot(line_chart, config={"displayModeBar": False}, show_link=False, 257 | include_plotlyjs=False, output_type='div') 258 | 259 | title = figures['title'] 260 | best_ep_num = figures['best_ep_num'] 261 | best_ep_name = figures['best_ep_name'] 262 | worst_ep_num = figures['worst_ep_num'] 263 | worst_ep_name = figures['worst_ep_name'] 264 | return render_template('viz.html', hm=heat_map, lc=line_chart, title=title, 265 | best_ep_num=best_ep_num, best_ep_name=best_ep_name, worst_ep_num=worst_ep_num, 266 | worst_ep_name=worst_ep_name) 267 | 268 | 269 | if __name__ == '__main__': 270 | app.run() 271 | --------------------------------------------------------------------------------