{ "cells": [ { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import json\n", "\n", "from branca.colormap import linear\n", "import folium\n", "from folium import Map, Marker, GeoJson, LayerControl\n", "import pandas as pd\n", "import geopandas as gpd\n", "\n", "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Load and explore geojson" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "gj_path = \"geojson/anc.geojson\"" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "anc_df = gpd.read_file(gj_path)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/html": [ "<div>\n", "<style scoped>\n", " .dataframe tbody tr th:only-of-type {\n", " vertical-align: middle;\n", " }\n", "\n", " .dataframe tbody tr th {\n", " vertical-align: top;\n", " }\n", "\n", " .dataframe thead th {\n", " text-align: right;\n", " }\n", "</style>\n", "<table border=\"1\" class=\"dataframe\">\n", " <thead>\n", " <tr style=\"text-align: right;\">\n", " <th></th>\n", " <th>OBJECTID</th>\n", " <th>ANC_ID</th>\n", " <th>WEB_URL</th>\n", " <th>NAME</th>\n", " <th>Shape_Leng</th>\n", " <th>Shape_Area</th>\n", " <th>geometry</th>\n", " </tr>\n", " </thead>\n", " <tbody>\n", " <tr>\n", " <th>0</th>\n", " <td>1</td>\n", " <td>1C</td>\n", " <td>http://anc.dc.gov/page/advisory-neighborhood-c...</td>\n", " <td>ANC 1C</td>\n", " <td>5218.954361</td>\n", " <td>1.285112e+06</td>\n", " <td>POLYGON ((-77.0464219248981 38.92597950466725,...</td>\n", " </tr>\n", " <tr>\n", " <th>1</th>\n", " <td>2</td>\n", " <td>1D</td>\n", " <td>http://anc.dc.gov/page/advisory-neighborhood-c...</td>\n", " <td>ANC 1D</td>\n", " <td>4224.010068</td>\n", " <td>9.475922e+05</td>\n", " <td>POLYGON ((-77.03645123520528 38.93638367371454...</td>\n", " </tr>\n", " <tr>\n", " <th>2</th>\n", " <td>3</td>\n", " <td>2A</td>\n", " <td>http://anc.dc.gov/page/advisory-neighborhood-c...</td>\n", " <td>ANC 2A</td>\n", " <td>12477.943204</td>\n", " <td>7.065358e+06</td>\n", " <td>POLYGON ((-77.05445304334567 38.90725341205063...</td>\n", " </tr>\n", " <tr>\n", " <th>3</th>\n", " <td>4</td>\n", " <td>2B</td>\n", " <td>http://anc.dc.gov/page/advisory-neighborhood-c...</td>\n", " <td>ANC 2B</td>\n", " <td>7712.504785</td>\n", " <td>2.160620e+06</td>\n", " <td>POLYGON ((-77.0412259402753 38.91701561959232,...</td>\n", " </tr>\n", " <tr>\n", " <th>4</th>\n", " <td>5</td>\n", " <td>2C</td>\n", " <td>http://anc.dc.gov/page/advisory-neighborhood-c...</td>\n", " <td>ANC 2C</td>\n", " <td>7811.084627</td>\n", " <td>2.861750e+06</td>\n", " <td>POLYGON ((-77.02404971019487 38.90293630338282...</td>\n", " </tr>\n", " </tbody>\n", "</table>\n", "</div>" ], "text/plain": [ " OBJECTID ANC_ID WEB_URL NAME \\\n", "0 1 1C http://anc.dc.gov/page/advisory-neighborhood-c... ANC 1C \n", "1 2 1D http://anc.dc.gov/page/advisory-neighborhood-c... ANC 1D \n", "2 3 2A http://anc.dc.gov/page/advisory-neighborhood-c... ANC 2A \n", "3 4 2B http://anc.dc.gov/page/advisory-neighborhood-c... ANC 2B \n", "4 5 2C http://anc.dc.gov/page/advisory-neighborhood-c... ANC 2C \n", "\n", " Shape_Leng Shape_Area \\\n", "0 5218.954361 1.285112e+06 \n", "1 4224.010068 9.475922e+05 \n", "2 12477.943204 7.065358e+06 \n", "3 7712.504785 2.160620e+06 \n", "4 7811.084627 2.861750e+06 \n", "\n", " geometry \n", "0 POLYGON ((-77.0464219248981 38.92597950466725,... \n", "1 POLYGON ((-77.03645123520528 38.93638367371454... \n", "2 POLYGON ((-77.05445304334567 38.90725341205063... \n", "3 POLYGON ((-77.0412259402753 38.91701561959232,... \n", "4 POLYGON ((-77.02404971019487 38.90293630338282... " ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "anc_df.head()" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "# Tried this to reduce the map complexity for Chrome, didn't work\n", "# anc_df.geometry = anc_df.geometry.simplify(500, preserve_topology=False)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "def embed_map(m):\n", " from IPython.display import IFrame\n", "\n", " m.save('inline_map.html')\n", " return IFrame('inline_map.html', width='100%', height='750px')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Preprocess data and join with election CSV using pandas" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "<div>\n", "<style scoped>\n", " .dataframe tbody tr th:only-of-type {\n", " vertical-align: middle;\n", " }\n", "\n", " .dataframe tbody tr th {\n", " vertical-align: top;\n", " }\n", "\n", " .dataframe thead th {\n", " text-align: right;\n", " }\n", "</style>\n", "<table border=\"1\" class=\"dataframe\">\n", " <thead>\n", " <tr style=\"text-align: right;\">\n", " <th></th>\n", " <th>ward</th>\n", " <th>year</th>\n", " <th>anc</th>\n", " <th>num_candidates</th>\n", " <th>votes</th>\n", " <th>vote_norm</th>\n", " <th>engagement</th>\n", " <th>prop_uncontested</th>\n", " <th>prop_empty</th>\n", " </tr>\n", " </thead>\n", " <tbody>\n", " <tr>\n", " <th>0</th>\n", " <td>1</td>\n", " <td>2012</td>\n", " <td>A</td>\n", " <td>1.583333</td>\n", " <td>481.500000</td>\n", " <td>NaN</td>\n", " <td>NaN</td>\n", " <td>0.500000</td>\n", " <td>0.0</td>\n", " </tr>\n", " <tr>\n", " <th>1</th>\n", " <td>1</td>\n", " <td>2012</td>\n", " <td>B</td>\n", " <td>1.250000</td>\n", " <td>544.333333</td>\n", " <td>NaN</td>\n", " <td>NaN</td>\n", " <td>0.916667</td>\n", " <td>0.0</td>\n", " </tr>\n", " <tr>\n", " <th>2</th>\n", " <td>1</td>\n", " <td>2012</td>\n", " <td>C</td>\n", " <td>1.500000</td>\n", " <td>641.875000</td>\n", " <td>NaN</td>\n", " <td>NaN</td>\n", " <td>0.625000</td>\n", " <td>0.0</td>\n", " </tr>\n", " <tr>\n", " <th>3</th>\n", " <td>1</td>\n", " <td>2012</td>\n", " <td>D</td>\n", " <td>1.400000</td>\n", " <td>559.800000</td>\n", " <td>NaN</td>\n", " <td>NaN</td>\n", " <td>0.600000</td>\n", " <td>0.0</td>\n", " </tr>\n", " <tr>\n", " <th>4</th>\n", " <td>1</td>\n", " <td>2014</td>\n", " <td>A</td>\n", " <td>1.333333</td>\n", " <td>296.250000</td>\n", " <td>0.529246</td>\n", " <td>0.63618</td>\n", " <td>0.666667</td>\n", " <td>0.0</td>\n", " </tr>\n", " </tbody>\n", "</table>\n", "</div>" ], "text/plain": [ " ward year anc num_candidates votes vote_norm engagement \\\n", "0 1 2012 A 1.583333 481.500000 NaN NaN \n", "1 1 2012 B 1.250000 544.333333 NaN NaN \n", "2 1 2012 C 1.500000 641.875000 NaN NaN \n", "3 1 2012 D 1.400000 559.800000 NaN NaN \n", "4 1 2014 A 1.333333 296.250000 0.529246 0.63618 \n", "\n", " prop_uncontested prop_empty \n", "0 0.500000 0.0 \n", "1 0.916667 0.0 \n", "2 0.625000 0.0 \n", "3 0.600000 0.0 \n", "4 0.666667 0.0 " ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "election_df = pd.read_csv(\"election_data_for_anc_map.csv\")\n", "election_df.head()" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(160, 8)\n" ] }, { "data": { "text/html": [ "<div>\n", "<style scoped>\n", " .dataframe tbody tr th:only-of-type {\n", " vertical-align: middle;\n", " }\n", "\n", " .dataframe tbody tr th {\n", " vertical-align: top;\n", " }\n", "\n", " .dataframe thead th {\n", " text-align: right;\n", " }\n", "</style>\n", "<table border=\"1\" class=\"dataframe\">\n", " <thead>\n", " <tr style=\"text-align: right;\">\n", " <th></th>\n", " <th>year</th>\n", " <th>num_candidates</th>\n", " <th>votes</th>\n", " <th>vote_norm</th>\n", " <th>engagement</th>\n", " <th>prop_uncontested</th>\n", " <th>prop_empty</th>\n", " <th>ANC_ID</th>\n", " </tr>\n", " </thead>\n", " <tbody>\n", " <tr>\n", " <th>0</th>\n", " <td>2012</td>\n", " <td>1.583333</td>\n", " <td>481.500000</td>\n", " <td>NaN</td>\n", " <td>NaN</td>\n", " <td>0.500000</td>\n", " <td>0.0</td>\n", " <td>1A</td>\n", " </tr>\n", " <tr>\n", " <th>1</th>\n", " <td>2012</td>\n", " <td>1.250000</td>\n", " <td>544.333333</td>\n", " <td>NaN</td>\n", " <td>NaN</td>\n", " <td>0.916667</td>\n", " <td>0.0</td>\n", " <td>1B</td>\n", " </tr>\n", " <tr>\n", " <th>2</th>\n", " <td>2012</td>\n", " <td>1.500000</td>\n", " <td>641.875000</td>\n", " <td>NaN</td>\n", " <td>NaN</td>\n", " <td>0.625000</td>\n", " <td>0.0</td>\n", " <td>1C</td>\n", " </tr>\n", " <tr>\n", " <th>3</th>\n", " <td>2012</td>\n", " <td>1.400000</td>\n", " <td>559.800000</td>\n", " <td>NaN</td>\n", " <td>NaN</td>\n", " <td>0.600000</td>\n", " <td>0.0</td>\n", " <td>1D</td>\n", " </tr>\n", " <tr>\n", " <th>4</th>\n", " <td>2014</td>\n", " <td>1.333333</td>\n", " <td>296.250000</td>\n", " <td>0.529246</td>\n", " <td>0.63618</td>\n", " <td>0.666667</td>\n", " <td>0.0</td>\n", " <td>1A</td>\n", " </tr>\n", " </tbody>\n", "</table>\n", "</div>" ], "text/plain": [ " year num_candidates votes vote_norm engagement prop_uncontested \\\n", "0 2012 1.583333 481.500000 NaN NaN 0.500000 \n", "1 2012 1.250000 544.333333 NaN NaN 0.916667 \n", "2 2012 1.500000 641.875000 NaN NaN 0.625000 \n", "3 2012 1.400000 559.800000 NaN NaN 0.600000 \n", "4 2014 1.333333 296.250000 0.529246 0.63618 0.666667 \n", "\n", " prop_empty ANC_ID \n", "0 0.0 1A \n", "1 0.0 1B \n", "2 0.0 1C \n", "3 0.0 1D \n", "4 0.0 1A " ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "election_df[\"ANC_ID\"] = election_df[\"ward\"].map(str) + election_df[\"anc\"]\n", "election_df.drop(columns = [\"ward\", \"anc\"], inplace=True)\n", "print(election_df.shape)\n", "election_df.head()" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(40, 8)\n" ] } ], "source": [ "election_df_2018 = election_df[election_df.year == 2018]\n", "print(election_df_2018.shape)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "<div>\n", "<style scoped>\n", " .dataframe tbody tr th:only-of-type {\n", " vertical-align: middle;\n", " }\n", "\n", " .dataframe tbody tr th {\n", " vertical-align: top;\n", " }\n", "\n", " .dataframe thead th {\n", " text-align: right;\n", " }\n", "</style>\n", "<table border=\"1\" class=\"dataframe\">\n", " <thead>\n", " <tr style=\"text-align: right;\">\n", " <th></th>\n", " <th>OBJECTID</th>\n", " <th>ANC_ID</th>\n", " <th>WEB_URL</th>\n", " <th>NAME</th>\n", " <th>Shape_Leng</th>\n", " <th>Shape_Area</th>\n", " <th>geometry</th>\n", " <th>year</th>\n", " <th>num_candidates</th>\n", " <th>votes</th>\n", " <th>vote_norm</th>\n", " <th>engagement</th>\n", " <th>prop_uncontested</th>\n", " <th>prop_empty</th>\n", " </tr>\n", " </thead>\n", " <tbody>\n", " <tr>\n", " <th>0</th>\n", " <td>1</td>\n", " <td>1C</td>\n", " <td>http://anc.dc.gov/page/advisory-neighborhood-c...</td>\n", " <td>ANC 1C</td>\n", " <td>5218.954361</td>\n", " <td>1.285112e+06</td>\n", " <td>POLYGON EMPTY</td>\n", " <td>2018</td>\n", " <td>1.375000</td>\n", " <td>690.500000</td>\n", " <td>0.695144</td>\n", " <td>0.818804</td>\n", " <td>0.625000</td>\n", " <td>0.0</td>\n", " </tr>\n", " <tr>\n", " <th>1</th>\n", " <td>2</td>\n", " <td>1D</td>\n", " <td>http://anc.dc.gov/page/advisory-neighborhood-c...</td>\n", " <td>ANC 1D</td>\n", " <td>4224.010068</td>\n", " <td>9.475922e+05</td>\n", " <td>POLYGON EMPTY</td>\n", " <td>2018</td>\n", " <td>1.600000</td>\n", " <td>513.200000</td>\n", " <td>0.608100</td>\n", " <td>0.818987</td>\n", " <td>0.400000</td>\n", " <td>0.0</td>\n", " </tr>\n", " <tr>\n", " <th>2</th>\n", " <td>3</td>\n", " <td>2A</td>\n", " <td>http://anc.dc.gov/page/advisory-neighborhood-c...</td>\n", " <td>ANC 2A</td>\n", " <td>12477.943204</td>\n", " <td>7.065358e+06</td>\n", " <td>POLYGON EMPTY</td>\n", " <td>2018</td>\n", " <td>1.125000</td>\n", " <td>254.375000</td>\n", " <td>0.688037</td>\n", " <td>0.770138</td>\n", " <td>0.875000</td>\n", " <td>0.0</td>\n", " </tr>\n", " <tr>\n", " <th>3</th>\n", " <td>4</td>\n", " <td>2B</td>\n", " <td>http://anc.dc.gov/page/advisory-neighborhood-c...</td>\n", " <td>ANC 2B</td>\n", " <td>7712.504785</td>\n", " <td>2.160620e+06</td>\n", " <td>POLYGON EMPTY</td>\n", " <td>2018</td>\n", " <td>1.222222</td>\n", " <td>574.666667</td>\n", " <td>0.698349</td>\n", " <td>0.840883</td>\n", " <td>0.777778</td>\n", " <td>0.0</td>\n", " </tr>\n", " <tr>\n", " <th>4</th>\n", " <td>5</td>\n", " <td>2C</td>\n", " <td>http://anc.dc.gov/page/advisory-neighborhood-c...</td>\n", " <td>ANC 2C</td>\n", " <td>7811.084627</td>\n", " <td>2.861750e+06</td>\n", " <td>POLYGON EMPTY</td>\n", " <td>2018</td>\n", " <td>1.333333</td>\n", " <td>417.000000</td>\n", " <td>0.686430</td>\n", " <td>0.806862</td>\n", " <td>0.666667</td>\n", " <td>0.0</td>\n", " </tr>\n", " </tbody>\n", "</table>\n", "</div>" ], "text/plain": [ " OBJECTID ANC_ID WEB_URL NAME \\\n", "0 1 1C http://anc.dc.gov/page/advisory-neighborhood-c... ANC 1C \n", "1 2 1D http://anc.dc.gov/page/advisory-neighborhood-c... ANC 1D \n", "2 3 2A http://anc.dc.gov/page/advisory-neighborhood-c... ANC 2A \n", "3 4 2B http://anc.dc.gov/page/advisory-neighborhood-c... ANC 2B \n", "4 5 2C http://anc.dc.gov/page/advisory-neighborhood-c... ANC 2C \n", "\n", " Shape_Leng Shape_Area geometry year num_candidates \\\n", "0 5218.954361 1.285112e+06 POLYGON EMPTY 2018 1.375000 \n", "1 4224.010068 9.475922e+05 POLYGON EMPTY 2018 1.600000 \n", "2 12477.943204 7.065358e+06 POLYGON EMPTY 2018 1.125000 \n", "3 7712.504785 2.160620e+06 POLYGON EMPTY 2018 1.222222 \n", "4 7811.084627 2.861750e+06 POLYGON EMPTY 2018 1.333333 \n", "\n", " votes vote_norm engagement prop_uncontested prop_empty \n", "0 690.500000 0.695144 0.818804 0.625000 0.0 \n", "1 513.200000 0.608100 0.818987 0.400000 0.0 \n", "2 254.375000 0.688037 0.770138 0.875000 0.0 \n", "3 574.666667 0.698349 0.840883 0.777778 0.0 \n", "4 417.000000 0.686430 0.806862 0.666667 0.0 " ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "joined_df = anc_df.merge(election_df_2018, how=\"left\", on=\"ANC_ID\")\n", "joined_df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Update geojson features from dataframe values" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "' 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'" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"\"\" No longer necessary\n", "for 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", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Construct map" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "anc_map = Map(location = (38.8899, -77.0091),\n", " zoom_start = 12,\n", " tiles = 'Stamen Toner')" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "<folium.features.Choropleth at 0x11b601c18>" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "folium.Choropleth(\n", " geo_data=gj_path,\n", " data=joined_df,\n", " columns=[\"ANC_ID\", \"votes\"],\n", " key_on='feature.properties.ANC_ID',\n", " fill_color='GnBu',\n", " fill_opacity=0.5,\n", " line_weight=1, \n", " highlight=True,\n", " overlay=True,\n", " name=\"average votes\",\n", " legend_name=\"average # votes for winning candidates\",\n", ").add_to(anc_map)" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "<folium.features.Choropleth at 0x11be971d0>" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "folium.Choropleth(\n", " geo_data=gj_path,\n", " data=joined_df,\n", " columns=[\"ANC_ID\", \"engagement\"],\n", " key_on='feature.properties.ANC_ID',\n", " fill_color='PuRd',\n", " fill_opacity=0.5,\n", " line_weight=1, \n", " highlight=True,\n", " overlay=True,\n", " name=\"engagement\",\n", " legend_name=\"percentage of ballots where ANC candidate was marked\",\n", ").add_to(anc_map)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "<folium.map.LayerControl at 0x11b6011d0>" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "LayerControl().add_to(anc_map)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " <iframe\n", " width=\"100%\"\n", " height=\"750px\"\n", " src=\"inline_map.html\"\n", " frameborder=\"0\"\n", " allowfullscreen\n", " ></iframe>\n", " " ], "text/plain": [ "<IPython.lib.display.IFrame at 0x11c4464e0>" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "embed_map(anc_map)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "anc_map.save(\"anc_map.html\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.3" } }, "nbformat": 4, "nbformat_minor": 2 }