Plotly dash-app hosted in Heroku – How to make visualization dashingly more interesting

In my previous blog post (Are there any language detection tools for assigning language to music data?), I descibed my failed attempts att concatenating Artist Origin (or, to be more precise, the artists origin with respect to the language sung in) to a dataset created from Spotify’s Web developer API. This information used to be available in The Echo Nest, but it was deprecated and Artist origin was not migrated to the new API. My idea was thus to use a well chosen language detect library and apply it to both Artist names and song titles. However, due to the fact that artists in many cases have names that do not reflect their origin, applying these tools on Artist names turned out to be a dead-end. Even som of the most obvious origins turned out to reslt in weird behaviors, e.g. Jay Chou, a chinese singer, was identified as Klingon (Yes, I did laugh when I discovered this). As for song title, the amount of information in the language of origin and often titles in another language made the task sufficiently shaky and blunt that it was useless.

Why did I want to do this? Well, the end game was not to show that it was possible. What I was after was a way to append new information to a dataset in order to use it for an interactive plot using plotly. Although there was a lesson taught, I did not abandon the idea and went for it “brute force”. So, in this blog post, I am going to do what I originally set out to do, create an interactive plot over the popularity of artists globally and in their country of origin (or rather a language specific popularity).

To make the whole thing a little more interesting, I will also show how to turn this plot into an app and deploy it on heroku.

Saving the day with brute force

So, as I mentioned above, I refused to give up on appending country origin to the dataset I started working with. However, and since the goal isn’t to create a completely new and improved dataset anymore, I chose to restrict the work to the following countries/languages

  1. China/Chinese
  2. France/French
  3. Sweden/Swedish
  4. USA/English
  5. UK/English
  6. South Korea/Korean

Why these? France because I’m french, Sweden because I live in Sweden, USA and UK because most people know at least a few artists and songs from these countries, China and South Korea because I love their music. So, what do I mean by brute force here. Well, simply that I scraped as much as possible on different sites (e.g. Wikipedia) for artist names from each country and created lists save as csv-files. Now, these might not be complete but I reckoned that if they are in some public list over singer/artists, chances are they end up also being in the dataset from Spotify.

Since this post is a little longer than what I usually write, I will not go inte the details of how to make the lists and concatenate them into one single dataframe. However, after having added the Aritst Origin/Language, I added two things. 1) The decade for the song and a Language specific ranking. Indeed, the popularity in the Spotify dataframe is a global one from 1 to 100. If we, for instance, look at popularity of French songs the max value for the most popular franch song might be very low. Hence, the need of a new ranking and a normalization procedure.

dfs = [Spotify_Chinese, Spotify_French, Spotify_Swedish, Spotify_SouthKorean, Spotify_USA, Spotify_UK]

SPotify_Reduced = pd.concat(dfs, ignore_index=True)

SPotify_Reduced.loc[(SPotify_Reduced['year'] >= 1920) & (SPotify_Reduced['year'] < 1930), 'Song Decade'] = '1920'
SPotify_Reduced.loc[(SPotify_Reduced['year'] >= 1930) & (SPotify_Reduced['year'] < 1940), 'Song Decade'] = '1930'
SPotify_Reduced.loc[(SPotify_Reduced['year'] >= 1940) & (SPotify_Reduced['year'] < 1950), 'Song Decade'] = '1940'
SPotify_Reduced.loc[(SPotify_Reduced['year'] >= 1950) & (SPotify_Reduced['year'] < 1960), 'Song Decade'] = '1950'
SPotify_Reduced.loc[(SPotify_Reduced['year'] >= 1960) & (SPotify_Reduced['year'] < 1970), 'Song Decade'] = '1960'
SPotify_Reduced.loc[(SPotify_Reduced['year'] >= 1970) & (SPotify_Reduced['year'] < 1980) , 'Song Decade'] = '1970'
SPotify_Reduced.loc[(SPotify_Reduced['year'] >= 1980) & (SPotify_Reduced['year'] < 1990) , 'Song Decade'] = '1980'
SPotify_Reduced.loc[(SPotify_Reduced['year'] >= 1990) & (SPotify_Reduced['year'] < 2000) , 'Song Decade'] = '1990'
SPotify_Reduced.loc[(SPotify_Reduced['year'] >= 2000) & (SPotify_Reduced['year'] < 2010) , 'Song Decade'] = '2000'
SPotify_Reduced.loc[(SPotify_Reduced['year'] >= 2010) & (SPotify_Reduced['year'] < 2020) , 'Song Decade'] = '2010'
SPotify_Reduced.loc[(SPotify_Reduced['year'] >= 2020) & (SPotify_Reduced['year'] < 2030) , 'Song Decade'] = '2020'

SPotify_Reduced['rank'] = SPotify_Reduced.groupby('Artist origin')['popularity'].rank('dense', ascending=True)
SPotify_Reduced['Language Specific Popularity'] = SPotify_Reduced.groupby('Artist origin')['rank'].apply(lambda x: 1+100*(x-x.min())/(x.max()-x.min()))
SPotify_Reduced['Language Specific Popularity'] = SPotify_Reduced['Language Specific Popularity'].round(0)

The last step is to add understandable information about keys. In the dataset, keys are labelled 1-11 but most people read them as C, D, E, F G, A, H (and #, b). Hence the following code:

music_keys = {0: 'C', 1: 'C#,Db', 2: 'D', 3: 'D#,Eb', 4: 'E', 5: 'F', 6: 'F#,Gb', 7: 'G', 8: 'G#,Ab', 9: 'A', 10: 'A#,Bb', 11: 'B'}
Spotify['music_key'] = Spotify['key'].map(music_keys)

An intresting observation is the distribution of songs in different keys:

plt.figure(figsize=(20,20))
sns.countplot(x = 'music_key', data=Spotify , order=Spotify['music_key'].value_counts().index)
plt.title("Count songs by keys")
plt.show()

The G, Em, C and D progressions are the most common and also the easiest for non-musicians (or common people) to remember and sing-along to.

Let’s get some statistics on this dataframe:

columns = ['id', 'Title', 'artists', 'release_date', 'year','Artist origin','Song Decade','Language Specific Popularity','keys']
for col in columns:
  print(f'{col:<30}: {SPotify_Reduced[col].nunique()} unique values')

Spotify = SPotify_Reduced.drop(labels=['id', 'release_date'], axis=1)

numeric_columns = Spotify.columns[Spotify.dtypes != 'object']
string_columns = Spotify.columns[Spotify.dtypes == 'object']
print(f'There are {len(numeric_columns)} numeric columns & {len(string_columns)} string columns')

which returns

id                            : 30579 unique values
Title                         : 24811 unique values
artists                       : 2062 unique values
release_date                  : 3526 unique values
year                          : 100 unique values
Artist origin                 : 6 unique values
Song Decade                   : 11 unique values
Language Specific Popularity  : 101 unique values
music_key                     : 11 unique values

There are 17 numeric columns & 5
 string columns

There are plently of things that can be done with this dataset (or att least the original one). I played around with it and among the things I constructed was a recommendation engine based upon song that I likes and for which I extracted the different features, such as valence, danceability, speechiness and so on. I then found all the songs “sharing” these properties. Worked quite well. Nothing revolutionary though, it’s been done before.

What I’m intrested of is to graphically see which songs were popular in different decades, different countries (again, this is a diffuse concept) and in which key different artist’s most popular song were. So, let’s start with what one would do traditionally.

Static plots barplots and their limitations

Recall that what I set out to do was to be able to visualize popularity of artists over decades, origin and the keys in which their songs were. So, an easy way to do at least of of these things would be to use matplotlib or seaborn. Here is an example for visualizing popularity of artists as given in the Spotify dataset:

Here I defined subgroups of my original dataset to isolate rows for artists after year 2000, for the 90s and the 80s.

Spotify = SPotify_Reduced.drop(['explicit','mode'],axis = 1)
Spotify['artists'] = Spotify['artists'].map(lambda x: x.lstrip("\'\[").rstrip("\'\]"))
Spotify['artists'] = Spotify['artists'].str.replace(r"\', \'", ",")

Spotify_2000 = Spotify.loc[Spotify['year'] >= 2000]
Spotify_1990s = Spotify.loc[(Spotify['year'] >= 1990) & (Spotify['year'] < 2000) ]
Spotify_1980s = Spotify.loc[(Spotify['year'] >= 1980) & (Spotify['year'] < 1990) ]

fig, ax1 = plt.subplots(figsize = (12, 10))
lead_artists = Spotify_1990s.groupby('artists')['popularity'].sum().sort_values(ascending=False).head(50)
ax1 = sns.barplot(x=lead_artists.values, y=lead_artists.index, palette="Blues", orient="h", edgecolor='white', ax=ax1)
ax1.set_xlabel('Popularity (Count of presence in the dataset Spotify)', c='w', fontsize=16)
ax1.set_ylabel('Artist', c='w', fontsize=16)
ax1.set_title('30 Most Popular Artists', c='w', fontsize=14, weight = 'bold')
plt.show()

In this case, I get the following plot

It’s quite ok, but I’d really like to see other decades without having to create yet another graph, e.g,

fig1 = plt.figure(figsize=[30,30])
gs  = gridspec.GridSpec(100,100)

ax1 = fig1.add_subplot(gs[0:45,0:40])
ax2 = fig1.add_subplot(gs[0:45,60:100])
ax3 = fig1.add_subplot(gs[55:100,0:40])
ax4 = fig1.add_subplot(gs[55:100,60:100])

lead_artists1 = Spotify.groupby('artists')['popularity'].sum().sort_values(ascending=False).head(30)
ax1 = sns.barplot(x=lead_artists1.values, y=lead_artists1.index, palette="Blues", orient="h", edgecolor='white', ax=ax1)
ax1.set_xlabel('Popularity All time (Count of presence in the dataset Spotify)', c='w', fontsize=16)
ax1.set_ylabel('Artist', c='w', fontsize=16)
ax1.set_title('30 Most Popular Artists past 100 years', c='w', fontsize=20, weight = 'bold')


lead_artists2 = Spotify_2000.groupby('artists')['popularity'].sum().sort_values(ascending=False).head(30)
ax2 = sns.barplot(x=lead_artists2.values, y=lead_artists2.index, palette="Reds", orient="h", edgecolor='white', ax=ax2)
ax2.set_xlabel('Popularity (Count of presence in the dataset Spotify_2000)', c='w', fontsize=16)
ax2.set_ylabel('Artist', c='w', fontsize=16)
ax2.set_title('30 least Popular Artists 00s', c='w', fontsize=20, weight = 'bold')


lead_artists3 = Spotify_1990s.groupby('artists')['popularity'].sum().sort_values(ascending=False).head(30)
ax3 = sns.barplot(x=lead_artists3.values, y=lead_artists3.index, palette="Greens", orient="h", edgecolor='white', ax=ax3)
ax3.set_xlabel('Popularity 1990s (Count of presence in the dataset Spotify_1980s)', c='w', fontsize=16)
ax3.set_ylabel('Artist', c='w', fontsize=16)
ax3.set_title('30 most Popular Artists 90s', c='w', fontsize=20, weight = 'bold')

lead_artists4 = Spotify_1980s.groupby('artists')['popularity'].sum().sort_values(ascending=False).head(30)
ax4 = sns.barplot(x=lead_artists4.values, y=lead_artists4.index, palette="YlOrBr", orient="h", edgecolor='white', ax=ax4)
ax4.set_xlabel('Popularity 1980s (Count of presence in the dataset Spotify_1980s)', c='w', fontsize=16)
ax4.set_ylabel('Artist', c='w', fontsize=16)
ax4.set_title('30 most Popular Artists 80s', c='w', fontsize=20, weight = 'bold')

plt.show()

The thing is that if I wish to have the same graphs by origins and by keys, I’d have to generate graph upon graph and honestly, my patience has limits. Also, from the perspective of a reader, I must say I would loose intrest very quickly. Needless to say, after a couple of graphs like these, I got so bored that I decided to spend some time learning how to use dash and plotly in order to make ONE multi-purpose app.

Dash and plotly with dropdown options

Ok. I have my dataset, I have decided how which features I want to be able to focus on: Origin, musik_key and decade. I could, of course add a few, but the for the puspose of this post, I think this is enough. Let’s start by looking at how a dash app using plotly is structured:

app = dash.Dash()
app.layout = html.Div([
    html.H2("TITLE"),
    html.Div(
        [
            dcc.Dropdown(
                id="feature 1",
                options=[{
                    'label': i,
                    'value': i
                } for i in option1],
                value='option1 value'
            ),
            dcc.Dropdown(
                id="feature 2",
                options=[{
                    'label': i,
                    'value': i
                } for i in option2],
                value='option2 value'
             ),
            dcc.Dropdown(
                id="feature 3",
                options=[{
                    'label': i,
                    'value': i
                } for i in option3],
                value='option3 value'
            ),
        ],
        style={'width': '25%',
               'display': 'inline-block'}),
     dcc.Graph(id='funnel-graph'),
    ])

@app.callback(
    dash.dependencies.Output('funnel-graph', 'figure'),
    [dash.dependencies.Input('feature 1','value'),
    dash.dependencies.Input('feature 2','value'),
    dash.dependencies.Input('feature 3','value')])
    
def update_graph(feature 1,feature 2,feature 3):
    if 'something on you df'
    
    elif 'something else on you df'
    elif 'something else on you df'
   elif 'something else on you df'
        Spotify_plot = Spotify[(Spotify['music_key']==music_key) & (Spotify['Song_Decade'].astype(str)==Song_Decade) & (Spotify['Artist_origin'].astype(str)==Artist_origin)]
        
    trace1 = go.Bar()
    trace2 = go.Bar()

    return {
        'data': [trace1, trace2],
        'layout':
        go.Layout(
            title='what this graph shows {} for this feature {} and this feature {} key'.format(feature 1,feature 2,feature 3),
            barmode ='group',plot_bgcolor='rgb(30, 215, 96)',paper_bgcolor='rgb(30, 215, 96)')
    }
if __name__ == '__main__':
    app.run_server()

As you can see, the first step is to define that it actually is a dash app you’re working with. The second step is to define the layout on a web page, define the dropdown options, i.e. how your dataset is going to be sliced. The third moment if to instruct how the dataset is going to be sliced given the chosen options. Note the @app.callback instructing on the graph dependencies and the graph_update function. Last, You need to give the instruction on what is supposed to be returned. Above, the data in barplots (trace1 and trace2), given the chosen layout upon which labelling is added.

Since I chose music keys, language origin and song decades as options, I need to list the unique possible values:

key_options          = Spotify["music_key"].astype(str).unique()
language_options     = Spotify["Artist_origin"].astype(str).unique()
decade_options       = Spotify["Song_Decade"].astype(str).unique()

These will the be used to define the dropdown selections. Note that id is the feature chosen (music_key, Artist_origin and Song_Decade) and that if no value for the option is chosen, then the dataset is not sliced on specific values of the feature, ie.. ALL values are chosen. I also define what kind of graph to return, here funnel-graph.

app = dash.Dash()
#app.layout = html.Div(html.H1('Heading', style={'backgroundColor':'blue'})
app.layout = html.Div([
    html.H2("Popularity of songs"),
    html.Div(
        [
            dcc.Dropdown(
                id="music_key",
                options=[{
                    'label': i,
                    'value': i
                } for i in key_options],
                value='All music keys'
            ),
            dcc.Dropdown(
                id="Artist_origin",
                options=[{
                    'label': i,
                    'value': i
                } for i in language_options],
                value='All origins'
             ),
            dcc.Dropdown(
                id="Song_Decade",
                options=[{
                    'label': i,
                    'value': i
                } for i in decade_options],
                value='All Decades'
            ),
        ],
        style={'width': '25%',
               'display': 'inline-block'}),
     dcc.Graph(id='funnel-graph'),
    ])

As mentioned above, we need to have an app.callback for the dependencies. What happens here is that when value for “music_key” is selected, then the ouput (a new funnel graph) is returned. Simple and elegant.

@app.callback(
    dash.dependencies.Output('funnel-graph', 'figure'),
    [dash.dependencies.Input('music_key','value'),
    dash.dependencies.Input('Artist_origin','value'),
    dash.dependencies.Input('Song_Decade','value')])

Now, when selections are done (or none, for that matter) the right data need to be taken from the original dataframe. This means that we need to look at all the possible combinations of choices to update the graph The first if-statement is what is returned if no selection is made in the dropdown, that is a copy of the Spotify dataset. The second if-statement is if no selections are made on “music_key” or “Song_Decades”, but one is made on “Artist_origin”, then what is returned is the Spotify dataframe where “Artist_origin = “what you chose in the dropdown list”. I think you get the picture. The last if-statement is a little bit special because all dropdowns have been selected, hence you need to state that none of the values are ALL choice of the dropdown. If you think about it, it makes a lot of sense.

def update_graph(music_key,Artist_origin,Song_Decade):
    if ((music_key == "All music keys") & (Artist_origin == "All origins") & (Song_Decade == "All Decades")):
        Spotify_plot = Spotify.copy()
    
    elif ((music_key == "All music keys") & (Song_Decade == "All Decades")):
        Spotify_plot = Spotify[(Spotify['Artist_origin'].astype(str)==Artist_origin)]
        
    elif ((music_key == "All music keys") & (Artist_origin == "All origins")):
        Spotify_plot = Spotify[(Spotify['Song_Decade'].astype(str)==Song_Decade)]
    
    elif ((Song_Decade == "All Decades") & (Artist_origin == "All origins")):
        Spotify_plot = Spotify[(Spotify['music_key']==music_key)]
                             
    elif ((music_key == "All music keys")):
        Spotify_plot = Spotify[(Spotify['Song_Decade'].astype(str)==Song_Decade) & (Spotify['Artist_origin'].astype(str)==Artist_origin)]
    
    elif ((Song_Decade == "All Decades")):
        Spotify_plot = Spotify[(Spotify['music_key']==music_key) & (Spotify['Artist_origin'].astype(str)==Artist_origin)]
                                            
    elif ((Artist_origin == "All origins")):
        Spotify_plot = Spotify[(Spotify['music_key']==music_key) & (Spotify['Song_Decade'].astype(str)==Song_Decade)]
    
    elif ((music_key != "All music keys") & (Song_Decade != "All Decades") & (Artist_origin != "All origins")):
        Spotify_plot = Spotify[(Spotify['music_key']==music_key) & (Spotify['Song_Decade'].astype(str)==Song_Decade) & (Spotify['Artist_origin'].astype(str)==Artist_origin)]
        
    trace1 = go.Bar(x=Spotify_plot['artists'], y=Spotify_plot['Language Specific Popularity'], name='Language Specific Popularity',marker_color='pink')
    trace2 = go.Bar(x=Spotify_plot['artists'], y=Spotify_plot['popularity'], name='Global Popularity',marker_color='black')

Last, but not least, all the information that has been chosen and the resulting dataframe need to be plotted.

trace1 = go.Bar(x=Spotify_plot['artists'], y=Spotify_plot['Language Specific Popularity'], name='Language Specific Popularity',marker_color='pink')
    trace2 = go.Bar(x=Spotify_plot['artists'], y=Spotify_plot['popularity'], name='Global Popularity',marker_color='black')

    return {
        'data': [trace1, trace2],
        'layout':
        go.Layout(
            title='Artist popularity from/in {} during {}. Song in {} key'.format(Artist_origin,Song_Decade,music_key),
            barmode ='group',plot_bgcolor='rgb(30, 215, 96)',paper_bgcolor='rgb(30, 215, 96)')
    }
if __name__ == '__main__':
    app.run_server()

In this case, the app was run from Jupyter notebook. If you run something similar from your python-script interpreter, you need to replace the last two lines by:

if __name__ == '__main__':
    app.run_server(debug=True)

Running it from jupyter notebook will result in

Dash is running on http://127.0.0.1:8050/

In the snapshot below, I have already filtered the data. I know, the design is not the best in the world but to my defence, I am not a UX-designer. So, what we can see is that during the first decade of the new millenium, Jay Chou (周杰伦) was by far the most popular chinese artist, followed by Michael Wong (王光良). And I can only agree! Not only did they produce brilliant piece, Jay Chou also acted in movies and directed a really cute movie (不能说的秘密). Really nice.

But, seeing this snapshot is not very helpful for anyone. What I wanted to offer was the ability for anyone, on any platfrom to access the dashboard. I looked in several alternatives for hosting an interactive plot, but the only one I really liked was hosting the app on heroku. How to do this will close this blog post.

Hosting apps on heroku

What is heroku? Well, it’s a cloud platform that lets companies and individuals build, deliver, monitor and scale apps. Also, it supports a large variety of languages, so the chances that your work is compatible with the platform specs is high. For smaller projects, like this one, heroku is completely free. This allows you to test your apps, work on them and decide whether to scale up, in which case you’ll need to pay for the services. As I see it, this is ideal for learning app-management. So, what do you need in order to get started?

  1. Git is a must. I use GitHub for my private project and it works just fine.
  2. A heroku account, which you can easily get from this sign-up page
  3. The heroku command line (Heroku CLI), which can be downloaded here. Make sure to read the instructions.
  4. You’ll also need to pip install virtualenv and pip install gunicorn

Now, if you haven’t saved your app as a python (.py) file (e.g. you wrote it in jupyter notebook and saved it as .ipynb), you’ll need to convert it. A word of advice, make some chages to your app.py file. For one, organize what you send to git in such a way that reading the data in the app makes sense. If you were running it in a local repo, the path to the data is that of the local environment. Change it to whatever structure you pushed to git. Compare the code to the app I shared above with the one in my app.py file:

import dash
import pandas as pd
import dash_core_components as dcc
import dash_html_components as html
import flask
import plotly.graph_objs as go
from jupyter_dash import JupyterDash
import dash
from dash.dependencies import Input, Output

from jupyter_dash import JupyterDash
import dash
import numpy as np


server = flask.Flask(__name__)
app = dash.Dash(__name__)
server = app.server


Spotify = pd.read_csv("Spotify_to_app.csv", sep=";", encoding='utf_8_sig')

key_options          = Spotify["music_key"].astype(str).unique()
language_options     = Spotify["Artist_origin"].astype(str).unique()
decade_options       = Spotify["Song_Decade"].astype(str).unique()

#app = dash.Dash(__name__)
#app.layout = html.Div(html.H1('Heading', style={'backgroundColor':'blue'})
app.layout = html.Div([
    html.H1("Popularity of songs"),
    html.Div(
        [
            dcc.Dropdown(
                id="music_key",
                options=[{
                    'label': i,
                    'value': i
                } for i in key_options],
                value='All music keys'
            ),
            dcc.Dropdown(
                id="Artist_origin",
                options=[{
                    'label': i,
                    'value': i
                } for i in language_options],
                value='All origins'
             ),
            dcc.Dropdown(
                id="Song_Decade",
                options=[{
                    'label': i,
                    'value': i
                } for i in decade_options],
                value='All Decades'
            ),
        ],
        style={'width': '25%',
               'display': 'inline-block'}),
     dcc.Graph(id='funnel-graph'),
    ])

@app.callback(
    dash.dependencies.Output('funnel-graph', 'figure'),
    [dash.dependencies.Input('music_key','value'),
    dash.dependencies.Input('Artist_origin','value'),
    dash.dependencies.Input('Song_Decade','value')])
    
def update_graph(music_key,Artist_origin,Song_Decade):
    if ((music_key == "All music keys") & (Artist_origin == "All origins") & (Song_Decade == "All Decades")):
        Spotify_plot = Spotify.copy()
    
    elif ((music_key == "All music keys") & (Song_Decade == "All Decades")):
        Spotify_plot = Spotify[(Spotify['Artist_origin'].astype(str)==Artist_origin)]
        
    elif ((music_key == "All music keys") & (Artist_origin == "All origins")):
        Spotify_plot = Spotify[(Spotify['Song_Decade'].astype(str)==Song_Decade)]
    
    elif ((Song_Decade == "All Decades") & (Artist_origin == "All origins")):
        Spotify_plot = Spotify[(Spotify['music_key']==music_key)]
                             
    elif ((music_key == "All music keys")):
        Spotify_plot = Spotify[(Spotify['Song_Decade'].astype(str)==Song_Decade) & (Spotify['Artist_origin'].astype(str)==Artist_origin)]
    
    elif ((Song_Decade == "All Decades")):
        Spotify_plot = Spotify[(Spotify['music_key']==music_key) & (Spotify['Artist_origin'].astype(str)==Artist_origin)]
                                            
    elif ((Artist_origin == "All origins")):
        Spotify_plot = Spotify[(Spotify['music_key']==music_key) & (Spotify['Song_Decade'].astype(str)==Song_Decade)]
    
    elif ((music_key != "All music keys") & (Song_Decade != "All Decades") & (Artist_origin != "All origins")):
        Spotify_plot = Spotify[(Spotify['music_key']==music_key) & (Spotify['Song_Decade'].astype(str)==Song_Decade) & (Spotify['Artist_origin'].astype(str)==Artist_origin)]
        
    trace1 = go.Bar(x=Spotify_plot['artists'], y=Spotify_plot['Language Specific Popularity'], name='Language Specific Popularity',marker_color='pink')
    trace2 = go.Bar(x=Spotify_plot['artists'], y=Spotify_plot['popularity'], name='Global Popularity',marker_color='black')

    return {
        'data': [trace1, trace2],
        'layout':
        go.Layout(
            title='Artist popularity from {} during {}. Song in {} key'.format(Artist_origin,Song_Decade,music_key),
            barmode ='group',plot_bgcolor='rgb(30, 215, 96)',paper_bgcolor='rgb(30, 215, 96)')
    }


if __name__ == '__main__':
    app.run_server(debug = True)

Note that I added “server = flask.Flask(name)” and changed “app.run_server()” to “app.run_server(debug = True)”. So, just converting wont be enough.

What else do we need? To get everthing right without mixing it with already existing files that most probably will have names similar to what you will call your app, create a new local repos and drop your data and app.py file there. Now, you’ll need to create a number of things:

Initialize git and create a virtual env

First, you’ll need to navigate to the repo/folder where you’ve saved you data and app.py file. Once, there you’ll need to initiate git.

git init                  
virtualenv venv           
source venv/bin/activate  #if Windows, replace line with: source venv/Scripts/activate

NB: Any library used in your app need to be installed in the environment.

Create necessary files for the app

There are three files that need to be created for the app to run.

Procfile

This file is an extensionless file containing only one line.

web: gunicorn app:server

.gitignore

As usual, there are files that one not necessarily wants uploaded to a public domain or that have no need to be uploaded for the app to run

venv
*.pyc
.DS_Store
.env

requirements.txt

This file is to ensure that all the libraries that are needed to produce the output of your app are actually installed on heroku. If you do not know exactly which these are (and chances are you do not know all the version numbers of your libraries) you can simply create the requirements.txt file using:

pip freeze > requirements.txt

From experience, and if you, like me, work in Windows go through your requirements.txt file and remove the following requirement “pywin32=323“. If it is there, it’ll trigger an error for which I have no clue about.

Your repo should look something like this

Deploying the app

Since you’ve installed the heroku CLI, you’re good to go. Type

heroku create yourapp 

in my case yourapp = app2blog2s. Note, name muststart with letter and no uppercases are accepted. Another thing, upon you first usage of the CLI you will be prompted to give your username and passwerd to heroku. After that, it’s all a child’s play.

git add .
git commit -m 'my first commit'
git push heroku master

If everthing went well, this is what you should see in your terminal. I highlighted the addresse to the app:

But……sometimes, things do go sideways and you might get this:

Luckily, the logs are quite self-explanatory and you get really good info on what went wrong. After a few tries, you have a new dashingly beautiful app with which to plays. One last thing. Every time you make changes to the app, you’ll have to update the content on heroku with git add . , commit and push.

The result?

https://app2blog2s.herokuapp.com/

(For security reasons, WordPress does not allow iframe in Premium Plans, hence the link). ALSO, it takes 10 seconds to load the first time.

You can hoover over the bars and get measures, you can mask either set of records, zoom in/out….With a little more work, you can easily make more advanced dsshboards.

I hope you enjoyed this little journey! Please feel free to comment, share with friends and use freely. If you want to data, you can find it on GitHub

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Website Powered by WordPress.com.

Up ↑

%d bloggers like this: