Los Alamos Voting Data on a Folium Choropleth Map
Somebody in a group I'm in has commented more than once that White Rock is a hotbed of Republicanism whereas Los Alamos leans Democratic. (For outsiders, our tiny county has two geographically-distinct towns in it, with separate zip codes, though officially they're both part of Los Alamos township which covers all of Los Alamos county. White Rock is about half the size of Los Alamos.)
After I'd heard her say it a couple times, I got curious. Was it true? I asked her for a reference, but she didn't have one. I decided to find out.
First, Find (or Scrape) the Data
I wasn't able to find a good source for voter registration data in an easily machine-readable format. The NM Secretary of State website has a Voter Registration Statistics page which has links to registration data in (ick!) PDF format. Email to the SOS's Public Records Request contact went unanswered (as usual).
So my only alternative was to scrape the data from the PDF. I loaded the 2022 PDF in Firefox, selected everything from Los Alamos county, and pasted it into a text file.
That came out looking something like this:
LOS ALAMOS County As of November 30, 2022 PRECINCT DEMOCRATIC REPUBLICAN LIBERTARIAN OTHER TOTAL PCT 001 325 41 % 257 33 % 11 1 % 194 25 % 787 5 % PCT 002 251 33 % 311 41 % 11 1 % 183 24 % 756 5 %... and so on.
(That's typical for a PDF, and explains why programmers hate PDF so much, in case you were wondering.)
Okay, so "PCT" starts a new entry; a number followed by a percent sign is a percent, while other numbers are absolute counts; and spaces and newlines are distributed somewhat randomly throughout and so must be ignored. So I wrote a script for that, los-alamos-voters.py, which turned the data into CSV format, la-precinct-registration.csv.
Precincts
The voter registration data is by precinct, of which Los Alamos has 23.
I needed to know which precincts comprised White Rock vs. Los Alamos.
I'd gotten GIS for Los Alamos's precincts a few weeks earlier from
Michael Smith, the
county GIS specialist (who is terrific, very knowledgeable and helpful),
and I'll be using that later in this project. But for now, it was
easier to do a web search for los alamos precinct map
which found
this
PDF precinct map. Then I counted them by hand, resulting in
WHITE_ROCK_PRECINCTS = [ 1, 2, 3, 4, 5, 6, 7, 8 ] LOS_ALAMOS_PRECINCTS = [ 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 ]
I added those to the script and had it print out the breakdown:
DEMOCRATIC REPUBLICAN LIBERTARIAN OTHER TOTAL White Rock 1823 (36%) 1773 (35%) 91 (1%) 1368 (27%) 5055 (100%) Los Alamos 4641 (44%) 2682 (25%) 178 (1%) 2992 (28%) 10493 (100%)
Neat. So White Rock is almost evenly divided between Democrats (36%) and Republicans (35%), while Los Alamos leans more Democratic (44%/25%).
And note how big that OTHER category is: there are almost as many OTHER voters as R or D. And New Mexico doesn't have open primaries, so the only people who register as "Decline to State" or minor parties are those who feel really strongly about not identifying with the major parties — so strongly that they're willing to give up their right to vote in the primaries. I bet if we had open primaries, those OTHER numbers would be a lot bigger.
A Red-Blue Map
But now I was curious. How homogeneous is the county? Are there areas that lean more strongly in one direction or another? I wanted one of those red-blue voting maps where really everybody's purple.
I've written in the past about how easy it is to make maps with Folium, and it seemed like the perfect tool for this job.
I translated the county precinct shapefile data to GeoJSON with
ogr2ogr -t_srs EPSG:4326 -f GeoJSON los_alamos_precincts.json ../losalamos-20230802/los_alamos_voting_precincts.shp
(By the way, if your county doesn't have a helpful GIS person, voting precinct GIS is surprisingly easy to find at the US Census and various other data repositories.)
Combining the CSV and the GeoJSON was easy following the same steps I'd followed in my earlier blog post. In maybe half an hour I had a map -- and it wasn't very satisfying. Basically, the county is pretty homogeneous, so everything was a medium purple.
What's worse, if there actually were
differences between the shades of purple, it would be impossible to tell
because the folium.GeoJson
overlay was too transparent
against the base map, so the underlying colors bleed through.
It's good to know that the county doesn't have any areas of extremism.
But I was still curious how political leaning varied across our various
precincts, even if the variation is small.
folium.Choropleth Color Options
I spent a long time trying to find a way to specify opacity in a
folium.GeoJson
layer, with no success.
But the more complicated folium.Choropleth
does allow specifying opacity. So I tried that.
And it wasn't quite right either. The problem is that folium's
Choropleth
uses a fairly small selection of predefined
colormaps — I was using
fill_color="RdBu_r"
, meaning the
Brewer
palette from Red to Blue, but in reverse.
But the middle isn't purple, it's a light blue next to a sort of
pinkish tan (see the scale at the top right of the image).
Worse, the Choropleth code assumed that the center of the data range should correspond to the center of the colormap, which isn't right since the whole county on average leans a little to the blue (like the state). So for instance, Precinct 1, the big one on the south that also covers most of LANL, is rendered as pinkish-tan even though its breakdown is 32% Republican, 41% Democrat, so it should be more blue than red.
It's possible to design a new colormap and weight it appropriately,
but that looked like a lot of work.
But it turned out that it's also possible to override
folium.Choropleth
's colormaps to use the same sort of
style function that I used with folium.GeoJson
,
where you pass in the object from the GeoJSON layer and the function
returns a snippet of CSS specifying the color (and other factors
like opacity).
If pdata is the pandas dataframe containing the
voting data,
cp = folium.Choropleth( ... ) def redbluestyle(feature): precinct = feature['properties']['V_DISTRICT'] thisprecinct = pdata[pdata["Precinct"] == precinct] bluefrac = (thisprecinct['DEM_PCT'] - min_percent) / normalize; redfrac = (thisprecinct['REP_PCT'] - min_percent)/ normalize; color = '#%02x%02x%02x' % (int(redfrac * 256), 0, int(bluefrac * 256)) return { 'fillColor': color, 'fillOpacity': .75 } cp.geojson.style_function = redbluestyle cp.add_to(map)
That did the trick: the map now showed shades of red and purple.
Choropleth Popups
There was one other thing folium.Choropleth
made more difficult:
showing detail when you click on or hover over a precinct.
In folium.GeoJson
that's easy: just include something
like this in the argument list when you create the GeoJSON layer:
popup=folium.GeoJsonPopup(fields=['featuretxt'], labels=False, aliases=['Precinct']),
Choropleth
doesn't accept the popup
argument.
But it turns out that a Choropleth
object has a
geojson
member inside it, and you can add popups to that:
folium.GeoJsonPopup(['featuretxt'], aliases=['']).add_to(cp.geojson)
And the results were very interesting, and not quite what I'd expected, so it was a worthwhile exercise.
The code is at LA-voter-registration
in my webapps GitHub repository, and
you can view the live map at:
Los Alamos
Voter Registration Percentages Map.
[ 11:58 Sep 07, 2023 More programming | permalink to this entry | ]