New Mexico’s 53rd State Legislature

[This article was first published on Jason Timm, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

In this post, we introduce a new R data package, nmlegisdatr, that makes available roll call data for New Mexico’s 53rd (2017-18) State Legislature (NMSL53). While these data are publicly available via, they are wrapped up in thousands of PDFs and, hence, largely inaccessible.

I have scraped roll calls and legislation details from these PDFs, and tidied things up some as a collection of data frames1; a full code-through of how the package was built is available here. The package can be downloaded here.

In this post, then, we demonstrate the contents/structure of the package, as well as its utility as an analytical tool. Ultimately, the goal is to make voting histories available (in an actionable format) for interested folks in New Mexico, as general elections for all 70 House seats are in November. This is the first in a series of posts investigating NMSL53.

library(nmlegisdatr)# devtools::install_github("jaytimm/nmlegisdatr")
library(tidycensus); options(tigris_use_cache = TRUE, tigris_class = "sf")

Package descriptives

The table below details the different data frames included in the nmlegisdatr package. Column names are consistent across tables, facilitating easy joining. We will demonstrate their specific contents as we go.

Table_Name Description
nml_legislation All introduced legislation, including bill id, title, and bill description
nml_legislators All legislators in both chambers, including party affiliation and district id
nml_rollcall Roll calls for all legislation reaching either chamber for vote
nml_sponsors Sponsors for each bill
nml_rollcall_results Summary roll call results
nml_legislator_descs Summary legislator voting patterns and attendance

NMSL53: an overview

We first consider some high level characteristics of NMSL53. Based on the nml_legislators data frame included in nmlegisdatr, the table below summarizes chamber composition by political affiliation. As can be noted, a slight majority in the House for Democrats, and a more sizable one in the Senate. So, unified party control in New Mexico’s legislative branch. (See postscript for a code-through of visualizing congressional compositions.)

nmlegisdatr::nml_legislators %>%
  filter(Representative != 'LT. GOV') %>%
  group_by(Chamber, Party) %>%
  summarize(Count = n()) %>%
  ungroup() %>%
  spread(Party, Count) %>%
  mutate(Per_Dem = round(Dem/sum(Dem,Rep)*100,1)) %>%
Chamber Dem Rep Per_Dem
House 38 32 54.3
Senate 26 16 61.9

Roll call results are summarized in the nml_rollcall_results table. Counts of legislation reaching a vote in NMSL53 are summarized below by chamber and session/year. The first session (2017) is two months in duration while the second session (2018) is only one month in duration.

nmlegisdatr::nml_rollcall_results %>%
  left_join(nml_legislation) %>%
  group_by(Chamber, Session, Year) %>%
  summarise(rollcalls = n())%>%
## Joining, by = "Bill_Code"
Chamber Session Year rollcalls
House Regular 2017 509
House Regular 2018 289
House Special 2017 5
Senate Regular 2017 490
Senate Regular 2018 233
Senate Special 2017 5

A sample of the nml_rollcall_results table is presented below. As can be noted, the table breaks down each vote by party affiliation. It also summarizes how each party voted in the aggregate. Examples below are for House votes only.

nmlegisdatr::nml_rollcall_results %>% filter(Chamber == 'House') %>% 
  select(-Chamber, -Dem_Vote, -Rep_Vote, -Roll_URL, -Motion) %>% 
  head() %>%
Bill_Code Result Dem: Yea Rep: Yea Dem: Nay Rep: Nay
R17_HB0001 65-0 35 30 0 0
R17_HB0002 37-32 37 0 0 32
R17_HB0004 40-26 36 4 1 25
R17_HB0005 37-29 37 0 0 29
R17_HB0008 60-0 33 27 0 0
R17_HB0009 62-0 33 29 0 0

Based on this table, we can get a sense of the degree of bi-partisanship in both houses. Here, we classify each roll call as one of the following:

  • Full consensus: All yea’s
  • Bi-partisan: Over 50% yea’s in both parties
  • Party line: Over 90% yea’s in one party and over 90% nay’s in the other
  • Competitive: All other roll calls
votes <- nmlegisdatr::nml_rollcall_results %>%
  mutate(dem_den = `Dem: Yea`+`Dem: Nay`,
         rep_den = `Rep: Yea`+`Rep: Nay`) %>%
  mutate(`Dem: Yea`= `Dem: Yea`/dem_den,
         `Dem: Nay`= `Dem: Nay`/dem_den,
         `Rep: Yea`= `Rep: Yea`/rep_den,
         `Rep: Nay`= `Rep: Nay`/rep_den) %>%
  mutate(line_vote = ifelse(`Dem: Yea` > 0.5 & `Rep: Yea` > 0.5, 
                            'Bi-partisan', 'Competitive')) %>%
  mutate(line_vote = ifelse(`Dem: Nay` == 0 & `Rep: Nay` == 0, 
                            'Full Consensus',line_vote)) %>%
  mutate(line_vote = ifelse((`Dem: Nay` > 0.9 & `Rep: Yea` > 0.9) | 
                            (`Dem: Yea` > 0.9 & `Rep: Nay` > 0.9), 
                            'Party Line',line_vote)) %>%
  group_by(Chamber, line_vote) %>%

The table below summarizes these distributions. Both chambers, then, vote largely in full consensus. I am not sure if this is typical of state legislatures, or if this is good or bad. That said, quite a bit of legislation is largely symbolic, eg. memorials, for which there is little contention.

Chamber Party Line Competitive Bi-partisan Full Consensus
House 34 56 134 579
Senate 24 68 132 504

Attendance & party loyalty

Next, we consider some details about the aggregate voting records of lawmakers in NMSL53. The nm_legislator_descs table summarizes the total votes cast, party loyalty, and attendance rates for each lawmaker. A portion of the table is presented below.

Votes Cast is the number of times a given legislator voted ‘Yea’ or ‘Nay’ in the legislature. Party Loyalty is the percentage of total roll calls that a given legislator voted in the same direction as the majority of his/her party. Attendance is the percentage of total roll calls for which a given legislator was not absent (ie, being excused/recused is counted as being present).

nmlegisdatr::nml_legislator_descs %>%
  select(Chamber, Representative, Party, Votes_Cast, Party_Loyalty, Attendance) %>%
  head() %>%  kable()
Chamber Representative Party Votes_Cast Party_Loyalty Attendance
House ADKINS Rep 768 95.1 97.8
House ALCON Dem 780 97.9 99.3
House ARMSTRONG, D. Dem 764 99.1 98.0
House ARMSTRONG, GAIL Rep 776 96.6 99.0
House BALDONADO Rep 768 96.4 97.0
House BANDY Rep 701 95.4 88.9

The plot below summarizes attendance rate distributions by political affiliation for each house. While attendance rates are generally quite high, Democratic lawmakers are less likely to miss roll calls than their Republican counterparts.

nmlegisdatr::nml_legislator_descs %>%
ggplot( aes(Attendance, fill = Party, colour = Party)) +
  wnomadds::scale_color_rollcall(aesthetics = c("fill","color")) + 
  geom_density(alpha = 0.4)+
  labs(title="Attendance rate distributions in NMSL53")+
  theme(legend.position = "none",
        plot.title = element_text(size=12))

The plot below summarizes party loyalty rate distributions by political affiliation for each house. Again, party loyalty rates are quite high in each party; however, in both the Senate and the House, Republicans are less loyal to party than Democrats.

This discrepancy is clearly a complicated one. Certainly relevant is Democratic majorities (and fairly sizable ones) in both chambers. Also relevant is potential variation of political ideologies among Republicans in a cash-strapped state that has become solidly blue.

nmlegisdatr::nml_legislator_descs %>%
ggplot( aes(Party_Loyalty, fill = Party, colour = Party)) +
  wnomadds::scale_color_rollcall(aesthetics = c("fill","color")) + 
  geom_density(alpha = 0.4)+
  labs(title="Party loyalty rate distributions in NMSL53")+
  theme(legend.position = "none",
        plot.title = element_text(size=12))

Roll call data underlying these aggregate voting records can be quickly accessed for individual legislators in NMSL53 using the nml_get_legislator function from nmlegisdatr. The function takes two parameters, legislator and chamber, and returns a list of three elements.

Below we access the Voting_Record element of a search for Republican Congressman James Townsend from House District 54. Member_Vote values in all caps indicate votes in which a lawmaker voted against the majority of his/her party.

nmlegisdatr::nml_get_legislator(legislator = 'TOWNSEND', 
                                chamber = 'House')$Voting_Record %>%
  slice(17:22) %>%
Chamber Bill_Code Motion Party_Vote Member_Vote Result
House R17_HB0032 Passage Yea NAY 50-11
House R17_HB0033 Passage Yea Yea 68-0
House R17_HB0034 Passage Yea Yea 67-0
House R17_HB0035 Passage Yea NAY 47-12
House R17_HB0036 Passage Yea Yea 57-0
House R17_HB0040 Passage Yea Excused 48-11

Roll call details

The nmlegisdatr package includes a simple roll call search function, nml_get_bill, that allows users to quickly obtain details about a given piece of legislation. The year, session, bill, and chamber parameters can be used to identify legislation. The function more or less just filters the multiple tables included in nmlegisdatr and collates bill details in one place (akin to the nml_get_legislators function presented above).

bill_dets <- nmlegisdatr::nml_get_bill(year = '2018',
                                       session = 'Regular',
                                       bill = 'HB0079',
                                       chamber = 'House')

The resulting object contains seven elements:

## [1] "Title"       "Sponsors"    "Description" "Motion"      "Result"     
## [6] "Actions"     "Vote"        "Rollcall"

The Description element provides a brief bill description as detailed below. The prefix “R18” specifies a Regular session vote in 2018.


The Sponsors element summarizes bill sponsors as a simple data frame.

Chamber District Representative Party
House 29 ADKINS Rep
Senate 2 NEVILLE Rep

The Result element summarizes roll call results by political affiliation:

bill_dets$Vote %>% 
  ggplot(aes(x=Party_Member_Vote, y=Count, fill= Party_Member_Vote)) +
    geom_col(width=.65, color = 'lightgray') +  
    wnomadds::scale_color_rollcall(aesthetics = c("fill")) +
    scale_x_discrete(limits = rev(levels(bill_dets$Vote$Party_Member_Vote)))+
    labs(title = paste0("R18_HB0079: ", bill_dets$Result),
         caption = bill_dets$Title) +
    xlab("") + ylab("") +
    theme(legend.position = "none",
          plot.title = element_text(size=12))

The Rollcall element details how each legislator voted on a given piece of legislation. Here, we map votes by party affiliation and legislative district. Via the tigris package, we obtain a shape file for lower house legislative districts in New Mexico, and join the Rollcall data frame.

house_shape <- tigris::state_legislative_districts("New Mexico", 
                                                   house = "lower", 
                                                   cb = TRUE)%>% 
  inner_join(bill_dets$Rollcall, by = c('NAME'='District')) %>%
  st_set_crs('+proj=longlat +datum=WGS84')

We then map roll call results by legislative district using the leaflet package, again using the nml_fill_vote function to classify votes by party affiliation. Among other things, the map illustrates an aversion to gross receipt tax exemptions among Republicans in southeastern New Mexico.

pal <- colorFactor(palette = wnomadds::voteview_pal, 
  domain = house_shape$Party_Member_Vote)

house_shape %>%
leaflet(width="450") %>%
                 options = providerTileOptions (minZoom = 5, maxZoom = 8)) %>%
addPolygons(popup = ~ Representative,
                fill = TRUE, 
                stroke = TRUE, weight=1,
                fillOpacity = 1,
                fillColor=~pal(Party_Member_Vote)) %>%
              pal = pal, 
              values = ~ Party_Member_Vote,
              title = "Roll call",
              opacity = 1)

To leave a comment for the author, please follow the link and comment on their blog: Jason Timm. offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)