In [1]:
import json

from branca.colormap import linear
import folium
from folium import Map, Marker, GeoJson, LayerControl
import pandas as pd
import geopandas as gpd

%matplotlib inline

Load and explore geojson


In [2]:
gj_path = "geojson/anc.geojson"

In [3]:
anc_df = gpd.read_file(gj_path)

In [4]:
anc_df.head()


Out[4]:
OBJECTID ANC_ID WEB_URL NAME Shape_Leng Shape_Area geometry
0 1 1C http://anc.dc.gov/page/advisory-neighborhood-c... ANC 1C 5218.954361 1.285112e+06 POLYGON ((-77.0464219248981 38.92597950466725,...
1 2 1D http://anc.dc.gov/page/advisory-neighborhood-c... ANC 1D 4224.010068 9.475922e+05 POLYGON ((-77.03645123520528 38.93638367371454...
2 3 2A http://anc.dc.gov/page/advisory-neighborhood-c... ANC 2A 12477.943204 7.065358e+06 POLYGON ((-77.05445304334567 38.90725341205063...
3 4 2B http://anc.dc.gov/page/advisory-neighborhood-c... ANC 2B 7712.504785 2.160620e+06 POLYGON ((-77.0412259402753 38.91701561959232,...
4 5 2C http://anc.dc.gov/page/advisory-neighborhood-c... ANC 2C 7811.084627 2.861750e+06 POLYGON ((-77.02404971019487 38.90293630338282...

In [5]:
# Tried this to reduce the map complexity for Chrome, didn't work
# anc_df.geometry = anc_df.geometry.simplify(500, preserve_topology=False)

In [6]:
def embed_map(m):
    from IPython.display import IFrame

    m.save('inline_map.html')
    return IFrame('inline_map.html', width='100%', height='750px')

Preprocess data and join with election CSV using pandas


In [7]:
election_df = pd.read_csv("../../cleaned_data/election_data_for_anc_map.csv")
election_df.head()


Out[7]:
ward year anc num_candidates votes vote_norm engagement prop_uncontested prop_empty
0 1.0 2018.0 A 1.166667 520.500000 0.896894 0.789882 0.833333 0.0
1 1.0 2018.0 B 1.166667 563.083333 0.880951 0.818210 0.833333 0.0
2 1.0 2018.0 C 1.375000 690.500000 0.849848 0.818804 0.625000 0.0
3 1.0 2018.0 D 1.600000 513.200000 0.746412 0.818987 0.400000 0.0
4 2.0 2018.0 A 1.125000 254.375000 0.909452 0.770138 0.875000 0.0

In [8]:
election_df = election_df[election_df["ward"].notna()]
election_df["ward"] = election_df["ward"].map(round)
election_df["ANC_ID"] = election_df["ward"].map(str) + election_df["anc"]
election_df.drop(columns = ["ward", "anc"], inplace=True)
print(election_df.shape)
election_df.head()


(40, 8)
Out[8]:
year num_candidates votes vote_norm engagement prop_uncontested prop_empty ANC_ID
0 2018.0 1.166667 520.500000 0.896894 0.789882 0.833333 0.0 1A
1 2018.0 1.166667 563.083333 0.880951 0.818210 0.833333 0.0 1B
2 2018.0 1.375000 690.500000 0.849848 0.818804 0.625000 0.0 1C
3 2018.0 1.600000 513.200000 0.746412 0.818987 0.400000 0.0 1D
4 2018.0 1.125000 254.375000 0.909452 0.770138 0.875000 0.0 2A

In [9]:
election_df_2018 = election_df[election_df.year == 2018]
print(election_df_2018.shape)


(40, 8)

In [10]:
turnout = pd.read_csv("../../cleaned_data/anc_turnout.csv")
turnout = turnout[turnout.year == 2018]
turnout = turnout.rename(columns={"anc.full": "ANC_ID"})[["ANC_ID", "turnout"]]
print(turnout.shape)
turnout.head()


(41, 2)
Out[10]:
ANC_ID turnout
3 1A 0.453458
7 1B 0.454878
11 1C 0.548409
15 1D 0.502601
19 2A 0.399852

In [11]:
joined_df = anc_df.merge(election_df_2018, how="outer", on="ANC_ID")
joined_df = joined_df.merge(turnout, how="outer", on="ANC_ID")
print(joined_df.shape)
joined_df.head()


(41, 15)
Out[11]:
OBJECTID ANC_ID WEB_URL NAME Shape_Leng Shape_Area geometry year num_candidates votes vote_norm engagement prop_uncontested prop_empty turnout
0 1.0 1C http://anc.dc.gov/page/advisory-neighborhood-c... ANC 1C 5218.954361 1.285112e+06 POLYGON ((-77.0464219248981 38.92597950466725,... 2018.0 1.375000 690.500000 0.849848 0.818804 0.625000 0.0 0.548409
1 2.0 1D http://anc.dc.gov/page/advisory-neighborhood-c... ANC 1D 4224.010068 9.475922e+05 POLYGON ((-77.03645123520528 38.93638367371454... 2018.0 1.600000 513.200000 0.746412 0.818987 0.400000 0.0 0.502601
2 3.0 2A http://anc.dc.gov/page/advisory-neighborhood-c... ANC 2A 12477.943204 7.065358e+06 POLYGON ((-77.05445304334567 38.90725341205063... 2018.0 1.125000 254.375000 0.909452 0.770138 0.875000 0.0 0.399852
3 4.0 2B http://anc.dc.gov/page/advisory-neighborhood-c... ANC 2B 7712.504785 2.160620e+06 POLYGON ((-77.0412259402753 38.91701561959232,... 2018.0 1.222222 574.666667 0.829025 0.840883 0.777778 0.0 0.496096
4 5.0 2C http://anc.dc.gov/page/advisory-neighborhood-c... ANC 2C 7811.084627 2.861750e+06 POLYGON ((-77.02404971019487 38.90293630338282... 2018.0 1.333333 417.000000 0.847020 0.806862 0.666667 0.0 0.459561

Update geojson features from dataframe values


In [12]:
""" No longer necessary
for anc in gjdata['features']:
    anc_id = anc['properties']['ANC_ID']
    features = election_df.columns.tolist()
    features.remove("ANC_ID")
    for feature in features:
        anc['properties'][feature] = joined_df.loc[joined_df['ANC_ID'] == anc_id, feature].item()
"""


Out[12]:
' No longer necessary\nfor anc in gjdata[\'features\']:\n    anc_id = anc[\'properties\'][\'ANC_ID\']\n    features = election_df.columns.tolist()\n    features.remove("ANC_ID")\n    for feature in features:\n        anc[\'properties\'][feature] = joined_df.loc[joined_df[\'ANC_ID\'] == anc_id, feature].item()\n'

Construct map


In [13]:
anc_map = Map(location = (38.8899, -77.0091),
              zoom_start = 12,
              tiles = 'Stamen Toner')

In [14]:
folium.Choropleth(
    geo_data=gj_path,
    data=joined_df,
    columns=["ANC_ID", "votes"],
    key_on='feature.properties.ANC_ID',
    fill_color='GnBu',
    fill_opacity=0.5,
    line_weight=1,  
    highlight=True,
    overlay=True,
    name="average votes",
    legend_name="average # votes for winning candidates",
).add_to(anc_map)


Out[14]:
<folium.features.Choropleth at 0x11cc0cef0>

In [15]:
folium.Choropleth(
    geo_data=gj_path,
    data=joined_df,
    columns=["ANC_ID", "engagement"],
    key_on='feature.properties.ANC_ID',
    fill_color='PuRd',
    fill_opacity=0.5,
    line_weight=1,  
    highlight=True,
    overlay=True,
    name="engagement",
    legend_name="percentage of ballots where ANC candidate was marked (complement of roll-off)",
).add_to(anc_map)


Out[15]:
<folium.features.Choropleth at 0x11d4ce1d0>

In [16]:
folium.Choropleth(
    geo_data=gj_path,
    data=joined_df,
    columns=["ANC_ID", "turnout"],
    key_on='feature.properties.ANC_ID',
    fill_color='YlOrBr',
    fill_opacity=0.5,
    line_weight=1,  
    highlight=True,
    overlay=True,
    name="turnout",
    legend_name="estimated voter turnout",
).add_to(anc_map)


Out[16]:
<folium.features.Choropleth at 0x11d94c908>

In [17]:
LayerControl().add_to(anc_map)


Out[17]:
<folium.map.LayerControl at 0x11cc0ccf8>

In [18]:
embed_map(anc_map)


Out[18]:

In [19]:
anc_map.save("anc_map.html")

In [ ]: