cmc/cleberg.net

My personal web garden & blog.

clone: git clone https://gitbay.org/cmc/cleberg.net.git

main: content/blog/2020-09-25-happiness-map.org · raw

  1#+date: [2020-09-25 Fri 00:00:00]
  2#+title:       Data Analysis: World Happiness Choropleth Map
  3#+description: A quick dive into choropleth maps.
  4#+slug:        happiness-map
  5#+filetags:    :personal:
  6
  7* Background Information
  8
  9The dataset (obtained from [[https://www.kaggle.com/unsdsn/world-happiness][Kaggle]]) used in this article contains a list of
 10countries around the world, their happiness rankings and scores, as well as
 11other national scoring measures.
 12
 13Fields include:
 14
 15- Overall rank
 16- Country or region
 17- GDP per capita
 18- Social support
 19- Healthy life expectancy
 20- Freedom to make life choices
 21- Generosity
 22- Perceptions of corruption
 23
 24There are 156 records. Since there are ~195 countries in the world, we can see
 25that around 40 countries will be missing from this dataset.
 26
 27* Install Packages
 28
 29As always, run the =install= command for all packages needed to perform
 30analysis.
 31
 32#+begin_src python
 33!pip install folium geopandas matplotlib numpy pandas
 34#+end_src
 35
 36* Import the Data
 37
 38We only need a couple packages to create a choropleth map. We will use [[https://python-visualization.github.io/folium/][Folium]],
 39which provides map visualizations in Python. We will also use geopandas and
 40pandas to wrangle our data before we put it on a map.
 41
 42#+begin_src python
 43# Import the necessary Python packages
 44import folium
 45import geopandas as gpd
 46import pandas as pd
 47#+end_src
 48
 49To get anything to show up on a map, we need a file that will specify the
 50boundaries of each country. Luckily, GeoJSON files exist (for free!) on the
 51internet. To get the boundaries of every country in the world, we will use the
 52GeoJSON link shown below.
 53
 54GeoPandas will take this data and load it into a dataframe so that we can easily
 55match it to the data we're trying to analyze. Let's look at the GeoJSON
 56dataframe:
 57
 58#+begin_src python
 59# Load the GeoJSON data with geopandas
 60geo_data = gpd.read_file('https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson')
 61geo_data.head()
 62#+end_src
 63
 64Next, let's load the data from the Kaggle dataset. I've downloaded this file, so
 65update the file path if you have it somewhere else. After loading, let's take a
 66look at this dataframe:
 67
 68#+begin_src python
 69# Load the world happiness data with pandas
 70happy_data = pd.read_csv(r'~/Downloads/world_happiness_data_2019.csv')
 71happy_data.head()
 72#+end_src
 73
 74* Clean the Data
 75
 76Some countries need to be renamed, or they will be lost when you merge the
 77happiness and GeoJSON dataframes. This is something I discovered when the map
 78below showed empty countries. I searched both data frames for the missing
 79countries to see the naming differences. Any countries that do not have records
 80in the =happy_data= data frame will not show up on the map.
 81
 82#+begin_src python
 83# Rename some countries to match our GeoJSON data
 84
 85# Rename USA
 86usa_index = happy_data.index[happy_data['Country or region'] == 'United States']
 87happy_data.at[usa_index, 'Country or region'] = 'United States of America'
 88
 89# Rename Tanzania
 90tanzania_index = happy_data.index[happy_data['Country or region'] == 'Tanzania']
 91happy_data.at[tanzania_index, 'Country or region'] = 'United Republic of Tanzania'
 92
 93# Rename the Congo
 94republic_congo_index = happy_data.index[happy_data['Country or region'] == 'Congo (Brazzaville)']
 95happy_data.at[republic_congo_index, 'Country or region'] = 'Republic of Congo'
 96
 97# Rename the DRC
 98democratic_congo_index = happy_data.index[happy_data['Country or region'] == 'Congo (Kinshasa)']
 99happy_data.at[democratic_congo_index, 'Country or region'] = 'Democratic Republic of the Congo'
100#+end_src
101
102* Merge the Data
103
104Now that we have clean data, we need to merge the GeoJSON data with the
105happiness data. Since we've stored them both in dataframes, we just need to call
106the =.merge()= function.
107
108We will also rename a couple columns, just so that they're a little easier to
109use when we create the map.
110
111#+begin_src python
112# Merge the two previous dataframes into a single geopandas dataframe
113merged_df = geo_data.merge(happy_data,left_on='ADMIN', right_on='Country or region')
114
115# Rename columns for ease of use
116merged_df = merged_df.rename(columns = {'ADMIN':'GeoJSON_Country'})
117merged_df = merged_df.rename(columns = {'Country or region':'Country'})
118#+end_src
119
120* Create the Map
121
122The data is finally ready to be added to a map. The code below shows the
123simplest way to find the center of the map and create a Folium map object. The
124important part is to remember to reference the merged dataframe for our GeoJSON
125data and value data. The columns specify which geo data and value data to use.
126
127#+begin_src python
128# Assign centroids to map
129x_map = merged_df.centroid.x.mean()
130y_map = merged_df.centroid.y.mean()
131print(x_map,y_map)
132
133# Creating a map object
134world_map = folium.Map(location=[y_map, x_map], zoom_start=2,tiles=None)
135folium.TileLayer('CartoDB positron',name='Dark Map',control=False).add_to(world_map)
136
137# Creating choropleth map
138folium.Choropleth(
139    geo_data=merged_df,
140    name='Choropleth',
141    data=merged_df,
142    columns=['Country','Overall rank'],
143    key_on='feature.properties.Country',
144    fill_color='YlOrRd',
145    fill_opacity=0.6,
146    line_opacity=0.8,
147    legend_name='Overall happiness rank',
148    smooth_factor=0,
149    highlight=True
150).add_to(world_map)
151#+end_src
152
153Let's look at the resulting map.
154
155* Create a Tooltip on Hover
156
157Now that we have a map set up, we could stop. However, I want to add a tooltip
158so that I can see more information about each country. The =tooltip_data= code
159below will show a popup on hover with all the data fields shown.
160
161#+begin_src python
162    # Adding labels to map
163    style_function = lambda x: {'fillColor': '#ffffff',
164                                'color':'#000000',
165                                'fillOpacity': 0.1,
166                            'weight': 0.1}
167
168tooltip_data = folium.features.GeoJson(
169    merged_df,
170    style_function=style_function,
171    control=False,
172    tooltip=folium.features.GeoJsonTooltip(
173        fields=['Country'
174                ,'Overall rank'
175                ,'Score'
176                ,'GDP per capita'
177                ,'Social support'
178                ,'Healthy life expectancy'
179                ,'Freedom to make life choices'
180                ,'Generosity'
181                ,'Perceptions of corruption'
182               ],
183        aliases=['Country: '
184                ,'Happiness rank: '
185                ,'Happiness score: '
186                ,'GDP per capita: '
187                ,'Social support: '
188                ,'Healthy life expectancy: '
189                ,'Freedom to make life choices: '
190                ,'Generosity: '
191                ,'Perceptions of corruption: '
192                 ],
193        style=('background-color: white; color: #333333; font-family: arial; font-size: 12px; padding: 10px;')
194    )
195)
196world_map.add_child(tooltip_data)
197world_map.keep_in_front(tooltip_data)
198folium.LayerControl().add_to(world_map)
199
200# Display the map
201world_map
202#+end_src
203
204The tooltip will now appear whenever you hover over a country.