Site icon R-bloggers

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

    Health care-related roll calls

    Next, we take a more detailed perspective on NMSL53, and investigate the results of individual roll calls. For demonstration purposes, we focus on legislation that references either “Health Care” or “Health Coverage” in their descriptions. Bill descriptions and details are available as a data frame in nml_legislation; an example piece of legislation is presented below.

    eg <- nmlegisdatr::nml_legislation %>%
      slice(11) %>%
      select(Bill_Title, Bill_Description)
    ARTERY SCREENING COVERAGE
    AN ACT RELATING TO HEALTH COVERAGE; ENACTING SECTIONS OF THE HEALTH CARE PURCHASING ACT, THE PUBLIC ASSISTANCE ACT, THE NEW MEXICO INSURANCE CODE, THE HEALTH MAINTENANCE ORGANIZATION LAW AND THE NONPROFIT HEALTH CARE PLAN LAW TO REQUIRE COVERAGE OF ARTERY CALCIFICATION SCREENING FOR EARLY DETECTION OF CARDIOVASCULAR DISEASE IN CERTAIN INDIVIDUALS.

    We can search these bill descriptions for reference to “Health Care/ Health Coverage” with a quick/simple call to the gregexpr function — the table below summarizes the results of this search.

    bills <- gregexpr(pattern= 'HEALTH CARE| HEALTH COVERAGE', 
                  nmlegisdatr::nml_legislation$Bill_Description, 
                  ignore.case=TRUE) %>%
      data.table::melt() %>%
      filter(value != -1) %>%
      select(L1) %>% unique
    
    bills1 <- nmlegisdatr::nml_legislation[bills$L1,]%>%
      select(Bill_Code, Bill_Title) 

    Then we summarize roll calls for Health care-related legislation that actually went to vote in the House. The faceted plot below illustrates vote results by party affiliation; we use the nml_fill_vote to color-code votes by party affiliation akin to the color scheme used at VoteView. So, a nice high-level perspective.

    nmlegisdatr::nml_rollcall %>%
      filter(Bill_Code %in% bills1$Bill_Code & Chamber == 'House') %>%
      group_by(Bill_Code, Party_Member_Vote) %>%
      summarize (Count = n())%>%
        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(nml_rollcall$Party_Member_Vote)))+
        coord_flip()+
        labs(title = "Roll call results for legislation referencing HEALTH CARE") +
        xlab("") + ylab("") +
        ggthemes::theme_fivethirtyeight()+
        theme(legend.position = "none",
              plot.title = element_text(size=12))+
        facet_wrap(~Bill_Code, ncol = 5)

    While most roll calls in this subset are lopsided in full consensus (ie, yea’s all-around), there are some examples of party-line votes as well as some in-party divisiveness (mostly among Republican state lawmakers).

    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="100%") %>%
    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)
  • To leave a comment for the author, please follow the link and comment on their blog: Jason Timm.

    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.