New Mexico’s 53rd State Legislature
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 nmlegis.gov, 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(wnomadds)#devtools::install_github("jaytimm/wnomadds") library(tidyverse) library(DT) library(tidycensus); options(tigris_use_cache = TRUE, tigris_class = "sf") library(tigris) library(sf) library(knitr) library(ggthemes) library(data.table)
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) %>% rowwise()%>% mutate(Per_Dem = round(Dem/sum(Dem,Rep)*100,1)) %>% kable()
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())%>% kable() ## 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() %>% kable()
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) %>% summarise(count=n())
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")) + ggthemes::theme_fivethirtyeight()+ geom_density(alpha = 0.4)+ facet_wrap(~Chamber)+ 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")) + ggthemes::theme_fivethirtyeight()+ geom_density(alpha = 0.4)+ facet_wrap(~Chamber)+ 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) %>% kable()
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:
names(bill_dets) ## [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.
R18_HB0079: THANKSGIVING SATURDAY GROSS RECEIPTS |
---|
AN ACT RELATING TO TAXATION; PROVIDING A DEDUCTION FROM GROSS RECEIPTS FOR RETAIL SALES MADE ON THE FIRST SATURDAY AFTER THANKSGIVING BY CERTAIN BUSINESSES. |
The Sponsors
element summarizes bill sponsors as a simple data frame.
Chamber | District | Representative | Party |
---|---|---|---|
House | 29 | ADKINS | Rep |
House | 52 | GALLEGOS, DOREEN | Dem |
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)))+ coord_flip()+ labs(title = paste0("R18_HB0079: ", bill_dets$Result), caption = bill_dets$Title) + xlab("") + ylab("") + ggthemes::theme_fivethirtyeight()+ 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.
library(leaflet) pal <- colorFactor(palette = wnomadds::voteview_pal, domain = house_shape$Party_Member_Vote) house_shape %>% leaflet(width="450") %>% addProviderTiles(providers$OpenStreetMap, options = providerTileOptions (minZoom = 5, maxZoom = 8)) %>% addPolygons(popup = ~ Representative, fill = TRUE, stroke = TRUE, weight=1, fillOpacity = 1, color="white", fillColor=~pal(Party_Member_Vote)) %>% addLegend("topleft", pal = pal, values = ~ Party_Member_Vote, title = "Roll call", opacity = 1)
R-bloggers.com 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.