├── 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 | 
19 | 
20 | 
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 |
23 |
Best Episode
24 |
{{ best_ep_num |safe }} : {{ best_ep_name |safe }}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
Worst Episode
38 |
{{ worst_ep_num |safe }} : {{ worst_ep_name |safe }}
39 |
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 |
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 |
52 | {#
#}
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Complete Code on Github
63 |
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 |
49 | TV Show Analyser
50 |
52 |
53 |
54 |
55 |
58 |
59 |
Blog
60 |
61 |
62 |
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 |
--------------------------------------------------------------------------------