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.