Los Alamos Voting Data on a Folium Choropleth Map (Shallow Thoughts)

Akkana's Musings on Open Source Computing and Technology, Science, and Nature.

Thu, 07 Sep 2023

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
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.


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:

 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.)

[red/blue map of Los Alamos county voter registration: all purple] 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

[Folium Choropleth illustrating wrong colormap] 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


That did the trick: the map now showed shades of red and purple.

Choropleth Popups

[Folium choropleth with popup for Los Alamos County voting precincts] 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:

                              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:


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.

Tags: , , ,
[ 11:58 Sep 07, 2023    More programming | permalink to this entry | ]

Comments via Disqus:

blog comments powered by Disqus