Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

This is the second in a series of posts investigating voting patterns in New Mexico’s 53rd State Legislature (NMSL53). In this post, we detail the use of K. T. Poole and Rosenthal (2011) ’s ideal point estimation procedure (aka NOMINATE) to examine political ideologies in NMSL53 using the R package wnominate (K. Poole et al. 2011).

NOMINATE is a multidimensional scaling procedure that represents legislators in two-dimensional political “space” based on roll call voting records for a given legislature. Throughout the history of the US Congress, the first dimension of this space has captured ideological differences along the traditional liberal-conservative continuum, while the second dimension has captured ideological differences based in social conservatism that crosscut party affiliation (K. T. Poole and Rosenthal 2011).

Here we apply these methods to roll call data from NMSL53 made available in my R package nmlegisdatr to get of sense of the political ideologies informing voting behavior among New Mexico State Senators. We also introduce a simple package that I have developed called wnomadds that facilitates finer-grained exploration of wnominate results.

library(wnomadds)#devtools::install_github("jaytimm/wnomadds")
library(nmlegisdatr)#devtools::install_github("jaytimm/nmlegisdatr")
library(tidyverse)
library(wnominate)
library(pscl)
library(knitr)

We quickly run through the ideal points estimation procedure using wnominate, and then spend most of the post unpacking and visualizing model results.

## Ideal points estimation

Roll call data for New Mexico’s 53rd State Legislature are made available as a data frame called nml_rollcall in my nmlegisdatr package. As can be noted below, roll call data are in a long format, with each row representing a unique legislator-bill vote.

nmlegisdatr::nml_rollcall %>%
filter (Chamber == 'Senate') %>%
select(Bill_Code, Representative, Member_Vote) %>%
kable()
Bill_Code Representative Member_Vote
R17_HB0001 BACA Nay
R17_HB0001 BRANDT Nay
R17_HB0001 BURT Yea
R17_HB0001 CAMPOS Yea
R17_HB0001 CANDELARIA Yea
R17_HB0001 CERVANTES Yea

Before using the wnominate package to run an ideal point estimation, we first need to reshape roll call data from long to wide format. Resulting data structure casts each roll call as an individual column and each legislator as an individual row.

wide_rolls <- nmlegisdatr::nml_rollcall  %>%
filter(Chamber =='Senate' & !grepl('^LT', Representative)) %>%
mutate(Bill_Unique = paste0(Bill_Code, substr(Motion, 1,1))) %>%
dplyr::select(Representative, Bill_Unique, Member_Vote) %>%
mutate(Member_Vote = case_when(Member_Vote == "Yea" ~ 1,
Member_Vote == "Nay" ~ 6,
Member_Vote %in% c("Excused", "Absent", "Rec") ~ 9)) %>%
spread(key= Bill_Unique, value = Member_Vote)

Per this new data structure, we then build a roll call object using the pscl package.

roll_obj <- pscl::rollcall(wide_rolls [,-1],
yea = 1,
nay = 6,
missing = 9,
notInLegis = NA,
vote.names = colnames(wide_rolls)[2:ncol(wide_rolls)],
legis.names = wide_rolls$Representative) Next, we perform ideal point estimation using the wnominate::wnominate function. The polarity parameter is used to specify a legislator who is conservative on each dimension. This does not “train” (or influence) the model in any way; it only affects the polarity of legislator coordinates in two-dimensional space such that conservatives are to the right (ie, positive coordinates) and liberals to the left (ie, negative coordinates), per the standard political metaphor. This will become clearer shortly. ideal_2D <- wnominate::wnominate (roll_obj, polarity=c("BURT","BURT")) # ## ## Preparing to run W-NOMINATE... ## ## Checking data... ## ## All members meet minimum vote requirements. ## ## Votes dropped: ## ... 507 of 728 total votes dropped. ## ## Running W-NOMINATE... ## ## Getting bill parameters... ## Getting legislator coordinates... ## Starting estimation of Beta... ## Getting bill parameters... ## Getting legislator coordinates... ## Starting estimation of Beta... ## Getting bill parameters... ## Getting legislator coordinates... ## Getting bill parameters... ## Getting legislator coordinates... ## Estimating weights... ## Getting bill parameters... ## Getting legislator coordinates... ## Estimating weights... ## Getting bill parameters... ## Getting legislator coordinates... ## ## ## W-NOMINATE estimation completed successfully. ## W-NOMINATE took 5.42 seconds to execute. The above notifications indicate that 507 out of the total 728 roll calls in the State Senate were excluded from the model by virtue of being lopsided, ie, legislation that is largely agreed upon does not shed light on how the political ideologies of legislators differ. The resulting NOMINATE object consists of seven elements. We will unpack each as we go. names(ideal_2D) ## [1] "legislators" "rollcalls" "dimensions" "eigenvalues" "beta" ## [6] "weights" "fits" ## Legislators in political space The legislator element contains model results-proper, ie, the ideal point estimates. Again, these estimates represent coordinates in a two-dimensional space bounded by a unit circle. Below we extract these coordinates, and add legislator details (eg, party affiliation and legislative district) for subsequent analyses. row.names(ideal_2D$rollcalls) <- colnames(wide_rolls)[2:ncol(wide_rolls)]

senate_data <- ideal_2D$legislators %>% bind_cols(nml_legislators %>% filter(Chamber == 'Senate' & !grepl('^LT', Representative)))  Sample output of legislator coordinates: Rep_Full District Party coord1D coord2D Gregory A. Baca 29 Rep 0.8773707 -0.3158852 Craig Brandt 40 Rep 0.9457566 -0.3203720 William F. Burt 33 Rep 0.4949957 0.3735212 Pete Campos 8 Dem -0.6229612 0.3306362 Jacob Candelaria 26 Dem -0.6403133 -0.6716896 Joseph Cervantes 31 Dem -0.7549632 -0.0196459 #### One-dimensional model First, we consider the first dimension of model results independently. As noted in K. T. Poole and Rosenthal (2011) (and the NOMINATE literature generally), variation along the first dimension captures traditional partisan divisions in a given legislature. The figure below illustrates 1D scores plotted against rank for New Mexico’s 53rd State Senate. Legislators with scores closer to 1 are described as more conservative and those with scores closer to -1 are described as more liberal. Legislators with scores close to zero can be described as moderate. ggplot(senate_data, aes(x=reorder(Representative, coord1D), y=coord1D, label=Representative)) + geom_point(stat='identity', aes(col=Party), size=4.5) + wnomadds::scale_color_rollcall()+ geom_text(size=2.5, nudge_y = -0.1) + labs(title="Figure 1: Legislator ideal point estimates in one-dimensional political space") + ggthemes::theme_fivethirtyeight()+ theme(axis.title.y=element_blank(), axis.text.y=element_blank(), legend.position = 'none', plot.title = element_text(size=12)) + coord_flip() Per model results along 1D, then, William Soules from District 37 can be described as the most liberal Democrat in the Senate and William Sharer from District 1 the most conservative Republican. In general, Republicans are more moderate in their voting patterns — State Senators Rue, Kernan, and Neville in particular. #### Two-dimensional model Next, we consider a two-dimensional model. Again, per K. T. Poole and Rosenthal (2011), variation along the second dimension in the US Congress has historically captured ideological differences based in social conservatism that crosscut party affiliation — reflecting different stances on social issues of the day, eg, the abolition of slavery, civil rights, and lifestyle choices. library(ggforce) circle <- data.frame( x0 = 0, y0 = 0, r = 1) d2 <- ggplot(senate_data, aes(x=coord1D, y=coord2D)) + geom_point(aes(color = Party), size= 3, shape= 17) + scale_color_rollcall() + theme(legend.position = 'bottom') + geom_text(aes(label=Representative), size=2.5, check_overlap = TRUE, hjust = "inward", nudge_y = -0.03)+ labs(title="Figure 2: Legislator ideal point estimates in two-dimensional political space") + ggforce::geom_circle (data = circle, aes(x0=x0, y0=y0, r=r), color = 'light gray', inherit.aes=FALSE) + coord_fixed()  As the figure illustrates, the added second dimension captures variation in voting patterns among legislators that crosscut party affiliation, most notably, dividing Senators in the Republican party. We will investigate the ideology underlying the second dimension in the NMSL53 Senate as we go. ## Legislation in 2D political space Figure 2, then, represents how legislators voted on all pieces of legislation in a given legislature. Per the NOMINATE procedure, the underlying structure of this political space is based on individual roll call cutting lines. A cutting line bisects the political space, dividing legislators who voted ‘Yea’ for a given piece of legislation from those who voted ‘Nay’. Long story short, the procedure positions legislator ideal points and cutting line coordinates to optimize correct classification of all votes cast (ie, Yea v. Nay) by all legislators in a given legislature. Figures 1 & 2 are ultimately the product of this optimization process/algorithm. The wnominate package provides two functions for investigating these cutting lines: plot.cutlines() and plot.angles(). The plots below illustrate (the first 50) cutting lines (along with legislator coordinates) and the distribution of cutting line angles, respectively. par(mfrow=c(1, 2)) wnominate::plot.cutlines(ideal_2D) ## NULL points(ideal_2D$legislators$coord1D, ideal_2D$legislators$coord2D, col="blue", font=2, pch=16) wnominate::plot.angles(ideal_2D) As the histogram demonstrates, the large proportion of cutting lines have angles in the 90 degree range, ie, more vertical cutting lines that divide legislators along the first dimension. We can investigate the efficacy of these cutting lines in correctly classifying votes via the fits element of the NOMINATE object. Per the output below, a one-dimensional model correctly classifies 90.2% of all cast votes. A second dimension accounts for an additional 1.3% of cast votes. ideal_2D$fits
## correctclass1D correctclass2D         apre1D         apre2D          gmp1D
##     90.2219086     91.5391006      0.5242494      0.5883372      0.7607572
##          gmp2D
##      0.8176903

What does this mean? Well, we have a fairly good model. We also have a State Senate in NMSL53 that is quite uni-dimensional in its voting patterns; in other words, the traditional partisan (ie, liberal-conservative) divide (as represented in Figure 1) can account for most of the voting behavior in the NM Senate.

While we do not have historical data for the NMSL, K. T. Poole and Rosenthal (2011) have noted the shrinking utility of the second dimension in accounting for voting patterns in the US Congress historically — which they interpret (paraphrased very roughly here) as the recasting of certain social issues in the national political debate (ca 1980s) as economic ones.

Despite the relatively small contribution of the second dimension in accounting for voting patterns in NMSL53, we carry on with our 2D model.

#### Cutting line coordinates & roll call polarity

While the wnominate::plot.cutlines() and wnominate::plot.angles()are super convenient functions for quickly visualizing model results, they hide away underlying data and, hence, limit subsequent analyses and visualizations. To address these limitations, I have developed a simple R package called wnomadds. Functions included in the package, dubbed wnm_get_cutlines() and wnm_get_angles(), are based on existing wnominate code, and have been tweaked some to output data frames as opposed to base R plots.

Here, we take a bit more detailed perspective on cutting line coordinates and roll call polarity using the wnomadds::get_cutlines() function. The function takes a nomObj object and a rollcall object (from the previous call to pscl::rollcall). In addition to cutting line coordinates, the function returns the coordinates of two points perpendicular to cutting line ends that specify the polarity of the roll call, ie, the direction of the ‘Yea’ vote in political space.

with_cuts <- wnomadds::wnm_get_cutlines(ideal_2D,
rollcall_obj = roll_obj,
arrow_length = 0.05)

Output contains four sets of (x,y) points. These four sets of points can be used to create three line segments via geom_segment: the actual cutting line and two line arrow segments denoting the polarity of the roll call.

head(with_cuts)
##      Bill_Code       x_1         y_1          x_2        y_2      x_1a
## 1: R17_HB0001P 0.7659736  0.64287197  0.684223776 -0.7292721 0.6973664
## 2: R17_HB0002P 0.9757444  0.21891277 -0.197162242 -0.9803709 0.9157803
## 3: R17_HB0063P 0.7779014 -0.62838635  0.474137214  0.8804510 0.7024595
## 4: R17_HB0080P 0.9966121  0.08224562 -0.633982079 -0.7733477 0.9538324
## 5: R17_HB0086P 0.6468509 -0.76261647 -0.001531652  0.9999988 0.5587202
## 6: R17_HB0087P 0.2567056  0.96648966 -0.149109277 -0.9888207 0.1589400
##          y_1a        x_2a       y_2a
## 1:  0.6469595  0.61561657 -0.7251846
## 2:  0.2775581 -0.25712642 -0.9217255
## 3: -0.6435746  0.39869535  0.8652628
## 4:  0.1637753 -0.67676175 -0.6918180
## 5: -0.7950356 -0.08966242  0.9675797
## 6:  0.9867804 -0.24687480 -0.9685300

The plot below illustrates cutting lines, legislator ideal points, and roll call polarity.

ggplot () +
theme(legend.position = 'bottom') +
geom_point(data=senate_data,
aes(x=coord1D, y=coord2D,color = Party),
size= 3,
shape= 17) +
geom_segment(data=with_cuts, #cutting start to end
aes(x = x_1, y = y_1, xend = x_2, yend = y_2)) +
geom_segment(data=with_cuts, #cutting end to opposite arrow
aes(x = x_2, y = y_2, xend = x_2a, yend = y_2a),
arrow = arrow(length = unit(0.2,"cm"))) +
geom_segment(data=with_cuts, #cutting start to opposite arrow
aes(x = x_1, y = y_1, xend = x_1a, yend = y_1a),
arrow = arrow(length = unit(0.2,"cm")))+
geom_text(data=with_cuts,
aes(x = x_1a, y = y_1a, label = Bill_Code),
size=2.5,
nudge_y = 0.03,
check_overlap = TRUE) +
coord_fixed(ratio=1) +
labs(title="Figure 3: Cutting lines, roll call polarity & legislator coordinates")

While a bit chaotic, the plot provides a nice high-level perspective on how legislation positions legislators in political space per the NOMINATE procedure. Again, most cutting lines are vertical in nature, dividing the legislature along partisan lines — perhaps most notable is the prevalence of vertical cutting lines on the right (ie, Republican) side of the plot. That said, there are some more horizontal cutting lines that reflect voting ideologies that crosscut political affiliation.

#### Cutting line angles & legislative bills

To get a sense of the types of legislation that divide the legislature along each dimension of political space (and the political ideologies underlying them), we use the wnm_get_angles() function to extract cutting line angles from the NOMINATE object.

angles <- wnomadds::wnm_get_angles(ideal_2D)

Sample output includes legislation ID and cutting line angles:

##     Bill_Code     angle
## 1 R17_HB0001P  86.59045
## 2 R17_HB0002P  45.63706
## 3 R17_HB0063P 101.38282
## 4 R17_HB0080P  27.68656
## 5 R17_HB0086P 110.19618
## 6 R17_HB0087P  78.27501

We can identify legislation that positions legislators in 1D by filtering the above output to roll calls with more vertical cutting lines. Here, we take a random sample of legislation with cutting line angles between 85 and 95 degrees.

set.seed(99)
angles %>%
mutate(Bill_Code = gsub('.$', '', Bill_Code)) %>% left_join(nml_legislation) %>% filter (angle > 85 & angle < 95) %>% sample_n(10) %>% select(Bill_Code, angle, Bill_Title)%>% kable(digits = 1, row.names = FALSE) Bill_Code angle Bill_Title R17_SB0277 93.3 PREGNANT/ LACTATING ALTERNATIVE SENTENCING R17_HB0199 87.6 DISTRIBUTED GENERATION CONSUMER PROTECTION R17_SB0374 89.6 HUNGER-FREE STUDENTS’ BILL OF RIGHTS ACT R18_HB0235 89.4 RAISE MUNICIPAL COURT AUTOMATION FEE R17_SB0139 91.8 AUTO RECYCLER REPORTING TO TAX & REV. DEPT. R18_HB0098 90.0 LOCAL ELECTION ACT R17_SB0192 89.5 TRANSFER OF LOTTERY FUNDS R17_HB0484 88.7 SCHOOL INDIAN STUDENT NEEDS ASSESSMENTS R17_SB0011 87.4 CHILD EARLY INTERVENTION REIMBURSEMENT BASIS R18_SB0040 94.9 WASTEWATER PROJECT FUNDING ELIGIBILITY Legislation with more horizontal cutting lines position legislators in 2D. A random sample of legislation with cutting line angles greater than 135 degrees or less than 45 degrees is presented below. set.seed(99) angles %>% mutate(Bill_Code = gsub('.$', '', Bill_Code)) %>%
left_join(nml_legislation) %>%
filter (angle > 135 | angle < 45) %>%
sample_n(10) %>%
select(Bill_Code, angle, Bill_Title)%>%
kable(digits = 1, row.names = FALSE)
Bill_Code angle Bill_Title
R17_SB0349 15.5 LIVESTOCK RUNNING AT LARGE
R17_HB0249 18.9 COLLEGE SPECIAL EVENT GROSS RECEIPTS
R17_SB0420 136.7 LOTTERY SCHOLARSHIP GRACE PERIOD
R18_SB0018 28.7 IMPOSITION OF AVIATION LANDING FEES
R17_SB0188 24.6 DISABILITIES STUDENTS LOTTERY SCHOLARSHIPS
R18_HB0079 158.3 THANKSGIVING SATURDAY GROSS RECEIPTS
R17_SB0258 154.6 DECREASE MARIJUANA PENALTIES
R17_SB0046 38.4 E911 SURCHARGES ON COMMUNICATIONS SERVICES
R17_SB0052 8.8 RONALD MCDONALD HOUSE LICENSE PLATES
R18_SB0176 20.0 INCREASE STATE OFFICER COMPENSATION

So, the two (random sub-) sets of legislation provide a super simple perspective on potential political ideologies underlying each dimension of our model. The sample of 1D legislation does in fact seem to speak to the traditional liberal-conservative divide.

The sample of 2D legislation, on the other hand, is a bit of a mixed bag, and one that does not strictly speak to social conservatism (as per K. T. Poole and Rosenthal (2011) in the case of US Congress). But certainly a collection of legislation that does not intuitively align with political affiliation.

#### Individual roll calls & ideal points

Next, we combine the utility of the two functions included in wnomadds to demonstrate how individual roll calls bisect political space. Having extracted cutting line angles, we can now investigate how individual pieces of legislation divide the legislature in different ways. First, we consider (some cherry-picked) roll calls that divide the legislature along 1D.

D1_cuts <- c('R17_HB0484', 'R17_SB0140', 'R17_HJR10',
'R18_SM023', 'R17_SB0155', 'R18_SM003')

sub <- nmlegisdatr::nml_rollcall %>%
filter(Bill_Code %in% D1_cuts) %>%
inner_join(senate_data) %>%
inner_join(nml_legislation)

cut_sub <- subset(with_cuts, Bill_Code %in% paste0(D1_cuts,'P')) %>%
mutate(Bill_Code = gsub('.$', '', Bill_Code)) %>% inner_join(nml_legislation)  Then we plot/facet individual roll calls and cutting lines utilizing some shape/color palettes made available via wnomadds to visualize party affiliation and vote type per legislator. d1 <- sub %>% ggplot(aes(x=coord1D, y=coord2D)) + geom_point(aes(color = Party_Member_Vote, shape= Party_Member_Vote, fill = Party_Member_Vote), size= 1.5) + wnomadds::scale_color_rollcall() + wnomadds::scale_fill_rollcall() + wnomadds::scale_shape_rollcall() + theme(legend.position = 'bottom') + geom_text(aes(label=Representative), size=1.75, check_overlap = TRUE, hjust = "inward", nudge_y = -0.03)+ ggforce::geom_circle (data = circle, aes(x0=x0, y0=y0, r=r), color = 'light gray', inherit.aes=FALSE) + coord_fixed() + geom_segment(data=cut_sub, aes(x = x_1, y = y_1, xend = x_2, yend = y_2)) + geom_segment(data=cut_sub, aes(x = x_2, y = y_2, xend = x_2a, yend = y_2a), arrow = arrow(length = unit(0.2,"cm"))) + geom_segment(data=cut_sub, aes(x = x_1, y = y_1, xend = x_1a, yend = y_1a), arrow = arrow(length = unit(0.2,"cm")))+ geom_text(data=cut_sub, aes(x = .7, y = -1, label = Bill_Code), size=2.25, nudge_y = 0.1, check_overlap = TRUE) + labs(title="Figure 4: Selected 1D cutting lines and roll calls") + facet_wrap(~Bill_Title, labeller = label_wrap_gen(), ncol = 3) + theme(strip.text = element_text(size=7)) So, some nice examples that demonstrate how individual pieces of legislation divide legislators along 1D space and, more specifically, position legislators along the liberal-conservative continuum. Next, we consider (some cherry-picked) roll calls that divide the legislature along 2D. D2_cuts <- c('R18_SB0176','R18_HB0079', 'R18_SB0030', 'R17_SB0055', 'R18_SB0018', 'R17_HB0080') Using the same ggplot pipe from above, we plot roll calls and cutting lines for legislation that crosscuts political affiliation in varying degrees. Roll calls below illustrate a curious faction comprised of the most conservative Republicans and some of the more moderate Democrats, one that collectively votes against more bureaucratic legislation. (Or some more informed explanation.) ## Political space and marijuana Here, we take a quick look at roll calls for legislation related to marijuana. As a political issue, marijuana-friendly legislation is fun, especially for Republican legislators, as it pits social conservatism against small government & libertarianism. An ideological cage-rattler, as it were. Again, we use the same ggplot pipe from above, and consider two pieces of marijuana-related legislation from the first session of NMSL53. weed <-c('R17_HB0527', 'R17_SB0258')  The plot illustrates a fairly clean division among Republicans in the Senate with respect to marijuana-based legislation. Republican State Senators with higher scores on 2D are generally less likely to support legislation friendly to marijuana, ie, social conservatism > small government. The opposite is true for Republican State Senators with lower scores on 2D. Democrats, in contrast, stand united on the issue. The plot sheds some additional light on the political ideology underlying the second dimension, and supports a social conservatism element to 2D. ## A quick geographical perspective Lastly, we investigate a potential relationship between geography and second dimension scores. Using the super convenient tigris package, we obtain a shape file for upper house legislative districts in New Mexico. Then we join our table containing ideal point estimates for each legislator. library(tigris); options(tigris_use_cache = TRUE, tigris_class = "sf") library(leaflet); library(sf) senate_shape <- tigris::state_legislative_districts("New Mexico", house = "upper", cb = TRUE)%>% inner_join(senate_data, by = c('NAME'='District')) %>% st_set_crs('+proj=longlat +datum=WGS84') We plot the 2D scores by upper house legislative district using the leaflet package. As the map illustrates, legislators with lower scores represent districts in the Albuquerque area (where roughly a third of the state’s population resides), as well as districts in the western part of the state. In contrast, legislators with higher 2D scores represent districts on the state’s periphery (north and south), which tend to be more rural districts. pal <- colorNumeric(palette ="Purples", domain = senate_data$coord2D)

senate_shape %>%
leaflet(width="450") %>%
options = providerTileOptions (minZoom = 5, maxZoom = 8)) %>%
fill = TRUE,
stroke = TRUE, weight=1,
fillOpacity = 1,
color="white",
fillColor=~pal(coord2D)) %>%
opacity = 1)