Replication Code and Robustness Checks

The Effects of Shifting Priorities and Capacity on Elected Officials’ Policy Work and Constituency Service: Evidence from a Census of Legislator Requests to U.S. Federal Agencies

Author

Devin Judge-Lord

Code
# NOTE: `replication.r` is the R code extracted from `replication.qmd`---it replicates all results but does not save figures in the /figs/ folder as required to render the manuscript.
# If you are not working in an RStudio project, replication.qmd may still render, 
# but replication.r will likely require a line pointing to the file path where you saved the dataverse files 
# (e.g., `here::i_am(path = "Downloads/dataverse_files")` )

testing = F
verification = T
start_time <- Sys.time()

# directory to store model objects 
if (!dir.exists(here::here("models"))) {dir.create(here::here("models"))}

# directory to store figures
if (!dir.exists(here::here("figs"))) {dir.create(here::here("figs"))}

Packages:

Code
requires <- c("tidyverse", "magrittr", # data wrangling
              "scales", # plot scales 
              "ggrepel",# plot labels
              "here", # file paths 
              "knitr", # document formatting 
              "kableExtra", # for table formatting 
              "modelsummary", # for regression tables 
              "marginaleffects", # for predictions 
              "fixest", # statistical package for fixed effects estimation 
              "ineq", # to calculate GINI coefficients
              # MAY BE REQUIRED FOR QMD RENDERING
              "base64enc",
              "digest",
              "evaluate",
              "glue",
              "highr",
              "htmltools",
              "jsonlite",
              "markdown",
              "mime",
              "rmarkdown",
              "stringi",
              "stringr",
              "xfun",
              "yaml")

to_install <- c(requires %in% rownames(installed.packages()) == FALSE)

if(sum(to_install>0)){
install.packages( requires[to_install], 
                  repos = "https://cloud.r-project.org/" )
}

library(modelsummary)
library(marginaleffects)
library(fixest)
library(tidyverse)
library(magrittr)
library(ineq)
library(kableExtra)
library(ggrepel)
library(scales)
Code
# html table formatting
kablebox <- . %>%  
  head(100) %>%
  knitr::kable() %>% 
  kableExtra::kable_styling() %>% 
  kableExtra::scroll_box(height = "200px")

kablebox_long <- . %>% 
  head(100) %>% 
  knitr::kable() %>% 
  kableExtra::kable_styling() %>% 
  kableExtra::scroll_box(height = "500px")

Theory

Code
# CAPACITY
capacity <- tibble::tibble(
  priority = .2,
  capacity = c(100, NA, NA, NA, NA, 150)
  ) %>% 
  ggplot() +
  aes(x = capacity*priority,
      y = capacity*(1-priority) ,
      label = str_c(capacity*priority, ", ", capacity*(1-priority))) + 
   geom_segment(x = c(0,0,0,0,0,0),
                 xend = c(100, 110, 120,130,140, 150),
                 y = c(100, 110, 120,130,140, 150),
                 yend = c(0,0,0,0,0,0),
              linetype = 1,
              alpha = c(.1,.2,.4,.6,.8,1)) + 
    geom_point(alpha = c(.5, 1,1,1,1,1) ) +
  geom_segment(x = 100*.2, 
           y = 100*.8,
           xend = 148*.2, 
           yend = 148*.8,
           arrow = arrow(type = "closed", length = unit(.1, "in")),
           color = "blue") + 
  annotate("text", 
          label = "50% increase in capacity, maintaining\na 80:20 ratio of constituency service to\npolicy work (80% constituency service)", 
           x = 150*.2, 
           y = 150*.8,
          hjust = -.01,
          vjust = -.1,
          color = "black",
          size = 3) +
  geom_label(x = 0, 
             y = c(100, 110, 120,130,140, 150), 
             label = c("1x", NA, NA, NA, NA, "1.5x"),
             size = 3,
             hjust = .44 ) + 
    scale_y_continuous(breaks = seq(0, 140, by = 20), limits = c(0, 150)) + 
  scale_x_continuous(breaks = seq(0, 140, by = 20), limits = c(0, 150)) + 
  labs(#title = "",
       x = "Volume of policy work",
       y = "Volume of constituency service") + 
  theme(legend.position = "", 
        panel.grid.minor = element_blank() )




# PRIORITY 
priority <- tibble::tibble(
  priority = c(.2, .4),
  capacity = c(100) 
  ) %>% 
  mutate(
    x = capacity*priority,
      y = capacity*(1-priority)
  ) %>% 
  ggplot() +
  aes(x = x,
      y = y ) + 
        geom_segment(x = c(0),
                 xend = c(100),
                 y = c(100),
                 yend = c(0),
              linetype = 1,
              alpha = c(.1)) +
   stat_function(fun =  ~ 4*.x , geom = "line", linetype = 2, alpha = .1) +
     stat_function(fun =  ~ 3.5*.x , geom = "line", linetype = 2, alpha = .2) +
   stat_function(fun =  ~ 3*.x , geom = "line", linetype = 2, alpha = .4) +
   stat_function(fun =  ~ 2.5*.x , geom = "line", linetype = 2, alpha = .6) +
   stat_function(fun =  ~ 2*.x , geom = "line", linetype = 2, alpha = .8) +
   stat_function(fun =  ~ 1.5*.x , geom = "line", linetype = 2, alpha = 1) +
  geom_point(alpha = c(.5,1) ) +
  geom_segment(x = 100*.2, 
           y = 100*.8,
           xend = 100*.4, 
           yend = 100*.6,
           arrow = arrow(type = "closed", length = unit(.1, "in")),
           color = "blue") + 
  annotate("text", 
          label = "Shifting priority toward policy work\nwithout increasing capacity", 
           x = 100*.4, 
           y = 100*.6,
          hjust = -.01,
          vjust = 1.1,
          color = "black",
          size = 3) +
  geom_label(aes(x = 150*c(.24, .66),
                 y = 150 ),
             label = c("80:20", "60:40"),
          size = 3) + 
  scale_y_continuous(breaks = seq(0, 140, by = 20), limits = c(0, 150)) + 
  scale_x_continuous(breaks = seq(0, 140, by = 20), limits = c(0, 150)) + 
  #scale_color_gradient(low = "grey", high = "black") +
  labs(#title = "",
       x = "Volume of policy work",
       y = "Volume of constituency service") +
  theme(legend.position = "",         
        #panel.grid.major = element_blank(),
        panel.grid.minor = element_blank() )



# BOTH  
both <- tibble::tibble(
  priority = c(.2, .4),
  capacity = c(100, 150) 
  ) %>% 
  mutate(
    x = capacity*priority,
      y = capacity*(1-priority)
  ) %>% 
  ggplot() +
  aes(x = x,
      y = y ) + 
      geom_segment(x = c(0, 0),
                 xend = c(100,150),
                 y = c(100, 150),
                 yend = c(0,0),
              linetype = 1,
              alpha = c(.1, 1)) +
   stat_function(fun =  ~ 4*.x , geom = "line", linetype = 2, alpha = .1) +
   stat_function(fun =  ~ 1.5*.x , geom = "line", linetype = 2, alpha = 1) +
  geom_label(aes(x = 150*c(.24, .66),
                 y = 150 ),
             label = c("80:20", "60:40"),
          size = 3) + 
    geom_point(alpha = c(.5,1) ) +
  geom_segment(x = 100*.2, 
           y = 100*.8,
           xend = 148*.4, 
           yend = 149*.6,
           arrow = arrow(type = "closed", length = unit(.1, "in")),
           color = "blue") + 
  annotate("text", 
          label = "Shifting priority toward policy work\n AND increasing capacity by 50%", 
           x = 150*.4, 
           y = 150*.6,
          hjust = .06,
          vjust = -.5,
          color = "black",
          size = 3) +
    geom_label(x = 0, y = c(100, 150), 
             label = c("1x", "1.5x"),
             size = 3,hjust = .44) + 
  scale_y_continuous(breaks = seq(0, 140, by = 20), limits = c(0, 150)) + 
  scale_x_continuous(breaks = seq(0, 140, by = 20), limits = c(0, 150)) + 
  #scale_color_gradient(low = "grey", high = "black") +
  labs(#title = "",
       x = "Volume of policy work",
       y = "Volume of constituency service") + 
  theme(legend.position = "",          
        panel.grid.minor = element_blank() )



# Regions   
more_c <- tibble(x = c(0, 20, 37.5,0),
       y = c(100, 80, 150,150) ) %>% 
  mutate(label = "A")

less_c <- tibble(x = c(0, 20, 0,0),
       y = c(0, 80, 80, 0) ) %>% 
  mutate(label = "D")


more_p <- tibble(x = c(19.8, 37.5, 150, 150),
       y = c(79, 150, 150, 79) ) %>% 
  mutate(label = "B")

less_p <- tibble(x = c(100, 21, 150, 150),
       y = c(0, 79, 79, 0) ) %>% 
  mutate(label = "C")


# REGIONS 
regions <- more_p %>%
 full_join( less_p) %>% #full_join(more_c) %>% 
  group_by(label) %>% 
  mutate(mean_x = mean(x),
         mean_y = mean(y),
         name = label %>% 
           str_replace("B", #"c_{i1}p_{i1} > c_{i2}p_{i2}" #FIXME 
                       "The effect of increasing capacity is large\nenough that levels of constituency service\nare maintained or increase, even as\nelected officials prioritize policy work."
                       ) %>% 
           str_replace("C", "Shifting priorities toward policy\ncauses less constituency service"
           #expression("$c_{i1}p_{i1} > c_{i2}p_{i2}$" #FIXME 
           )
         ) %>% 
  ungroup() %>% 
ggplot() +
  aes(x = x,
      y = y,
      fill = label,
      label = name) + 
  geom_polygon( alpha = .5) + 
    geom_segment(x = 0,
                 xend = 100,
                 y = 100,
                 yend = 0,
              linetype = 1,
              alpha = c(.01)) +
     stat_function(fun =  ~ 4*.x , geom = "line", linetype = 2, alpha = .1) +
  geom_point(x = 20, y = 80, alpha = .5, color = "grey") + 
  scale_fill_grey() +
  scale_y_continuous(breaks = seq(0, 140, by = 20), limits = c(0, 150)) + 
  scale_x_continuous(breaks = seq(0, 140, by = 20), limits = c(0, 150)) + 
  geom_text(aes(x = mean_x, y = mean_y), 
            check_overlap = T, 
            color = "black",
            #parse = T,
          size = 3,
          vjust =.5) + 
  labs(#title = "",
       x = "Volume of policy work",
       y = "Volume of constituency service") + 
  theme(legend.position = "",          
        panel.grid.minor = element_blank() )


capacity  
priority 
both  
regions

Replication Data

Code
load(here::here("data", "corr_counts.Rdata"))
## just in case it was saved as grouped data 
corr_counts %<>% ungroup()

# subset to 2007-2020
corr_counts  %<>% filter(year > 2006, year < 2021)

# New member data 
load(here::here("data", "member_data.Rdata"))

member_data <- member_data |> 
  mutate(
    member = bioname |>
      str_remove(", .*") |>
      str_to_title() |> 
      str_replace("cc", "cC"),
    member_state = paste(member, state_abbrev, sep = " (") |>
      paste0(")"),
    cqlabel = paste0("(", 
                          state_abbrev, 
                          "-", 
                          district_code, 
                          ")") |> 
           str_remove("-0")
    ) 

d <- corr_counts %>% 
  ungroup() %>% 
  mutate(Type = case_when(
    TYPE %in% c(1,2,3) ~ "Constituency\nService", 
    TYPE %in% c(4,5) ~ "Policy"), 
         type = case_when(
            TYPE == 1 ~ "Constituent (individual)",
            TYPE == 2 ~ "Constituent (corporate)",
            TYPE == 3 ~ "Constituent (501c3 or local\n government)",
            TYPE == 4 ~ "Policy (corporate)",
            TYPE == 5 ~ "Policy (general)"
         )) 

type <- d %>% 
  group_by(Type) %>% 
  summarise(n = sum(per_icpsr_chamber_year_agency_type)) %>% 
  ungroup()

percent <- function(x){
  y <- x*100 
  y %<>% round() 
  return(y)}

year_congress<- function(year){
  return(floor((year - 1787)/2))
}

d %<>% mutate(congress = year_congress(year))



dcounts <- d |> left_join(member_data)

Descriptive

We hand-coded data on 611,239 instances of members of Congress contacting federal agencies.

Types of Legislator Requests

84% of the average legislators’ contacts with agencies are about constituency service.

16% of the average legislators’ contacts with agencies are about policy work.


Percents by Type

We classify legislator requests into five overall types.

Code
# paper figure 
type_totals <- d %>% 
  tidyr::drop_na(Type) %>%  
  mutate(total = sum(per_icpsr_chamber_year_agency_type)) %>% 
  group_by(total, Type, type) %>% 
  summarise(nT =  sum(per_icpsr_chamber_year_agency_type)) %>% 
  mutate(percent = paste0(nT, " = ", 100*round(nT/total, 2), "%"),
         type = str_c(type, "\n    ", percent)) %>% 
  ungroup() %>% 
  group_by(Type) %>% 
  mutate(nt = sum(nT),
         percent_t = paste0(100*round(nt/total, 2), "%"))

type_totals %<>% mutate(type = type %>% 
                          # FOR AJPS - remove N and percent 
                          str_remove_all("\n    .*") %>% 
                        # END FOR AJPS 
                          str_replace_all("\\)", "") %>% 
                          str_replace_all(" \\(", ",\n") 
                          
                        ) %>% 
  ungroup()

N <- type_totals$total |> unique()


# horizontal
type_totals %>% 
  ungroup() %>% 
  ggplot() + 
  geom_col(alpha = .7,
           aes(x = type, fill = Type, y = nT) ) + 
  scale_y_continuous(breaks = seq.int(0,500000, by = 100000), 
                     limits = c(0,400000), 
                     labels = scales::label_comma() ) + 
  labs(x = "",
       fill = "",
       y = paste("Number of contacts"),
       title = "") +
  #geom_text(aes(label = Type, x = Type,y = nT) ) + 
  theme(panel.border = element_blank(),
        panel.grid.major.x = element_blank(),
        axis.ticks = element_blank(),
        #axis.text.x.top = element_text(),
        legend.position ="none") + 
  ggpubr::geom_bracket(label.size = 3, # FOR AJPS
    xmin = type_totals$type[1], 
    xmax = type_totals$type[3], 
    y.position = 380000,
    label = paste(type_totals$percent_t[1], "Constituency service" ),
    #hjust = 1,
    vjust = -1
  ) + 
  ggpubr::geom_bracket(label.size = 3, # FOR AJPS
    xmin = type_totals$type[4], xmax = type_totals$type[5], 
    y.position = 85000,
    label = paste(type_totals$percent_t[5], "Policy work"),
    #hjust = 1,
    vjust = -1
  )+ 
  scale_fill_viridis_d(option = "cividis", end = .8, direction = -1)

Code
type_totals |> select(type, percent, percent_t)
# A tibble: 5 × 3
  type                                        percent      percent_t
  <chr>                                       <chr>        <chr>    
1 "Constituent,\n501c3 or local\n government" 37121 = 7%   84%      
2 "Constituent,\ncorporate"                   29651 = 6%   84%      
3 "Constituent,\nindividual"                  362751 = 71% 84%      
4 "Policy,\ncorporate"                        13299 = 3%   16%      
5 "Policy,\ngeneral"                          68207 = 13%  16%      

Percents by chair status

Code
# extra figure 
type_totals <- d %>% 
  tidyr::drop_na(Type) %>%  
  left_join(member_data |> distinct(icpsr, congress, chair)) %>% 
  group_by(chair) %>% 
  mutate(total = sum(per_icpsr_chamber_year_agency_type)) %>% 
  group_by(total, Type, type, chair) %>% 
  summarise(nT =  sum(per_icpsr_chamber_year_agency_type)) %>% 
  mutate(percent = paste0(nT, " = ", 100*round(nT/total, 2), "%"),
         type = str_c(type, "\n    ", percent)) %>% 
  ungroup() %>% 
  group_by(Type, chair) %>% 
  mutate(nt = sum(nT),
         percent_t = paste0(100*round(nt/total, 2), "%"))

type_totals %<>% mutate(type = type %>% 
                          # FOR AJPS - remove N and percent 
                          str_remove_all("\n    .*") %>% 
                        # END FOR AJPS 
                          str_replace_all("\\)", "") %>% 
                          str_replace_all(" \\(", ",\n") 
                          
                        ) %>% 
  ungroup()


# chair
type_totals %>% 
  ungroup() %>% 
  filter(chair == 1) %>% 
  ggplot() + 
  geom_col(alpha = .7,
           aes(x = type, fill = Type, y = nT) ) + 
  scale_y_continuous(breaks = seq.int(0,500000, by = 100000), 
                     limits = c(0,400000), 
                     labels = scales::label_comma() ) + 
  labs(x = "",
       fill = "",
       y = paste("Number of contacts"),
       title = "Chairs only") +
  #geom_text(aes(label = Type, x = Type,y = nT) ) + 
  theme(panel.border = element_blank(),
        panel.grid.major.x = element_blank(),
        axis.ticks = element_blank(),
        #axis.text.x.top = element_text(),
        legend.position ="none") + 
  ggpubr::geom_bracket(label.size = 3, # FOR AJPS
    xmin = type_totals$type[1], 
    xmax = type_totals$type[3], 
    y.position = 380000,
    label = paste(type_totals$percent_t[1], "Constituency service" ),
    #hjust = 1,
    vjust = -1
  ) + 
  ggpubr::geom_bracket(label.size = 3, # FOR AJPS
    xmin = type_totals$type[4], xmax = type_totals$type[5], 
    y.position = 85000,
    label = paste(type_totals$percent_t[5], "Policy work"),
    #hjust = 1,
    vjust = -1
  )+ 
  scale_fill_viridis_d(option = "cividis", end = .8, direction = -1) 

Code
# non-chair 
type_totals %>% 
  ungroup() %>% 
  filter(chair == 0) %>% 
  ggplot() + 
  geom_col(alpha = .7,
           aes(x = type, fill = Type, y = nT) ) + 
  scale_y_continuous(breaks = seq.int(0,500000, by = 100000), 
                     limits = c(0,400000), 
                     labels = scales::label_comma() ) + 
  labs(x = "",
       fill = "",
       y = paste("Number of contacts"),
       title = "Non-chairs only") +
  #geom_text(aes(label = Type, x = Type,y = nT) ) + 
  theme(panel.border = element_blank(),
        panel.grid.major.x = element_blank(),
        axis.ticks = element_blank(),
        #axis.text.x.top = element_text(),
        legend.position ="none") + 
  ggpubr::geom_bracket(label.size = 3, # FOR AJPS
    xmin = type_totals$type[1], 
    xmax = type_totals$type[3], 
    y.position = 380000,
    label = paste(type_totals$percent_t[6], "Constituency service" ),
    #hjust = 1,
    vjust = -1
  ) + 
  ggpubr::geom_bracket(label.size = 3, # FOR AJPS
    xmin = type_totals$type[4], xmax = type_totals$type[5], 
    y.position = 85000,
    label = paste(type_totals$percent_t[9], "Policy work"),
    #hjust = 1,
    vjust = -1
  )+ 
  scale_fill_viridis_d(option = "cividis", end = .8, direction = -1) 

Code
type_totals |> distinct(chair, Type, percent_t)
# A tibble: 4 × 3
  chair Type                    percent_t
  <dbl> <chr>                   <chr>    
1     1 "Constituency\nService" 76%      
2     1 "Policy"                24%      
3     0 "Constituency\nService" 85%      
4     0 "Policy"                15%      

Requests by Percentile

Code
percentiles <- dcounts %>% 
  dplyr::filter(year > 2006, year < 2021) %>% 
  dplyr::group_by(member_state, chamber, year) %>% 
  dplyr::summarise(n = sum(per_icpsr_chamber_year_agency_type)) %>%
  dplyr::group_by(member_state, chamber) %>% 
  dplyr::summarise(mean = mean(n)) %>% 
  dplyr::ungroup() %>% 
  dplyr::group_by(chamber) %>% 
  dplyr::mutate(Percentile = dplyr::ntile(mean, 100),
         rank = dplyr::dense_rank(-mean)) %>% 
  dplyr::arrange(-Percentile) %>% 
  dplyr::select(member_state, Percentile, mean, chamber, rank) %>% 
  dplyr::distinct() 


percentiles |>
  arrange(rank) |>
  kablebox()
member_state Percentile mean chamber rank
Byrd (WV) 100 538.5000 Senate 1
Wolf (VA) 100 380.8750 House 1
Comstock (VA) 100 349.2500 House 2
Nelson (FL) 99 535.2500 Senate 2
Donovan (NY) 100 243.7500 House 3
Webb (VA) 98 532.6667 Senate 3
Curbelo (FL) 100 240.0000 House 4
Schumer (NY) 97 519.7143 Senate 4
Soto (FL) 100 234.7500 House 5
Gillibrand (NY) 96 429.3333 Senate 5
Sestak (PA) 100 224.0000 House 6
Lugar (IN) 95 417.0000 Senate 6
Rodriguez (TX) 100 216.0000 House 7
Mikulski (MD) 94 403.3000 Senate 7
Meng (NY) 100 215.5000 House 8
McCain (AZ) 93 396.4167 Senate 8
McCaul (TX) 99 196.9286 House 9
Warner (VA) 92 396.2857 Senate 9
Graham (FL) 99 190.5000 House 10
Van Hollen (MD) 91 389.5000 Senate 10
Goodlatte (VA) 99 189.1667 House 11
Toomey (PA) 90 386.1000 Senate 11
Stefanik (NY) 99 188.1667 House 12
Rubio (FL) 89 376.8000 Senate 12
Davis (TN) 99 180.2500 House 13
Grassley (IA) 89 366.0714 Senate 13
Beyer (VA) 99 179.8333 House 14
Durbin (IL) 88 358.4286 Senate 14
Macarthur (NJ) 99 174.7500 House 15
Specter (PA) 88 350.5000 Senate 15
O'rourke (TX) 99 171.6667 House 16
Brown (OH) 87 318.7857 Senate 16
Taylor (VA) 98 170.0000 House 17
Obama (IL) 87 318.5000 Senate 17
Visclosky (IN) 98 167.5714 House 18
Isakson (GA) 86 306.7857 Senate 18
Scott (GA) 98 161.7857 House 19
Booker (NJ) 86 301.7500 Senate 19
Carter (GA) 98 161.6667 House 20
Chambliss (GA) 85 301.6250 Senate 20
Faso (NY) 98 160.0000 House 21
Cornyn (TX) 85 296.6429 Senate 21
Van Hollen (MD) 98 158.5000 House 22
Casey (PA) 84 294.0714 Senate 22
Moulton (MA) 98 157.1667 House 23
Hagan (NC) 84 281.3333 Senate 23
Forbes (VA) 98 156.5000 House 24
Manchin (WV) 83 280.6667 Senate 24
Brown (MD) 98 156.2500 House 25
Shelby (AL) 83 261.2143 Senate 25
Costello (PA) 97 151.7500 House 26
Levin (MI) 82 256.8750 Senate 26
Espaillat (NY) 97 151.2500 House 27
Klobuchar (MN) 82 255.8571 Senate 27
Maloney (NY) 97 148.6429 House 28
Duckworth (IL) 81 254.7500 Senate 28
Alexander (LA) 97 146.3750 House 29
Shaheen (NH) 81 253.5833 Senate 29
Khanna (CA) 97 146.2500 House 30
Feinstein (CA) 80 249.8571 Senate 30
Garrett (NJ) 97 145.3000 House 31
Kaine (VA) 80 245.3750 Senate 31
Shimkus (IL) 97 143.7143 House 32
Harris (CA) 79 238.5000 Senate 32
Kingston (GA) 97 143.3750 House 33
Tillis (NC) 79 237.3333 Senate 33
Tenney (NY) 97 143.0000 House 34
Kirk (IL) 78 233.7500 Senate 34
Dent (PA) 96 141.5833 House 35
Burr (NC) 78 231.2857 Senate 35
Owens (NY) 96 141.3333 House 36
Cardin (MD) 77 228.0714 Senate 36
Mast (FL) 96 138.2500 House 37
Kohl (WI) 77 228.0000 Senate 37
Goode (VA) 96 136.5000 House 38
Boxer (CA) 76 227.5000 Senate 38
Diaz-Balart (FL) 96 135.2857 House 39
Feingold (WI) 76 226.0000 Senate 39
Filner (CA) 96 134.8333 House 40
Warren (MA) 75 218.3750 Senate 40
Suozzi (NY) 96 134.2500 House 41
Graham (SC) 75 218.1429 Senate 41
Waxman (CA) 96 133.3750 House 42
Collins (ME) 74 218.0000 Senate 42
Pallone (NJ) 96 132.7857 House 43
Smith (MN) 74 209.0000 Senate 43
Davis (IL) 95 131.8571 House 44
Baldwin (WI) 73 207.0000 Senate 44
Crist (FL) 95 131.2500 House 45
Alexander (TN) 73 202.5714 Senate 45
Gaetz (FL) 95 129.2500 House 46
Landrieu (LA) 72 197.6250 Senate 46
Gomez (CA) 95 128.7500 House 47
Snowe (ME) 72 197.1667 Senate 47
Miller (CA) 95 128.6250 House 48
Rahall (WV) 95 128.6250 House 48
Portman (OH) 71 192.9000 Senate 48
Clarke (NY) 95 128.0714 House 49
Reid (NV) 71 192.5000 Senate 49
Griffith (VA) 95 127.4000 House 50
Code
# percentiles %>%
#     ggplot() +
#     geom_col(aes(x = Percentile, y = mean),
#              color = "grey",
#              width = .000001,
#              fill = "black",
#              position = "dodge") +
#   geom_point(aes(x = Percentile, y = mean), color = "light blue") +
#   geom_text(aes(x = Percentile, y = mean,
#                 label = ifelse(rank <24 | Percentile < 50,
#                                member_state, "")),
#             check_overlap = T,
#                   size = 3,
#                   hjust = "inward") +
#   labs(#title = "Average Legislator Requests per Year by Percentile",
#        x = "Percentile within chamber",
#        y = "Average requests per year") +
#   facet_grid(. ~ chamber , scales = "free_y")
Code
percentiles |>
  group_by(chamber) |>
  summarise(chamber_average = mean(mean)) 
# A tibble: 2 × 2
  chamber chamber_average
  <chr>             <dbl>
1 House              54.3
2 Senate            148. 
Code
percentiles |> arrange(rank) |> head()
# A tibble: 6 × 5
# Groups:   chamber [2]
  member_state  Percentile  mean chamber  rank
  <chr>              <int> <dbl> <chr>   <int>
1 Byrd (WV)            100  538. Senate      1
2 Wolf (VA)            100  381. House       1
3 Comstock (VA)        100  349. House       2
4 Nelson (FL)           99  535. Senate      2
5 Donovan (NY)         100  244. House       3
6 Webb (VA)             98  533. Senate      3
Code
percentiles %>% 
    filter(chamber == "Senate") %>% 
    ggplot() + 
    geom_col(aes(x = Percentile, y = mean), 
             color = "grey", 
             width = .000001,
             fill = "black",
             position = "dodge") + 
  geom_point(aes(x = Percentile, 
                 y = mean), 
             color = "light blue") + 
  geom_text(aes(x = Percentile, 
                y = mean, 
                label = ifelse(rank <24 | Percentile < 50,
                               member_state, ""
                               )
                ), 
            check_overlap = T, 
                  size = 3,
                  hjust = "inward") + 
  # theme_minimal() + 
  labs(#title = "Average Legislator Requests per Year by Percentile",
       x = "Percentile in the Senate",
       y = "Average requests per year") 

Code
percentiles %>% 
  filter(chamber == "House") %>% 
    ggplot() + 
    geom_col(aes(x = Percentile, y = mean), 
             color = "grey", 
             width = .000001,
             fill = "black",
             position = "dodge") + 
  geom_point(aes(x = Percentile, y = mean), color = "light blue") + 
  geom_text(aes(x = Percentile, y = mean, 
                label = ifelse(rank <24 | Percentile < 50,
                               member_state, "")), 
            check_overlap = T, 
                  size = 3,
                  hjust = "inward") + 
  # theme_minimal() + 
  labs(#title = "Average Legislator Requests per Year by Percentile",
       x = "\nPercentile in the House",
       y = "Average requests per year") 

Requests by Year

Code
#counts-per-year

dcounts_per_legislator <- dcounts  %>% 
  group_by(bioname, year) %>% 
  mutate(per_bioname_year = sum(per_icpsr_chamber_year_agency_type)) %>%
  ungroup() 

schumer2013 <- dcounts_per_legislator %>% filter(bioname == "SCHUMER, Charles Ellis (Chuck)",
                   year == 2013) %>% .$per_bioname_year %>% unique()

cruz2013 <- dcounts_per_legislator %>% filter(bioname == "CRUZ, Rafael Edward (Ted)",
                   year == 2013) %>% .$per_bioname_year %>% unique()

mcconnell2013 <- dcounts_per_legislator %>% filter(bioname == "McCONNELL, Addison Mitchell (Mitch)",
                   year == 2013) %>% .$per_bioname_year %>% unique()

# plot a line for all legislator-year counts in the data (this is a lot of data points and thus takes a long time to render)
p <- dcounts_per_legislator %>% 
  filter(year > 2006, year <2018) %>%
  distinct(bioname, per_bioname_year, year, chamber) %>% 
  filter(per_bioname_year > 0) %>% 
  ggplot() + 
  geom_line(aes(x = year, 
                y = per_bioname_year, 
                group = bioname, 
                color = chamber), 
            alpha = .1) +
  geom_line(aes(x = year, 
                group = bioname, 
                color = chamber,
                y = ifelse(bioname == "SCHUMER, Charles Ellis (Chuck)", per_bioname_year, NA))) + 
  geom_label(x = 2013, 
             y = schumer2013, 
             label = "Chuck Schumer") +
  geom_line(aes(x = year, group = bioname, color = chamber,
                y = ifelse(bioname == "CRUZ, Rafael Edward (Ted)", per_bioname_year, NA))) + 
  geom_label(x = 2013, 
             y = cruz2013, 
             label = "Ted Cruz") +
  geom_line(aes(x = year, group = bioname, color = chamber,
                y = ifelse(bioname == "McCONNELL, Addison Mitchell (Mitch)", per_bioname_year, NA))) + 
  geom_label(x = 2013, 
             y = mcconnell2013, 
             label = "Mitch McConnell") +
  labs(x = "",
       color = " ",
       y = paste("Requests per legislator per year"),
       title = "") +
  scale_x_continuous(breaks = unique(dcounts$year)) + 
  scale_color_viridis_d(begin = 0, end = .6, option = "cividis", direction = -1) 

# Non-AJPS formatting 
# p + 
  # theme_minimal() + 
  # theme(axis.text.x = element_text(angle = 30, vjust = 0) ) 


# FOR AJPS 
p +
  theme(panel.background = element_blank(), 
        legend.position = "inside",
        #FIXME NOTE: legend justification requires ggplot2 v3.5 or higher https://stackoverflow.com/questions/78212838/error-when-running-code-examples-from-usmap-r-package
        legend.justification.inside = c(1, 1), 
        #legend.position.inside = c(1, 1),
        axis.ticks = element_blank()
        ) 

Example

Rep. Barbara Comstock worked in Rep. Frank Wolf’s congressional office for five years, described him as a “longtime mentor,” and claimed that Wolf first urged her to run for office. He endorsed and campaigned with her.

Notice several patterns - dip in constituency service when Wolf is no longer running for re-election - further dip with transition - quick rebound as Comstock gets her office in order - inheriting a highly effective office, Comstock is far above average House member

Code
case_study <- function(cq){

  CHAMBER <- filter(member_data, cqlabel == cq) %>% 
    .$chamber %>% .[1]

dcounts %>% 
  group_by(member_state, year, chamber, cqlabel) %>% summarise(n = sum(per_icpsr_chamber_year_agency_type)) %>% 
  ungroup() %>% 
  group_by(chamber) %>% 
  mutate(mean_per_chamber = mean(n) %>% round(0)) %>% 
  filter(cqlabel == cq, year > 2007, year <2017) %>% 
  ggplot() +
  aes(x = year, 
      y = n, 
      fill = member_state %>% str_replace("-.*", "\\)")) + 
  geom_col(alpha = .5, position = "dodge") +
  geom_hline(aes(yintercept = mean_per_chamber), linetype = 2) + 
  geom_text(aes(y = mean_per_chamber), 
            x = 2007.5, 
            label = "Chamber average", check_overlap = T,
            size = 3, vjust = 1.3, hjust = 0) + 
  scale_x_continuous(breaks = seq(2008, 2016, 1)) +
  labs(x = "",
       y = "Number of requests", 
       fill = "") +  # str_c(CHAMBER, "-", cq) %>% str_remove_all("\\(|\\)|NA") ) +
  theme(panel.grid.major.x =  element_blank(),         
        legend.position = "bottom",
        axis.title.x = element_text(margin = unit(c(-5, 0, 0, 0), "mm")),
        # # FOR AJPS
        # axis.text.x = element_text(angle = 45), 
        # # END AJPS 
        panel.grid.minor.x =  element_blank() )+
    scale_fill_viridis_d(begin = 0, end = .6, option = "cividis", direction = -1) 
  
} 
Code
# plot for WI for paper
case_study("(WI)")

Code
case_study("(WI-7)")

Code
# case_study("(VA-10)")
# 
# case_study("(NY-07)")
# 
# case_study("(NY-14)")

# plot all 
# cases <- unique(member_data$cqlabel) #%>% head()

# map(cases, case_study )

Requests by State Population

Code
dcounts %>% 
  filter(chamber == "Senate") %>% 
    group_by(member_state, year, pop2010) %>% 
  summarise(n = sum(per_icpsr_chamber_year_agency_type)) %>% 
    group_by(member_state, pop2010) %>%
  summarise(mean = mean(n)) %>% ungroup() %>% 
    ggplot() + 
    geom_point(aes(x = pop2010, y = mean), color = "light blue") + 
    geom_smooth(aes(x = pop2010, y = mean)) + 
  geom_text_repel(
    aes(
    x = pop2010,
    y = mean, 
    label = ifelse(mean > 390 | log(pop2010) > 17.0 | mean < 100,# & (log(pop2010) > 27 | log(pop2010) < 14), 
                   member_state  ,
                   "")), 
            seed = 44,
            check_overlap = T, 
            min.segment.length = 0,
            size = 3) + 
  labs(#title = "",
       x = "State population (log scale)",
       y = "Average number of requests per year") +
  scale_x_log10(labels = scales::comma) + 
  scale_y_continuous(labels = scales::comma, limits = c(0,500) ) 

Code
##creating the member-level aggregate count variables

# TOTAL counts 
d <- corr_counts |>
  group_by(agency, icpsr, chamber, year) |>
  summarise(perYear = sum(per_icpsr_chamber_year_agency_type)) |> 
  ungroup()

# Constituent counts 
d_con <- corr_counts |>
  subset(TYPE %in% c(1, 2, 3)) |> 
  group_by(agency, icpsr, chamber, year) |>
  summarise(perYear_con = sum(per_icpsr_chamber_year_agency_type))

# Policy counts 
d_policy <- corr_counts |>
  subset(TYPE %in% c(4, 5)) |> 
  group_by(agency, icpsr, chamber, year) |>
  summarise(perYear_pol = sum(per_icpsr_chamber_year_agency_type))

d$perYear_con <- d_con$perYear_con
d$perYear_policy<- d_policy$perYear_pol


## Means for paper
mean_total <- mean(d$perYear)
mean_con <- mean(d$perYear_con)
mean_pol <- mean(d$perYear_policy)

## Ns
n2007_2020 <- d$perYear |> sum()
n2007_2020coded <- sum(d$perYear_con, d$perYear_policy)
n_agency <- d$agency |> unique() |> length()



# Regression table formatting for AJPS 
modelsummary_AJPS <- function(models, notes = "", center_rows = 1, ...){
  modelsummary::modelsummary(models,  
             # Custom sig stars for AJPS 
             stars = c('†' = .1, '*' = .05, '**' = .01), 
             # Align coefficients by decimal for AJPS 
             align = paste0("l", paste0(rep("d", length(models)), collapse = "")), 
             add_rows = rows,               
             coef_map = cm, 
             gof_map = gm, 
             output = "tinytable",
             notes = notes) |>
    # bold header, hline bottom, aligned center
    tinytable::style_tt(i = 0:1, bold = T, line = "b",  align = "c") |>
    # stats aligned center 
    tinytable::style_tt(i = center_rows, align = "c") |> 
    # row labels left
    tinytable::style_tt(j = 1, align = "l")
} 

modelsummary <- modelsummary_AJPS

save(mean_total, mean_con, mean_pol, 
     n_agency, n2007_2020, n2007_2020coded, 
     modelsummary_AJPS,
     file = here::here("data", "means.Rdata"))
Code
n_agency
[1] 92
Code
n2007_2020
[1] 611239
Code
n2007_2020coded
[1] 511029
Code
# make icpsr year variable 
d %<>% mutate( icpsr_year = paste(icpsr, year, sep='_') )

## chamber switchers  in original data (party switchers are not an issue because they net new ICPSR ids)
chamber_switchers  <- d |> 
  distinct(icpsr_year, agency, chamber) |>
  add_count(icpsr_year, agency,  #party, 
            name = "n") |>
  filter(n > 1) |> 
  distinct(icpsr_year, chamber) |> 
  arrange(icpsr_year)

# DROP CHAMBER SWITCHERS
d %<>% filter(!icpsr_year %in% chamber_switchers$icpsr_year)

############################
# add covariates for models #
############################

# congress year converters 
congress_years<- function(congress){
  years<- c(congress*2 + 1787, congress*2 + 1788 )
  return(years)
}

year_congress<- function(year){
  return(floor((year - 1787)/2))
}

# make congress to merge with member data 
d$congress<- year_congress(d$year)

# inspect duplicates 
d <- d |> 
  ungroup() |>
  distinct() |>
  add_count(icpsr, year, agency,  #party, 
            name = "n") 

if(testing){
d |>filter(n > 1) |> distinct(icpsr, year) 
} 

# LEFT join in member data 
d <- d |>  
  ungroup() |> 
  left_join(member_data, 
            by = c('congress', 'icpsr', "chamber") ) 


## count or repeated values (chamber and party switchers)
# note that in the full data, we use the dates of letters to attribute them to the proper party or chamber at the time
# but in yearly counts, we lose this level of detail, leading to undercounts for switchers

# # inspect for duplicates 
if(testing){
d <- d |> 
  ungroup() |>
  distinct() |>
  add_count(icpsr, year, agency,  #party, 
            name = "n") 
  
d |>filter(n > 1) |> distinct(icpsr, year) 
}
Code
# This chunk takes the minimal count data and creates legislator-level and district-level yearly count and ratio data
# These are then used to estimate the models
# the key DVs are
# - perYear = counts per year (member level and district level)
# - ratio = ratio of policy work to constituency service


####################
# TRANSFORMATIONS #  
###################

# tenure in office for experience tests 
d <- d |> 
  group_by(icpsr) |>
  mutate(
    tenure = year - first_year,
    first = ifelse(tenure==0, 1, 0),
    second = ifelse(tenure==1, 1, 0),
    third = ifelse(tenure==2, 1, 0),
    fourth = ifelse(tenure==3, 1, 0),
    fifth = ifelse(tenure==4, 1, 0),
    sixth = ifelse(tenure==5, 1, 0),
    max_year = max(tenure)
  ) 

# two-way fixed effects 
d <- d |> 
  ungroup() |> 
  mutate(
    icpsr_agency = paste(agency, icpsr, sep='_'),
    agency_year = paste(agency, year, sep='_')
  )

# indicator for whether they survived their first election 
d <- d |> 
  mutate(survive = ifelse(
    chamber =='House' & max_year>1 | chamber=='Senate' & max_year>5,
    1, 0)
  )

dcounts_tenure <- d

#########
# RATIO # 
#########  

##  the ratio variable - at the legislator-year level 
## (not the legislator-year-agency level like above)
dcounts_ratio  <- d |>
  ungroup() |> 
  # variables for ratio models 
  group_by(year, icpsr, chamber,
           chair, ranking_minority,  
           majority, presidents_party,
           first, second, third, fourth, fifth, sixth)  |>
  summarise(perCon = sum(perYear_con), 
            perPol = sum(perYear_policy)) |>
  mutate(ratio = perCon/(perCon + perPol))

#### PER DISTRICT COUNTS ######################
d %<>% mutate(decade = case_when(
  year < 2011 ~ '0', 
  year > 2010 ~ '1'))


d %<>% mutate(state_dist = case_when(
  chamber=='Senate'~ paste(state,district_code,  sep='_' ),
  chamber =='House'~ paste(paste(state, district_code, sep='_'), decade, sep='_')
))


dcounts_per_district<- d |>
  group_by(year, state_dist, icpsr, chamber, 
           tenure, first, second, third, fourth, fifth, sixth,
           state) |>
  summarise(perYear = sum(perYear),
            perYear_con = sum(perYear_con),
            perYear_policy = sum(perYear_policy)) |>
  distinct()

# rename first year as "new_member" 
dcounts_per_district %<>% 
  mutate(new_member = first)

Defining Prestige Committees

All of these findings replicate with various interpretations of “prestige” committees. To measure committee prestige, we use a revealed preference approach–what do members think is most valuable–using party rules/norms that limit members to serving on only one of a small number of desirable committees. For House members, the “exclusive” committees are: Appropriations, Energy & Commerce, Financial Services, Rules, and Ways & Means (CRS 2022). For Senators, the exclusive (also called “Super A”) Committees are: Appropriations, Armed Services, Finance, and Foreign Relations (CRS 2024).

Code
# THE BETTER DEFINITION WE USE IN THE FINAL PAPER 
prestige_house <- c(
  "APPROPRIATIONS", #
  # "ARMED SERVICES", #
  #"BUDGET", # 
  "COMMERCE", #
  "ENERGY", #
  "FINANCIAL SERVICES", # 
  "BANKING", # 
  #"FOREIGN"
    #"INTERNATIONAL RELATIONS", # 
  # "JUDICIARY", # 
    "FINANCIAL SERVICES", # 
    "RULES", #
  "WAYS"
  ) %>% paste(collapse  = "|")# 

prestige_senate <- c(
   #"AGRICULTURE", 
   "APPROPRIATIONS", #
  "ARMED SERVICES", #
  #  "BANKING", # 
  #"BUDGET", # 
  #"COMMERCE", #
  #"ENERGY", #
  #  "NATURAL RESOURCES", # 
  #  "ENVIRONMENT", # 
   "FINANCE", #
  "FOREIGN RELATIONS") %>% paste(collapse  = "|")
  #"HOMELAND SECURITY", #
  #"JUDICIARY", # 
  # "INTELLIGENCE",
  #"RULES") #

prestige_house |> str_split("\\|") |> kable(col.names = "House `Exclusive` Prestige Committees (CRS 2022, pg 3)")
House Exclusive Prestige Committees (CRS 2022, pg 3)
APPROPRIATIONS
COMMERCE
ENERGY
FINANCIAL SERVICES
BANKING
FINANCIAL SERVICES
RULES
WAYS
Code
prestige_senate |> str_split("\\|") |> kable(col.names = "Senate `Super A` Prestige Committees  (CRS 2022, pg 3)")
Senate Super A Prestige Committees (CRS 2022, pg 3)
APPROPRIATIONS
ARMED SERVICES
FINANCE
FOREIGN RELATIONS
Code
member_data %<>% ungroup() %>% 
  mutate(prestige = ifelse(
    (chamber == "House" & str_detect(committees, prestige_house) ) | (chamber == "Senate" & str_detect(committees, prestige_senate)), 1, 0)
                    )

# INSPECT 
member_data |> drop_na(prestige) |> count(prestige) |> kable()
prestige n
0 1562
1 2240
Code
# Join prestige coding into main data 
dcounts_tenure %<>% left_join(member_data %>% distinct(icpsr, congress, prestige))

dcounts_ratio %<>% left_join(member_data %>% distinct(icpsr, congress, prestige))

Count Means

Code
# check N
dcounts_tenure %>% tally(perYear, name = "Member-Level Observations") |> kable()
Member-Level Observations
609992
Code
# dcounts_ratio %>% tally(perCon) |> kable()
# dcounts_ratio %>% tally(perPol) |> kable()
dcounts_per_district %>% ungroup %>%  tally(perYear, name = "District-Level Observations") |> kable()
District-Level Observations
609992
Code
# check n per year 
dcounts_tenure %>% filter(year > 2006) %>%  group_by(year) %>% tally(perYear) |> kablebox_long()
year n
2007 23368
2008 35688
2009 44615
2010 48212
2011 49537
2012 43473
2013 43430
2014 45609
2015 41503
2016 43352
2017 32911
2018 134375
2019 12896
2020 11023
Code
# average per member
dcounts_tenure %>% left_join(member_data %>% distinct(bioname, icpsr)) %>%
  group_by(bioname, year) %>% 
  summarise(perYear = sum(perYear)) %>% 
  group_by(bioname) %>% 
  summarise(average_per_year = mean(perYear), average_per_year) |> 
  arrange(-average_per_year) |> 
  kablebox()
bioname average_per_year
BYRD, Robert Carlyle 538.5000
NELSON, Clarence William (Bill) 535.2500
WEBB, James H. (Jim) 532.6667
SCHUMER, Charles Ellis (Chuck) 519.7143
WARNER, Mark 425.6667
LUGAR, Richard Green 417.0000
MIKULSKI, Barbara Ann 403.3000
McCAIN, John Sidney, III 396.4167
GILLIBRAND, Kirsten 387.1667
TOOMEY, Patrick Joseph 386.1000
WOLF, Frank Rudolph 380.8750
RUBIO, Marco 376.8000
GRASSLEY, Charles Ernest 366.0714
DURBIN, Richard Joseph 358.4286
SPECTER, Arlen 350.5000
COMSTOCK, Barbara J. 349.2500
BROWN, Sherrod 318.7857
OBAMA, Barack 318.5000
ISAKSON, Johnny 306.7857
BOOKER, Cory Anthony 301.7500
CHAMBLISS, Saxby 301.6250
CORNYN, John 296.6429
CASEY, Robert (Bob), Jr. 294.0714
HAGAN, Kay 281.3333
MANCHIN, Joe, III 280.6667
SHELBY, Richard C. 261.2143
LEVIN, Carl 256.8750
KLOBUCHAR, Amy 255.8571
SHAHEEN, Jeanne 253.5833
FEINSTEIN, Dianne 249.8571
KAINE, Timothy Michael (Tim) 245.3750
DONOVAN, Daniel M., Jr. 243.7500
KIRK, Mark Steven 243.3750
CURBELO, Carlos 240.0000
HARRIS, Kamala Devi 238.5000
TILLIS, Thomas Roland (Thom) 237.3333
SOTO, Darren Michael 234.7500
BURR, Richard M. 231.2857
CARDIN, Benjamin Louis 228.0714
KOHL, Herbert H. 228.0000
BOXER, Barbara 227.5000
FEINGOLD, Russell Dana 226.0000
VAN HOLLEN, Christopher 224.5000
SESTAK, Joe 224.0000
WARNER, John William 220.0000
WARREN, Elizabeth 218.3750
GRAHAM, Lindsey O. 218.1429
COLLINS, Susan Margaret 218.0000
RODRIGUEZ, Ciro D. 216.0000
MENG, Grace 215.5000
SMITH, Tina 209.0000
ALEXANDER, Lamar 202.5714
LANDRIEU, Mary L. 197.6250
SNOWE, Olympia Jean 197.1667
McCAUL, Michael T. 196.9286
PORTMAN, Robert Jones (Rob) 192.9000
REID, Harry 192.5000
BLUMENTHAL, Richard 190.6000
GRAHAM, Gwendolyn (Gwen) 190.5000
GOODLATTE, Robert William 189.1667
STEFANIK, Elise M 188.1667
HUTCHISON, Kathryn Ann Bailey (Kay) 183.3333
BINGAMAN, Jesse Francis, Jr. (Jeff) 181.5000
MURRAY, Patty 180.2857
BEYER, Donald Sternoff Jr. 179.8333
MacARTHUR, Thomas Charles 174.7500
WYDEN, Ronald Lee 173.2857
McCASKILL, Claire 172.8333
McCONNELL, Addison Mitchell (Mitch) 172.1429
HATCH, Orrin Grant 171.7500
O'ROURKE, Beto 171.6667
DAVIS, Rodney 170.2500
TAYLOR, Scott William 170.0000
ERNST, Joni 168.6667
CORKER, Robert (Bob) 167.9167
VISCLOSKY, Peter 167.5714
CORTEZ MASTO, Catherine Marie 167.5000
STABENOW, Deborah Ann 165.3571
MARTINEZ, Melquiades R. (Mel) 162.2500
CARTER, Buddy 161.6667
DAVIS, Lincoln 160.5000
VITTER, David 160.1000
FASO, John J. 160.0000
HARKIN, Thomas Richard (Tom) 159.7500
MOULTON, Seth 157.1667
DUCKWORTH, Tammy 156.8750
REED, John F. (Jack) 156.7143
FORBES, J. Randy 156.5000
BROWN, Anthony Gregory 156.2500
BAUCUS, Max Sieben 153.0000
COSTELLO, Ryan 151.7500
ESPAILLAT, Adriano J. 151.2500
KERRY, John Forbes 147.0000
ALEXANDER, Rodney 146.3750
KHANNA, Rohit 146.2500
DODD, Christopher John 146.0000
BALDWIN, Tammy 145.7143
GARRETT, Scott 145.3000
STEVENS, Theodore Fulton (Ted) 144.0000
SHIMKUS, John M. 143.7143
Code
# average per member per agency 
dcounts_tenure %>% left_join(member_data %>% select(bioname, icpsr)) %>%
  group_by(bioname) %>% 
  summarise(average_per_year_per_agency = mean(perYear)) |> arrange(-average_per_year_per_agency) |> 
  kablebox()
bioname average_per_year_per_agency
BYRD, Robert Carlyle 9.324675
SCHUMER, Charles Ellis (Chuck) 9.027295
WEBB, James H. (Jim) 8.732240
NELSON, Clarence William (Bill) 8.298450
WARNER, Mark 7.286733
LUGAR, Richard Green 6.836066
GILLIBRAND, Kirsten 6.832353
TOOMEY, Patrick Joseph 6.714783
RUBIO, Marco 6.553044
HARRIS, Kamala Devi 6.489796
SOTO, Darren Michael 6.387755
GRASSLEY, Charles Ernest 6.358561
DURBIN, Richard Joseph 6.225807
McCAIN, John Sidney, III 6.145995
MIKULSKI, Barbara Ann 6.119879
OBAMA, Barack 6.066667
WOLF, Frank Rudolph 5.998031
SMITH, Tina 5.687075
BROWN, Sherrod 5.537221
BOOKER, Cory Anthony 5.486364
ISAKSON, Johnny 5.328784
COMSTOCK, Barbara J. 5.251880
CORNYN, John 5.152605
CASEY, Robert (Bob), Jr. 5.107940
MANCHIN, Joe, III 4.804565
TILLIS, Thomas Roland (Thom) 4.778524
CHAMBLISS, Saxby 4.750000
CORTEZ MASTO, Catherine Marie 4.557823
SHELBY, Richard C. 4.537221
KIM, Andy 4.500000
KAINE, Timothy Michael (Tim) 4.461364
KLOBUCHAR, Amy 4.444169
SHAHEEN, Jeanne 4.340941
FEINSTEIN, Dianne 4.339950
BROWN, Anthony Gregory 4.251701
WARNER, John William 4.190476
HAGAN, Kay 4.188586
ESPAILLAT, Adriano J. 4.115646
LEVIN, Carl 4.045276
SPECTER, Arlen 4.028912
BURR, Richard M. 4.017370
KHANNA, Rohit 3.979592
WARREN, Elizabeth 3.970455
CARDIN, Benjamin Louis 3.961539
MENG, Grace 3.918182
FEINGOLD, Russell Dana 3.913420
VAN HOLLEN, Christopher 3.899504
SESTAK, Joe 3.878788
GRAHAM, Lindsey O. 3.789082
STEFANIK, Elise M 3.788591
COLLINS, Susan Margaret 3.786601
MAST, Brian Jeffery 3.761905
RODRIGUEZ, Ciro D. 3.740260
KOHL, Herbert H. 3.737705
DONOVAN, Daniel M., Jr. 3.665414
SUOZZI, Thomas 3.653061
KIRK, Mark Steven 3.652908
BEYER, Donald Sternoff Jr. 3.620805
CURBELO, Carlos 3.609023
CRIST, Charlie Joseph Jr. 3.571429
MALINOWSKI, Tomaz 3.531250
ALEXANDER, Lamar 3.518610
GAETZ, Matthew L. II 3.517007
GOMEZ, Jimmy 3.503401
McBATH, Lucy 3.468750
BOXER, Barbara 3.452200
McCAUL, Michael T. 3.420596
ERNST, Joni 3.395973
PORTMAN, Robert Jones (Rob) 3.354783
BLUMENTHAL, Richard 3.314783
CARTER, Buddy 3.255034
PANETTA, James Varni 3.251701
SNOWE, Olympia Jean 3.232240
JAYAPAL, Pramila 3.231292
MOULTON, Seth 3.164429
RASKIN, Jamie Ben 3.156463
MURRAY, Patty 3.131514
LANDRIEU, Mary L. 3.112205
DAVIS, Rodney 3.095455
GOTTHEIMER, Josh S. 3.034014
GONZALEZ, Vicente Jr. 3.027211
WYDEN, Ronald Lee 3.009926
HUTCHISON, Kathryn Ann Bailey (Kay) 3.005464
McCONNELL, Addison Mitchell (Mitch) 2.990074
BINGAMAN, Jesse Francis, Jr. (Jeff) 2.975410
TAYLOR, Nicholas 2.968750
TAYLOR, Scott William 2.956522
GOODLATTE, Robert William 2.932816
REID, Harry 2.921093
VISCLOSKY, Peter 2.910670
BRAUN, Michael 2.906250
STABENOW, Deborah Ann 2.872208
DUCKWORTH, Tammy 2.852273
TRAHAN, Lori 2.843750
KRISHNAMOORTHI, S. Raja 2.816327
ARRINGTON, Jodey Cook 2.809524
MARTINEZ, Melquiades R. (Mel) 2.809524
FASO, John J. 2.782609
DAVIS, Lincoln 2.779221
STEVENS, Theodore Fulton (Ted) 2.742857
Code
# per member per agency 
dcounts_tenure %>% left_join(member_data %>% distinct(bioname, icpsr)) %>%
  group_by(bioname, agency) %>% 
  summarise(per_year_per_agency = perYear) |> arrange(-per_year_per_agency) |> 
  kablebox()
bioname agency per_year_per_agency
DURBIN, Richard Joseph DHS_USCIS 1481
MENG, Grace DHS_USCIS 1450
RUBIO, Marco DHS_USCIS 1431
CLARKE, Yvette Diane DHS_USCIS 1373
KLOBUCHAR, Amy DHS_USCIS 1335
DIAZ-BALART, Mario DHS_USCIS 1209
PORTMAN, Robert Jones (Rob) DHS_USCIS 1084
GILLIBRAND, Kirsten DHS_USCIS 1042
SCHUMER, Charles Ellis (Chuck) DHS_USCIS 1038
ISAKSON, Johnny DHS_USCIS 1031
WARREN, Elizabeth DHS_USCIS 947
SHAHEEN, Jeanne DHS_USCIS 935
TOOMEY, Patrick Joseph DHS_USCIS 917
PASCRELL, William J., Jr. DHS_USCIS 912
TILLIS, Thomas Roland (Thom) DHS_USCIS 891
BROWN, Sherrod DHS_USCIS 866
JEFFRIES, Hakeem DHS_USCIS 860
DONOVAN, Daniel M., Jr. DHS_USCIS 856
GUTIÉRREZ, Luis V. DHS_USCIS 854
HARRIS, Kamala Devi DHS_USCIS 833
NELSON, Clarence William (Bill) DHS_USCIS 833
SERRANO, José E. DHS_USCIS 818
VAN HOLLEN, Christopher DHS_USCIS 801
VELÁZQUEZ, Nydia M. DHS_USCIS 793
MEEKS, Gregory W. DHS_USCIS 772
KAINE, Timothy Michael (Tim) DHS_USCIS 768
MURPHY, Christopher DHS_USCIS 768
SMITH, Tina DHS_USCIS 758
CARDIN, Benjamin Louis DHS_USCIS 746
CASEY, Robert (Bob), Jr. DHS_USCIS 744
CURBELO, Carlos DHS_USCIS 740
CORNYN, John DHS_USCIS 731
CONNOLLY, Gerald E. (Gerry) DHS_USCIS 706
CROWLEY, Joseph DHS_USCIS 699
PETERS, Gary C. DHS_USCIS 650
BOOKER, Cory Anthony DHS_USCIS 639
WYDEN, Ronald Lee DHS_USCIS 612
WATSON COLEMAN, Bonnie DHS_USCIS 590
NADLER, Jerrold Lewis DHS_USCIS 573
REED, John F. (Jack) DHS_USCIS 571
BURR, Richard M. DHS_USCIS 570
CORTEZ MASTO, Catherine Marie DHS_USCIS 568
CHAMBLISS, Saxby VA 562
COMSTOCK, Barbara J. DHS_USCIS 559
DUCKWORTH, Tammy DHS_USCIS 559
MALONEY, Carolyn Bosher DHS_USCIS 556
SIRES, Albio DHS_USCIS 554
KHANNA, Rohit DHS_USCIS 547
CORKER, Robert (Bob) DHS_USCIS 545
WOODALL, Rob DHS_USCIS 541
COOPER, James Hayes Shofner DHS_USCIS 534
HASTINGS, Alcee Lamar DHS_USCIS 531
ESPAILLAT, Adriano J. DHS_USCIS 526
O'ROURKE, Beto DHS_USCIS 523
WARNER, Mark DHHS_CMS 523
SCHNEIDER, Brad DHS_USCIS 508
ALEXANDER, Lamar DHS_USCIS 501
ESHOO, Anna Georges DHS_USCIS 499
STABENOW, Deborah Ann DHS_USCIS 499
GOMEZ, Jimmy DHS_USCIS 498
BEYER, Donald Sternoff Jr. DHS_USCIS 489
DONNELLY, Joe DHS_USCIS 487
ROS-LEHTINEN, Ileana DHS_USCIS 483
HATCH, Orrin Grant DHS_USCIS 482
McCAIN, John Sidney, III VA 481
PINGREE, Chellie DHS_USCIS 471
GRAHAM, Lindsey O. DHS_USCIS 465
ENGEL, Eliot Lance DHS_USCIS 464
MARKEY, Edward John DHS_USCIS 452
VISCLOSKY, Peter DHS_USCIS 446
LOWEY, Nita M. DHS_USCIS 429
LIEU, Ted DHS_USCIS 427
LANKFORD, James DHS_USCIS 426
MOULTON, Seth DHS_USCIS 422
SCHAKOWSKY, Janice D. DHS_USCIS 421
SCHIFF, Adam DHS_USCIS 413
GALLEGO, Ruben DHS_USCIS 412
McCONNELL, Addison Mitchell (Mitch) DHS_USCIS 411
LEWIS, John R. DHS_USCIS 406
MENENDEZ, Robert DHS_USCIS 405
BENNET, Michael F. DHS_USCIS 392
JOHNSON, Hank DHS_USCIS 389
PRICE, David Eugene DHS_USCIS 389
PELOSI, Nancy DHS_USCIS 388
DELANEY, John DHS_USCIS 387
BARR, Garland H. (Andy) IV DHS_USCIS 384
CAPUANO, Michael Everett DHS_USCIS 384
DAVIS, Rodney DHS_USCIS 384
ADAMS, Alma DHS_USCIS 383
McCAIN, John Sidney, III VA 380
CANTWELL, Maria E. DHS_USCIS 374
PALLONE, Frank, Jr. DHS_USCIS 372
HEITKAMP, Mary Kathryn (Heidi) DHS_USCIS 369
PERDUE, David Alfred, Jr. DHS_USCIS 368
MANCHIN, Joe, III VA 365
SPEIER, Karen Lorraine Jacqueline (Jackie) DHS_USCIS 365
COLLINS, Susan Margaret DHS_USCIS 356
KINZINGER, Adam DHS_USCIS 355
BLUMENTHAL, Richard DHS_USCIS 353
FRELINGHUYSEN, Rodney P. DHS_USCIS 348
Code
# mean 
perYear_mean1 <- dcounts_tenure$perYear |> mean()
perYear_mean_total <- dcounts_tenure |> 
  group_by(icpsr, year) |> 
  transmute(perYearTotal = sum(perYear))
perYear_mean_total <- mean(perYear_mean_total$perYearTotal) |> round()

perYear_mean_policy <- dcounts_tenure |> 
  group_by(icpsr, year) |> 
  transmute(perYear = sum(perYear_policy))
perYear_mean_policy <- mean(perYear_mean_policy$perYear) |> round()

perYear_mean_con <- dcounts_tenure |> 
  group_by(icpsr, year) |> 
  transmute(perYear = sum(perYear_con))
perYear_mean_con <- mean(perYear_mean_con$perYear) |> round()

# Better variable names for tables
dcounts_tenure %<>% 
  mutate(Legislator = icpsr,
         Legislator_x_Agency = icpsr_agency,
         Year_x_Agency = agency_year)

# total per agency
dcounts_tenure %>% group_by(agency) %>% summarise(n = perYear %>% sum()) %>% arrange(-n) %>% kablebox()
agency n
DHS_USCIS 115887
VA 90626
DHHS_CMS 68435
HUD 32064
DOL_SOL 26781
DHS 24941
USPS 17084
FTC 12859
DHHS_FDA 12470
DOT_FAA 12249
Treasury_Fiscal 11579
Treasury_IRS 10977
DOL_OWCP 10928
EPA 10859
DHHS_HRSA 10576
DOL_EBSA 10021
NARA 9836
DOT_FHWA 9198
DOL_ETA 8938
DHHS_CDC 8547
DHHS_NIH 7397
EOP_USTR 6982
FCC 6059
USDA 4958
ED 4572
SBA 4544
DOD_NAVY 4475
SSA 4364
DOI_NPS 4248
FHFA 2844
NASA 2772
DOT_FRA 2696
DOE_FERC 2563
USDA_RD 2484
RRB 2324
DOT_FTA 2168
DOD_OSDJS 1906
DOL_OSHA 1876
DOJ_CIV 1797
DOD_OIG 1654
DOC_OS 1484
Amtrak 1427
DOI_BOEM 1398
Treasury_OCC 1267
DHHS_IHS 1219
DOL_VETS 992
DOL_MSHA 943
DOC_EDA 925
CNCS 831
EEOC 823
DHHS_ACF 690
DOD_DFAS 688
DOJ_EOIR 683
USDA_FS 657
USDA_RMA 654
DHS_ICE 642
NLRB 634
DOJ_ENRD 602
USDA_NRCS 551
DOL_OFCCP 533
DOD_DeCA 531
DOT_PHMSA 477
DOL_OASAM 397
PRC 360
NCUA 349
DOC_IOS 339
DOD_USACE 326
DOC_NIST 262
EOP_CEQ 234
DOD_DLA_Aviation 224
DOI_USGS 195
USDA_NIFA 121
DOC_NTIA 116
DHHS_ACL 112
DOI_BSEE 88
TVA 82
ABMC 78
DOI_SOL 77
USDA_NASS 77
FCA 53
DOC_OCPA 52
DOC_NOAA 50
DOI_OSMRE 47
USDA_ERS 42
CSOSA 32
DOL_OCFO 22
VA_CEM 20
DOT_SLSDC 17
NCPC 12
DOC_MBDA 9
DOI_NIGC 6
NWTRB 4
Code
# total per legislator-agency pair 
dcounts_tenure %>% group_by(Legislator_x_Agency) %>% summarise(n = perYear %>% sum()) %>% arrange(-n) %>% kablebox()
Legislator_x_Agency n
VA_15039 2158
VA_40915 1694
VA_94659 1691
VA_14858 1654
DHHS_CMS_40909 1579
DHS_USCIS_15021 1481
DHS_USCIS_21342 1450
DHS_USCIS_41102 1431
DHS_USCIS_20733 1373
DHS_USCIS_40700 1335
VA_29718 1297
VA_20530 1281
VA_29935 1210
DHS_USCIS_20316 1209
DHS_USCIS_29386 1084
VA_14440 1070
DHS_14858 1058
VA_15021 1045
DHS_USCIS_20735 1042
DHS_USCIS_14858 1038
DHS_USCIS_29909 1031
VA_29512 1029
VA_14869 949
DHS_USCIS_41301 947
DHS_USCIS_40906 935
VA_20330 918
DHS_USCIS_29935 917
VA_29566 915
DHS_USCIS_29741 912
DHS_USCIS_41504 892
DHS_USCIS_29389 866
DHS_USCIS_21343 860
DHS_USCIS_21560 856
DHS_USCIS_29348 854
DHHS_CMS_20336 853
DHS_USCIS_14651 833
DHS_USCIS_41701 833
DHS_USCIS_29134 818
DHS_USCIS_20330 801
VA_40305 796
DHS_USCIS_29378 793
DHHS_CMS_14226 789
DHS_USCIS_29776 772
DHS_USCIS_20707 768
DHS_USCIS_41305 768
DHS_USCIS_41706 758
DHS_USCIS_15408 746
DHS_USCIS_40703 744
DHS_USCIS_21512 740
DHS_USCIS_40305 731
VA_15408 726
DHHS_CMS_14921 719
DHS_USCIS_20952 706
DHS_USCIS_29925 699
HUD_14651 690
VA_21328 686
DHHS_CMS_14506 684
NARA_41308 678
DHS_USCIS_20923 650
VA_14226 640
DHS_USCIS_41308 639
DHHS_CMS_14503 633
DHHS_CMS_14440 621
DHS_USCIS_14871 612
DHS_USCIS_21538 590
DHHS_CMS_29548 577
VA_1366 575
DHS_USCIS_29377 573
DHS_USCIS_29142 571
DHS_USCIS_29548 570
DHS_USCIS_41700 568
DHS_USCIS_21325 559
DHS_USCIS_21555 559
DHS_USCIS_29379 556
VA_40706 556
DHS_USCIS_20542 554
DHS_USCIS_21728 547
DHS_USCIS_40705 545
HUD_49300 542
DHHS_CMS_41102 541
DHS_USCIS_21122 541
HUD_29909 539
VA_21746 539
NARA_21111 537
DHS_USCIS_15019 534
DHHS_CMS_94659 533
VA_41502 533
DHS_15000 532
DHS_USCIS_29337 531
DHS_40706 526
DHS_USCIS_21715 526
HUD_20735 525
DHS_USCIS_21361 523
VA_29771 512
DHS_USCIS_21326 508
DHS_USCIS_40304 501
DHS_USCIS_29312 499
DHS_USCIS_29732 499
DHS_USCIS_21754 498
VA_29909 496

Values for Predictions

The most frequent legislator agency pair: McCain x VA with 2159 letters!

The average is 84 letters per legislator per year, 1.38 letters per agency per year. For predictions, we need an average member-year-agency triad. The first table below shows the members who averaged 84, entered Congress between 2007 and 2011, were observed in our data for at least seven years, and most often submitted exactly one letter to an agency (the closest integer 84 ). For realism, this person would ideally become a chair at some point in our data.

Gini coefficients

Code
gini <- dcounts_tenure |> 
  ungroup() |> 
  group_by(year, chamber) |> 
  summarise(gini = round(ineq::Gini(perYear),2)) |>
  arrange(- year) |> 
  filter(year > 2006) 

gini|> 
  kablebox()
year chamber gini
2020 House 0.91
2020 Senate 0.88
2019 House 0.94
2019 Senate 0.90
2018 House 0.98
2018 Senate 0.97
2017 House 0.91
2017 Senate 0.87
2016 House 0.90
2016 Senate 0.86
2015 House 0.90
2015 Senate 0.85
2014 House 0.89
2014 Senate 0.86
2013 House 0.89
2013 Senate 0.86
2012 House 0.89
2012 Senate 0.84
2011 House 0.87
2011 Senate 0.84
2010 House 0.88
2010 Senate 0.85
2009 House 0.88
2009 Senate 0.87
2008 House 0.90
2008 Senate 0.87
2007 House 0.89
2007 Senate 0.83
Code
gini |> 
  ggplot() +
  aes( x = year, y = gini, color = chamber) + 
  geom_point() + 
  geom_smooth(method = "lm")

Code
gini2 <- dcounts_tenure |> 
  filter(perYear > 0) |> 
  ungroup() |> 
  group_by(year, chamber) |> 
  summarise(gini = round(ineq::Gini(perYear),2)) |>
  arrange(- year) |> 
  filter(year > 2006) 

gini2|> 
  kablebox()
year chamber gini
2020 House 0.62
2020 Senate 0.61
2019 House 0.66
2019 Senate 0.70
2018 House 0.87
2018 Senate 0.90
2017 House 0.56
2017 Senate 0.64
2016 House 0.56
2016 Senate 0.63
2015 House 0.55
2015 Senate 0.63
2014 House 0.55
2014 Senate 0.64
2013 House 0.53
2013 Senate 0.63
2012 House 0.54
2012 Senate 0.63
2011 House 0.53
2011 Senate 0.64
2010 House 0.56
2010 Senate 0.65
2009 House 0.54
2009 Senate 0.66
2008 House 0.54
2008 Senate 0.67
2007 House 0.47
2007 Senate 0.59
Code
gini2 |> 
  ggplot() +
  aes( x = year, y = gini, color = chamber) + 
  geom_point() + 
  geom_smooth(method = "lm")

Code
gini2 %<>% filter(year > 2017)

min(gini2$gini)
[1] 0.61
Code
max(gini2$gini)
[1] 0.9

In 2023, Colombia had the highest income inequality Gini coefficient at .55.

https://ourworldindata.org/what-is-the-gini-coefficient

Code
# helper functions to plot predicted values
ggchair <- function(predicted = predicted) {
  
  predicted$chair %<>% str_replace("0", " Not\nchair") %>% str_replace("1", "Chair")
  
  predicted$presidents_party %<>% str_replace("0", "\nNot president's\nparty") %>% str_replace("1", "\nPresident's\nparty")

predicted %>%  
  ggplot() + 
  aes(x = presidents_party, 
      y = estimate, # predicted, 
      ymin = conf.low,# predicted - 1.96*std.error,
      ymax = conf.high, #predicted + 1.96*std.error,
      fill = chair,
      shape = chair,
      color = chair,
      label = chair,
      ) + 
      geom_text_repel(direction = "y", size = 3, 
                      hjust= -.25, 
                      min.segment.length = Inf, 
                      force = 2,
                      vjust = 0, 
                      check_overlap = T) + 
  geom_pointrange(position =  position_dodge(width = -.1)  )  + 
  scale_fill_viridis_d(begin = 0, end = .6, option = "cividis") +
  scale_color_viridis_d(begin = 0, end = .6, option = "cividis") +
  theme(legend.position = "none",
        axis.ticks.x = element_blank())
}


ggtenure <- function(predicted = predicted) {
  
predicted %<>%
  # drop estimates from impossible values
      group_by(rowid, 
               #predicted, std.error, 
               estimate, conf.low, conf.high,
               chair) %>% 
  mutate(sum = sum(first, second, third, fourth, fifth, sixth),
         more = ifelse(sum == 0, 1,0)) %>% 
    filter(sum < 2) %>% 
    pivot_longer(cols = c("first", "second", "third", "fourth", "fifth", "sixth", "more")) %>% 
    select(name, value) %>% 
    filter(value == 1) %>% 
    # clean up for presentation
    mutate(year_in_congress = name %>% 
             str_replace("more", "7\nor more") %>% 
             str_replace("sixth", "6") %>% 
             str_replace("fifth", "5") %>% 
             str_replace("fourth", "4") %>% 
             str_replace("third", "3") %>% 
             str_replace("second", "2") %>% 
             str_replace("first", "1") 
             ) %>% 
    ungroup()
  
# Ideally, we could plot against data, but there is so much variation that you can no longer distinguish differences in predicted values 
# predicted %<>% full_join(dcounts_tenure2 %>% mutate(predicted = NA))
  
predicted %<>% filter(chair == 0 | name %in% c("sixth", "more"))

  predicted$chair %<>% str_replace("0", " Not\nchair") %>% str_replace("1", "Chair")
  

predicted %>%  
  ungroup() %>% 
  ggplot() + 
  aes(x = year_in_congress, 
            y = estimate, # predicted, 
      ymin = conf.low,# predicted - 1.96*std.error,
      ymax = conf.high, #predicted + 1.96*std.error,
      shape = chair,
      color = chair,
      label = ifelse(year_in_congress == "6", chair, NA) ) + 
    geom_text_repel(direction = "y", size = 3,
                    min.segment.length = Inf,
                    hjust= -.15, 
                    check_overlap = T) + 
  geom_pointrange(position =  position_dodge(width = -.3)  )  + 
  scale_fill_viridis_d(begin = 0, end = .6, option = "cividis") +
  scale_color_viridis_d(begin = 0, end = .6, option = "cividis")  +
  theme(legend.position = "none")
}

Model Results: Counts per member per year

Clustering standard errors at the legislator level:

“Clustering on the panel variable produces an estimator of the VCE that is robust to cross-sectional heteroskedasticity and within-panel (serial) correlation that is asymptotically equivalent to that proposed by Arellano (1987)”

We use fixest::feglm here and replicated the main models using xtreg in Replication.do. See more about how robust (clustered) standard errors with fixed effects are calculated by fixest here and by xtreg here.

Code
# Coef Map
cm = c("chair" = "Committee Chair",
       "ranking_minority" = "Ranking Member",
       "prestigeR2" = "Prestige Committee (R2)",
       "prestigeOLD" = "Prestige Committee (Old)",
       "prestige" = "Prestige Committee",
       "new_member" = "New Member", 
       "new_senator" = " New Senator in Delegation",
       "new_one" = " New Member in Delegation",
       "new_proportion" = " New Proportion in Delegation",
       "new_member:same_party" = " New Member x Same Party", 
       "first" = "First Year",
       "second" = "Second Year",
       "third" = "Third Year",
       "fourth" = "Fourth Year",
       "fifth" = "Fifth Year",
       "sixth" = "Sixth Year",
       "same_party:second" = "Second Year x Same Party",
       "same_party:third" = "Third Year x Same Party",
       "same_party:fourth" = "Fourth Year x Same Party",
       "same_party:fifth" = "Fifth Year x Same Party",
       "same_party:sixth" = "Sixth Year x Same Party",
       "same_party" = "Same Party",
       "majority" = "Majority",
       "presidents_party" = "President's party",
       "Legislator" = "Legislator", 
       "Agency" = "Agency",
       "Num.Obs." = "Observations"
       )

# FORMATTING FOR AJPS 
cmAJPS <- cm |> str_to_sentence()
names(cmAJPS) <- names(cm)
cm <- cmAJPS
# END FORMATTING FOR AJPS 

# set fixed effects mapping 
setFixest_dict(cm)

format_n <- function(x) format(round(x, 3), big.mark=",") # this works
f <- function(x) stringr::str_replace(x, "[A-z]", "✓") #FIXME not sure why this is not working

gm <- list(
  list("raw" = "nobs", "clean" = "Observations", "fmt" = format_n),
    list("raw" = "FE: Year_x_Agency", "clean" = "Year x agency fixed effects", "fmt" = f),
      list("raw" = "FE: Legislator_x_Agency", "clean" = "Legislator x agency fixed effects", "fmt" = f),
  list("raw" = "FE: icpsr_agency", "clean" = "Legislator-agency fixed effects", "fmt" = f),
       list("raw" = "FE: District", "clean" = "District fixed effects", "fmt" = f),
       list("raw" = "FE: Year", "clean" = "Year fixed effects", "fmt" = f),
       list("raw" = "FE: Legislator.*x.*Agency", "clean" = "Legislator x agency fixed effects", "fmt" = f),
        list("raw" = "FE: Year.*x.*Agency", "clean" = "Year x agency fixed effects", "fmt" = f),
       list("raw" = "FE: Legislator", "clean" = "Legislator fixed effects", "fmt" = f)
  )

Total Letters

Member-level Coefficient Plots

  • Figures: figs/m-total-[1:4].png
Code
# paper table 2
# Model 1 
# cross-sectional 
m_total_cross <-feglm (perYear ~ 
                         chair + ranking_minority + prestige + 
                    first + second + third + fourth + fifth + sixth + 
                    majority + presidents_party | Year_x_Agency,
                    cluster = "Legislator", 
           data = dcounts_tenure)

if(testing){modelsummary::modelsummary(m_total_cross)}

coefplot(m_total_cross, horiz = T, drop = "(Intercept)") 



# Model 2
m_total_dnd <-feglm (perYear ~ 
                       chair + ranking_minority + prestige + 
                    first + second + third + fourth + fifth + sixth + 
                    majority + presidents_party | Year_x_Agency + Legislator_x_Agency, 
                    cluster = "Legislator",
                    # ALT vcov = hetero ~ ssc(cluster.adj = TRUE),
           data = dcounts_tenure)

if(testing){modelsummary::modelsummary(m_total_dnd)}

coefplot(m_total_dnd, horiz = T) 


# 3
m_total_2nd <-feglm (perYear ~ 
                       chair + ranking_minority + prestige + 
                    first + second + third + fourth + fifth + sixth + 
                    majority + presidents_party | Year_x_Agency + Legislator_x_Agency, 
                    cluster = "Legislator", 
           data = dcounts_tenure %>% filter(survive == 1))

if(testing){modelsummary::modelsummary(m_total_2nd)}
coefplot(m_total_2nd, horiz = T) 


# 4
m_logtotal_dnd <-feglm (log(perYear + 1) ~ 
                          chair + ranking_minority + prestige + 
                    first + second + third + fourth + fifth + sixth + 
                    majority + presidents_party | Year_x_Agency + Legislator_x_Agency, 
                    cluster = "Legislator", 
           data = dcounts_tenure)

if(testing){modelsummary::modelsummary(m_logtotal_dnd)}

coefplot(m_logtotal_dnd, horiz = T) 

1

2

3

4

m-total-[1:4].png

Total Letters Table

  • Models: models/models_total.Rdata
Code
models_total <- list(
  "(1)" = m_total_cross,
  "(2)" = m_total_dnd,
  "(3)" =  m_total_2nd,
  "(4)" = m_logtotal_dnd
)

rows <- tibble(
  term = c("Dependent variable", 
           "Majority",
           "President's party",
           "All legislators", 
           "Served at least 2nd term"),
  `(1)` = c("Count", "✓", "✓", "✓", ""),
  `(2)` =c("Count", "✓", "✓", "✓", ""),
  `(3)` = c("Count", "✓","✓","", "✓"),
  `(4)` = c("Log(Count+1)","✓","✓", "✓", "") 
)

rows <- tibble(
  term = c("Dependent variable", 
           #"Majority",
           #"President's party",
           "All legislators", 
           "Served at least 2nd term"),
  `(1)` = c("Count", "✓", ""),
  `(2)` =c("Count", "✓", ""),
  `(3)` = c("Count","", "✓"),
  `(4)` = c("Log(Count+1)", "✓", "") 
)

attr(rows, 'position') <- c(0, 20,21,22,23)

attr(rows, 'position') <- c(0, 24,25)


# HTML
modelsummary(models_total,
             notes = list("Robust standard errors in parentheses, clustered by legislator.",
                          "This table shows estimates of the effect of institutional power on levels of constituency service. All coefficients represent the average additional requests per year per agency; per legislator, per year effects are simply these coefficients times the number of agencies in the data.")) 
(1) (2) (3) (4)
† p < 0.1, * p < 0.05, ** p < 0.01
Robust standard errors in parentheses, clustered by legislator.
This table shows estimates of the effect of institutional power on levels of constituency service. All coefficients represent the average additional requests per year per agency; per legislator, per year effects are simply these coefficients times the number of agencies in the data.
Dependent variable Count Count Count Log(Count+1)
Committee chair 0.819** 0.249** 0.250** 0.047**
(0.163) (0.092) (0.092) (0.012)
Ranking member 0.903** 0.167† 0.175† 0.031**
(0.172) (0.101) (0.101) (0.011)
Prestige committee 0.400** 0.073 0.072 0.010
(0.072) (0.049) (0.049) (0.007)
First year -0.175** -0.487** -0.488** -0.106**
(0.055) (0.078) (0.077) (0.012)
Second year -0.099 -0.396** -0.415** -0.048**
(0.083) (0.081) (0.080) (0.011)
Third year 0.069 -0.181** -0.184** -0.034**
(0.064) (0.069) (0.067) (0.009)
Fourth year 0.055 -0.202** -0.237** -0.022*
(0.102) (0.074) (0.071) (0.009)
Fifth year 0.000 -0.126* -0.117† -0.025**
(0.059) (0.064) (0.064) (0.007)
Sixth year 0.031 -0.084 -0.068 -0.015*
(0.124) (0.077) (0.076) (0.007)
Majority -0.141* 0.019 0.023 -0.010*
(0.060) (0.033) (0.033) (0.004)
President's party -0.135* 0.027 0.029 0.011**
(0.056) (0.031) (0.031) (0.004)
All legislators
Served at least 2nd term
Observations 435,999 435,999 417,987 435,999
Year x agency fixed effects X X X X
Legislator x agency fixed effects X X X
Code
beta <- m_total_dnd$coefficients %>%
  round(3) %>% 
  as_tibble(rownames = "beta") %>% 
  pivot_wider(names_from = beta)

se <- m_total_dnd$se %>%
  round(3) %>% as_tibble(rownames = "se") %>%
  pivot_wider(names_from = se)

save(models_total, rows, cm, gm,  beta, se,
     file = here::here("models", "models_total.Rdata"))


if(testing){
rows <- tibble(
  term = c("Dependent variable"),
  `(1)` = c("Count"),
  `(2)` =c("Count"),
  `(3)` = c("Count"),
  `(4)` = c("Log(Count+1)") 
)

attr(rows, 'position') <- c(0)


modelsummary::modelsummary(models_total,  
             # Custom sig stars for AJPS 
             #stars = c('†' = .1, '*' = .05, '**' = .01), 
             # Align coefficients by decimal for AJPS 
             #align = paste0("l", paste0(rep("d", length(models)), collapse = "")), 
             add_rows = rows,               
             #coef_map = cm, 
             #gof_map = gm, 
             #output = "tinytable",
             notes = "") 
}
Code
models_total
$`(1)`
GLM estimation, family = gaussian, Dep. Var.: perYear
Observations: 435,999 
Fixed-effects: Year_x_Agency: 806
Standard-errors: Clustered (Legislator) 
                  Estimate Std. Error   t value   Pr(>|t|)    
chair             0.819271   0.163444  5.012548 6.2734e-07 ***
ranking_minority  0.903476   0.172145  5.248343 1.8468e-07 ***
prestige          0.399584   0.071673  5.575142 3.1236e-08 ***
first            -0.174695   0.055066 -3.172465 1.5541e-03 ** 
second           -0.098991   0.082655 -1.197633 2.3132e-01    
third             0.069295   0.063694  1.087940 2.7686e-01    
fourth            0.055204   0.101772  0.542429 5.8764e-01    
fifth             0.000339   0.058640  0.005779 9.9539e-01    
sixth             0.030613   0.123551  0.247774 8.0436e-01    
majority         -0.141263   0.060189 -2.347001 1.9105e-02 *  
presidents_party -0.135137   0.056239 -2.402898 1.6434e-02 *  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Log-Likelihood: -1,609,220.4   Adj. Pseudo R2: 0.062907
           BIC:  3,229,049.9     Squared Cor.: 0.393187

$`(2)`
GLM estimation, family = gaussian, Dep. Var.: perYear
Observations: 435,999 
Fixed-effects: Year_x_Agency: 806,  Legislator_x_Agency: 81,164
Standard-errors: Clustered (Legislator) 
                  Estimate Std. Error   t value   Pr(>|t|)    
chair             0.249154   0.092098  2.705323 6.9312e-03 ** 
ranking_minority  0.167261   0.101125  1.653999 9.8418e-02 .  
prestige          0.072793   0.048506  1.500698 1.3373e-01    
first            -0.486846   0.078237 -6.222723 6.9783e-10 ***
second           -0.396377   0.081088 -4.888232 1.1715e-06 ***
third            -0.181007   0.069184 -2.616299 9.0129e-03 ** 
fourth           -0.201998   0.073741 -2.739282 6.2587e-03 ** 
fifth            -0.126317   0.063573 -1.986961 4.7179e-02 *  
sixth            -0.083934   0.077228 -1.086844 2.7735e-01    
majority          0.019341   0.032730  0.590915 5.5470e-01    
presidents_party  0.026941   0.031389  0.858299 3.9092e-01    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Log-Likelihood: -1,449,151.1   Adj. Pseudo R2: 0.108833
           BIC:  3,962,845.0     Squared Cor.: 0.708816

$`(3)`
GLM estimation, family = gaussian, Dep. Var.: perYear
Observations: 417,987 
Fixed-effects: Year_x_Agency: 806,  Legislator_x_Agency: 72,097
Standard-errors: Clustered (Legislator) 
                  Estimate Std. Error   t value   Pr(>|t|)    
chair             0.250277   0.091956  2.721701 6.6219e-03 ** 
ranking_minority  0.175166   0.101340  1.728487 8.4249e-02 .  
prestige          0.071684   0.048621  1.474335 1.4075e-01    
first            -0.487960   0.077451 -6.300259 4.6714e-10 ***
second           -0.414626   0.079854 -5.192330 2.5758e-07 ***
third            -0.183978   0.067489 -2.726042 6.5361e-03 ** 
fourth           -0.236530   0.071255 -3.319463 9.3863e-04 ***
fifth            -0.116974   0.064007 -1.827513 6.7958e-02 .  
sixth            -0.068130   0.076342 -0.892440 3.7240e-01    
majority          0.022644   0.032865  0.688990 4.9101e-01    
presidents_party  0.028579   0.031467  0.908237 3.6400e-01    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Log-Likelihood: -1,389,536.6   Adj. Pseudo R2: 0.111538
           BIC:  3,722,801.1     Squared Cor.: 0.706931

$`(4)`
GLM estimation, family = gaussian, Dep. Var.: log(perYear + 1)
Observations: 435,999 
Fixed-effects: Year_x_Agency: 806,  Legislator_x_Agency: 81,164
Standard-errors: Clustered (Legislator) 
                  Estimate Std. Error  t value   Pr(>|t|)    
chair             0.047018   0.011969  3.92848 9.0911e-05 ***
ranking_minority  0.031243   0.010755  2.90506 3.7466e-03 ** 
prestige          0.009672   0.007421  1.30337 1.9273e-01    
first            -0.105964   0.011500 -9.21408  < 2.2e-16 ***
second           -0.047599   0.010719 -4.44077 9.8852e-06 ***
third            -0.033538   0.009125 -3.67554 2.4901e-04 ***
fourth           -0.021796   0.008986 -2.42564 1.5444e-02 *  
fifth            -0.025440   0.006976 -3.64676 2.7823e-04 ***
sixth            -0.015363   0.007182 -2.13921 3.2643e-02 *  
majority         -0.010035   0.004068 -2.46686 1.3785e-02 *  
presidents_party  0.011145   0.003768  2.95768 3.1671e-03 ** 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Log-Likelihood:  -175,084.7   Adj. Pseudo R2: 0.436319
           BIC: 1,414,712.2     Squared Cor.: 0.724402

Total Letters Narrative

Code
beta$chair
[1] 0.249
Code
beta$chair - se$chair*1.96
[1] 0.06868
attr(,"type")
[1] "Clustered (Legislator)"
Code
beta$chair + se$chair*1.96
[1] 0.42932
attr(,"type")
[1] "Clustered (Legislator)"
Code
n_agency
[1] 92
Code
(beta$chair*n_agency) %>% round(0)
[1] 23
Code
(beta$chair*n_agency)/perYear_mean_total  %>% round(2)*100
[1] 27.27143

To address potential confounding in across-legislator comparisons, the estimates from Model 2 (Column 2 of Table @tbl-models_total) provide the estimated effects from the difference-in-differences specification in Equation @eq-diff1. Across all measures of institutional power, we find that more power increases the number of requests that legislators make. Consider first the effect of being a committee chair. We estimate that becoming a committee chair causes an increase of 0.25 requests per agency (95-percent confidence interval [0.07, 0.43]). Across all 92 agencies, this represents an increase of approximately 23 additional requests per year, 27.27% of the average requests per year in our data. There is a smaller increase for individuals who become ranking members and those who join a Prestige Committee, though the increase is statistically significant for the Prestige Committee. Becoming a ranking member of a committee causes an increase of 0.17 contacts per agency, while joining a prestige committee causes an 0.25 per agency increase in the number of contacts a member of Congress makes.

We estimate that the experience gained between the first and second year in Congress causes an increase of 0.09 requests per agency. The experience gained between the first and seventh year causes an increase of 0.49 per agency. Across all n_agency agencies, this represents an increase of approximately 45 additional requests per year, 35% of the average requests per year in our data. There is a smaller increase after the second year. The experience gained between the second and seventh year causes an increase of 0.4 per agency, an increase of approximately 36 additional requests per year, 29% of the average requests per year in our data.

Total Letters Predictions

  • Figures: figs/m-total-predicted-[1:4].png
Code
predicted <- predictions(m_total_cross,
                         newdata = values)

# Cross-sectional predictions
predicted %>%
    mutate(estimate = estimate*n_agency, 
         conf.high = conf.high*n_agency, conf.low = conf.low*n_agency) %>%  
  ggchair() + 
  labs(#title =  "", # "Predicted total letters per year\n(cross-sectional)",
       x = "",
       y = "Predicted total letters per year",
       fill = "",
       color = "",
       shape = "") 

# by tenure
predicted <- predictions(m_total_cross,
                         newdata = values_tenure)

predicted %>%
    mutate(estimate = estimate*n_agency, 
         conf.high = conf.high*n_agency, conf.low = conf.low*n_agency) %>% 
ggtenure() +
  labs(#title =  "", # "Predicted total letters per year\n(cross-sectional)",
       x = "Years serving in Congress",
       y = "      Predicted letters per year",
       fill = "",
       color = "",
       shape = "") 


# diff in diff 
predicted <- predictions(m_total_dnd,
                         newdata = values)


# Predictions by Chair and President's Party
predicted %>%
    mutate(estimate = estimate*n_agency, #predicted*n_agency,
         conf.high = conf.high*n_agency, conf.low = conf.low*n_agency) %>% # std.error = std.error*n_agency)) %>%
  ggchair() + 
  labs(#title =  "", # "Predicted total letters per year\nDifference in Differences (Within-Legislator)",
       x = "",
       y = "  Predicted letters per year",
       fill = "",
       color = "",
       shape = "") 

# by tenure
predicted2 <- predictions(m_total_dnd,
                         newdata = values_tenure)

predicted2 %>%
    mutate(estimate = estimate*n_agency, #predicted*n_agency,
         conf.high = conf.high*n_agency, conf.low = conf.low*n_agency) %>%  # std.error = std.error*n_agency) %>%
ggtenure() +
    geom_line(aes(x = as.numeric(year_in_congress %>% str_sub(1,1) %>% str_replace("N", "1")))) + 
  labs(#title =  "", # "Predicted total letters per year\nDifference in Differences (Within-Legislator)",
       x = "Years serving in Congress",
       y = "  Predicted letters per year",
       fill = "",
       color = "",
       shape = "") 

1

2

3

4

m-total-predicted

Code
predicted |> distinct( presidents_party, estimate, chair) |> as_tibble() |> 
  mutate(estimate*n_agency)
# A tibble: 4 × 4
  presidents_party estimate chair `estimate * n_agency`
             <dbl>    <dbl> <dbl>                 <dbl>
1                0     1.04     0                  96.1
2                1     1.07     0                  98.5
3                0     1.29     1                 119. 
4                1     1.32     1                 121. 

Constituency Service

Member-level Constituency Service Coefficient Plots

DV: Only letters coded as constituency service letters

  • Figures: figs/m-con-[1:4].png
Code
m_con_cross <-feglm (perYear_con ~ 
                       chair + ranking_minority + prestige + 
                    first + second + third + fourth + fifth + sixth + 
                    majority + presidents_party | Year_x_Agency,
                    cluster = "Legislator", 
           data = dcounts_tenure)

if(testing){modelsummary::modelsummary(m_con_cross)}

coefplot(m_con_cross, horiz = T, drop = "(Intercept)") 



# 2
m_con_dnd <-feglm (perYear_con ~ 
                     chair + ranking_minority + prestige + 
                    first + second + third + fourth + fifth + sixth + 
                    majority + presidents_party | Year_x_Agency + Legislator_x_Agency, 
                    cluster = "Legislator",
           data = dcounts_tenure)

if(testing){modelsummary::modelsummary(m_con_dnd)}
coefplot(m_con_dnd, horiz = T) 


# 3
m_con_2nd <-feglm (perYear_con ~ 
                     chair + ranking_minority + prestige + 
                    first + second + third + fourth + fifth + sixth + 
                    majority + presidents_party | Year_x_Agency + Legislator_x_Agency, 
                    cluster = "Legislator", 
           data = dcounts_tenure %>% filter(survive == 1))

if(testing){modelsummary::modelsummary(m_con_2nd)}

coefplot(m_con_2nd, horiz = T) 


# 4
m_logcon_dnd <-feglm (log(perYear_con + 1) ~ 
                        chair + ranking_minority + prestige + 
                    first + second + third + fourth + fifth + sixth + 
                    majority + presidents_party | Year_x_Agency + Legislator_x_Agency, 
                    cluster = "Legislator", 
           data = dcounts_tenure)

if(testing){modelsummary::modelsummary(m_logcon_dnd)}

coefplot(m_logcon_dnd, horiz = T) 

1

2

3

4

figs/m-con-[1:4].png

Constituency Service Table

  • Models: models/models_con.Rdata
Code
models_con <- list(
  "(1)" = m_con_cross,
  "(2)" = m_con_dnd,
  "(3)" =  m_con_2nd,
  "(4)" = m_logcon_dnd
)

beta <- m_con_dnd$coefficients %>%round(3) %>% as_tibble(rownames = "beta") %>% pivot_wider(names_from = beta)

se <- m_con_dnd$se %>%round(3) %>% as_tibble(rownames = "se") %>% pivot_wider(names_from = se)

save(models_con, rows, cm, gm,  beta, se,
     file = here::here("models", "models_con.Rdata"))

Constituency Service Narrative

Code
beta$chair
[1] 0.019
Code
beta$chair + se$chair*1.96
[1] 0.14248
attr(,"type")
[1] "Clustered (Legislator)"
Code
beta$chair - se$chair*1.96
[1] -0.10448
attr(,"type")
[1] "Clustered (Legislator)"

Table @tbl-models_con is identical to Table @tbl-models_total except that we subset the data to only legislator requests hand-coded as constituency service. Column 2 of Table @tbl-models_con provides the estimated effects from the difference-in-differences specification in Equation @eq-diff1. More experience increases the level of constituency service that legislators provide. The effect of being a committee chair is positive but not significant at the .05 level. We estimate that the experience gained between the first and second year in Congress causes an increase of 0.02 requests per agency. The experience gained between the first and seventh year causes an increase of 0.25 per agency. Across all 92 agencies, this represents an increase of approximately 23 additional requests per year, 26% of the average requests per year in our data. There is a smaller increase after the second year. The experience gained between the second and seventh year causes an increase of 0.24 per agency, an increase of approximately 22 additional requests per year, 24% of the average requests per year in our data.

Constituency Service Predictions

  • Figures: figs/m-con-predicted-[1:4].png
Code
# Cross-sectional predictions:
predicted <- predictions(m_con_cross,
                         newdata = values)


# A plot by chair & presidents' party
predicted %>%
  mutate(estimate = estimate*n_agency, #predicted*n_agency,
         conf.high = conf.high*n_agency, conf.low = conf.low*n_agency) %>% # std.error = std.error*n_agency)) %>%
  ggchair() + 
  labs(#title =  "", # "Predicted Constituency Service Letters per year\n(cross-sectional)",
       x = "",
       y = "  Predicted letters per year",
       fill = "",
       color = "",
       shape = "") 

# by tenure
predicted <- predictions(m_con_cross,
                         newdata = values_tenure)

predicted %>%
    mutate(estimate = estimate*n_agency, #predicted*n_agency,
         conf.high = conf.high*n_agency, conf.low = conf.low*n_agency) %>%  # std.error = std.error*n_agency) %>%
ggtenure() +
   # geom_line(aes(x = as.numeric(year_in_congress %>% str_sub(1,1)))) + 
  labs(#title =  "", # "Predicted Constituency Service Letters Per year\n(Cross Sectional)",
       x = "Years serving in Congress",
       y = "  Predicted letters per year",
       fill = "",
       color = "",
       shape = "") 



# Diff in diff predictions 
predicted <- predictions(m_con_dnd,
                         newdata = values)


# diff in diff plot
predicted %>%
  mutate(estimate = estimate*n_agency, #predicted*n_agency,
         conf.high = conf.high*n_agency, conf.low = conf.low*n_agency) %>% # std.error = std.error*n_agency)) %>%
  ggchair() + 
  labs(#title =  "", # "Predicted Constituency Service Letters per year\nDifference in Differences (Within-Legislator)",
       x = "",
       y = "  Predicted letters per year",
       fill = "",
       color = "",
       shape = "") 

# by tenure
predicted <- predictions(m_con_dnd,
                         newdata = values_tenure)

predicted %>%
    mutate(estimate = estimate*n_agency, #predicted*n_agency,
         conf.high = conf.high*n_agency, conf.low = conf.low*n_agency) %>%  # std.error = std.error*n_agency) %>%
ggtenure() +
    geom_line(aes(x = as.numeric(year_in_congress %>% str_sub(1,1)))) + 
  labs(#title =  "", # "Predicted Constituency Service Letters Per year\nDifference in Differences (Within-Legislator)",
       x = "Years serving in Congress",
       y = "  Predicted letters per year",
       fill = "",
       color = "",
       shape = "") 

1

2

3

4

m-con-predicted

Policy Work

Member-level Policy Work Coefficient Plots

Only using letters coded as constituent letters

  • Figures: figs/m-policy-[1:4].png
Code
m_policy_cross <-feglm (perYear_policy ~ 
                          chair + ranking_minority + prestige + 
                    first + second + third + fourth + fifth + sixth + 
                    majority +  presidents_party | Year_x_Agency,
                    cluster = "Legislator", 
           data = dcounts_tenure)

if(testing){modelsummary::modelsummary(m_policy_cross)}

coefplot(m_policy_cross, horiz = T, drop = "(Intercept)") 



# 2
m_policy_dnd <-feglm (perYear_policy ~ 
                        chair + ranking_minority + prestige + 
                    first + second + third + fourth + fifth + sixth + 
                    majority + presidents_party | Year_x_Agency + Legislator_x_Agency, 
                    cluster = "Legislator",
           data = dcounts_tenure)

if(testing){modelsummary::modelsummary(m_policy_dnd)}
coefplot(m_policy_dnd, horiz = T) 


# 3
m_policy_2nd <-feglm (perYear_policy ~ 
                        chair + ranking_minority + prestige + 
                    first + second + third + fourth + fifth + sixth + 
                    majority + presidents_party | Year_x_Agency + Legislator_x_Agency, 
                    cluster = "Legislator", 
           data = dcounts_tenure %>% filter(survive == 1))

if(testing){modelsummary::modelsummary(m_tenure_2nd)}
coefplot(m_policy_2nd, horiz = T) 


# 4
m_logcon_dnd <-feglm (log(perYear_policy + 1) ~ 
                        chair + ranking_minority + prestige + 
                    first + second + third + fourth + fifth + sixth + 
                    majority + presidents_party | Year_x_Agency + Legislator_x_Agency, 
                    cluster = "Legislator", 
           data = dcounts_tenure)

if(testing){modelsummary::modelsummary(m_logcon_dnd)}

coefplot(m_logcon_dnd, horiz = T) 

1

2

3

4

figs/m-policy-[1:4].png

Policy Work Table

  • Models: models/models_policy.Rdata
Code
models_policy <- list(
  "(1)" = m_policy_cross,
  "(2)" = m_policy_dnd,
  "(3)" =  m_policy_2nd,
  "(4)" = m_logcon_dnd
)

modelsummary(models_policy)
(1) (2) (3) (4)
† p < 0.1, * p < 0.05, ** p < 0.01
Dependent variable Count Count Count Log(Count+1)
Committee chair 0.220** 0.173** 0.174** 0.040**
(0.029) (0.037) (0.037) (0.007)
Ranking member 0.164** 0.105** 0.106** 0.027**
(0.030) (0.028) (0.028) (0.006)
Prestige committee 0.063** 0.023* 0.025* 0.005
(0.010) (0.011) (0.011) (0.003)
First year -0.063** -0.090** -0.087** -0.034**
(0.007) (0.018) (0.018) (0.005)
Second year -0.034** -0.057** -0.054** -0.021**
(0.008) (0.017) (0.017) (0.005)
Third year -0.039** -0.044** -0.045** -0.016**
(0.008) (0.014) (0.014) (0.004)
Fourth year -0.018† -0.023 -0.024† -0.009*
(0.009) (0.014) (0.014) (0.004)
Fifth year -0.025** -0.019† -0.020† -0.009**
(0.009) (0.011) (0.011) (0.003)
Sixth year -0.010 -0.004 -0.004 -0.007*
(0.012) (0.013) (0.013) (0.003)
Majority -0.001 -0.006 -0.006 -0.003†
(0.007) (0.006) (0.006) (0.002)
President's party 0.037** 0.014† 0.013† 0.006**
(0.008) (0.007) (0.008) (0.002)
All legislators
Served at least 2nd term
Observations 435,999 435,999 417,987 435,999
Year x agency fixed effects X X X X
Legislator x agency fixed effects X X X
Code
beta <- m_policy_dnd$coefficients %>%round(3) %>% as_tibble(rownames = "beta") %>% pivot_wider(names_from = beta)

se <- m_policy_dnd$se %>%round(3) %>% as_tibble(rownames = "se") %>% pivot_wider(names_from = se)

save(models_policy, rows, cm, gm,  beta, se,
     file = here::here("models", "models_policy.Rdata"))

Policy Work Predictions

  • Figures: figs/m-policy-predicted-[1:4].png
Code
predicted <- predictions(m_policy_cross,
                         newdata = values)

# Cross-sectional predictions
predicted %>%
  mutate(estimate = estimate*n_agency, #predicted*n_agency,
         conf.high = conf.high*n_agency, conf.low = conf.low*n_agency) %>% # std.error = std.error*n_agency)) %>%
  ggchair() +
  labs(#title =  "", # "Predicted Policy Letters per year\n(cross-sectional)",
       x = "",
       y = "  Predicted letters per year",
       fill = "",
       color = "",
       shape = "") 

# by tenure
predicted <- predictions(m_policy_cross,
                         newdata = values_tenure)

predicted %>%
    mutate(estimate = estimate*n_agency, #predicted*n_agency,
         conf.high = conf.high*n_agency, conf.low = conf.low*n_agency) %>%  # std.error = std.error*n_agency) %>%
ggtenure() +
  labs(#title =  "", # "Predicted Policy Letters Per year\n(Cross Sectional)",
       x = "Years serving in Congress",
       y = "  Predicted letters per year",
       fill = "",
       color = "",
       shape = "") 


# Predictions for diff in diff plot 
predicted <- predictions(m_policy_dnd,
                         newdata = values)


# diff in diff plot
predicted %>%
  mutate(estimate = estimate*n_agency, #predicted*n_agency,
         conf.high = conf.high*n_agency, conf.low = conf.low*n_agency) %>% # std.error = std.error*n_agency)) %>%
  ggchair() +
  labs(#title =  "", # "Predicted Policy Letters per year\nDifference in Differences (Within-Legislator)",
       x = "",
       y = "  Predicted letters per year",
       fill = "",
       color = "",
       shape = "")  

# by tenure
predicted <- predictions(m_policy_dnd,
                         newdata = values_tenure)

predicted %>%
    mutate(estimate = estimate*n_agency, #predicted*n_agency,
         conf.high = conf.high*n_agency, conf.low = conf.low*n_agency) %>%  # std.error = std.error*n_agency) %>%
ggtenure() +
    geom_line(aes(x = as.numeric(year_in_congress %>% str_sub(1,1)))) + 
  labs(#title =  "", # "Predicted Policy Letters Per year\nDifference in Differences (Within-Legislator)",
       x = "Years serving in Congress",
       y = "  Predicted letters per year",
       fill = "",
       color = "",
       shape = "") 

1

2

3

4

m-policy-predicted

Policy Work Narrative

Table @tbl-models_policy is identical to Table @tbl-models_total except that we subset the data to only legislator requests hand-coded as policy work. Column 2 of Table @tbl-models_policy provides the estimated effects from the difference-in-differences specification in Equation @eq-diff1. Across all measures of institutional power, we find that more power increases the level of policy work that legislators provide. Consider first the effect of being a committee chair. We estimate that becoming a committee chair causes an increase of 0.17 policy requests per agency (95-percent confidence interval [0.1, 0.25]). Across all 92 agencies, this represents an increase of approximately 16 additional requests per year, 94% of the average requests per year in our data. There is a smaller increase for individuals who become ranking members and those who join a Prestige Committee, though the increase is statistically significant for the prestige committee. Becoming a ranking member of a committee causes an increase of 0.1 contacts per agency, while joining a prestige committee causes a 0.17 per agency increase in the policy requests a member of Congress makes.

Total + Constituency

Total + Constituency Service Table

  • Models: models/models_total_con.Rdata
Code
models_total_con <- list(
  "(1)" = m_total_cross,
  "(2)" = m_total_dnd,
  "(3)" =  m_con_cross,
  "(4)" = m_con_dnd
)


rows <- tibble(
  term = c("Dependent variable"),
  `(1)` = c("Total Count"),
  `(2)` =c("Total Count"),
  `(3)` = c("Constituent Service"),
  `(4)` = c("Constituent Service") 
)

attr(rows, 'position') <- c(0)

modelsummary(models_total_con)
(1) (2) (3) (4)
† p < 0.1, * p < 0.05, ** p < 0.01
Dependent variable Total Count Total Count Constituent Service Constituent Service
Committee chair 0.819** 0.249** 0.403** 0.019
(0.163) (0.092) (0.123) (0.063)
Ranking member 0.903** 0.167† 0.561** 0.064
(0.172) (0.101) (0.130) (0.074)
Prestige committee 0.400** 0.073 0.259** 0.029
(0.072) (0.049) (0.056) (0.034)
First year -0.175** -0.487** -0.060 -0.253**
(0.055) (0.078) (0.043) (0.060)
Second year -0.099 -0.396** -0.048 -0.236**
(0.083) (0.081) (0.072) (0.064)
Third year 0.069 -0.181** 0.124* -0.057
(0.064) (0.069) (0.050) (0.057)
Fourth year 0.055 -0.202** 0.079 -0.112†
(0.102) (0.074) (0.091) (0.062)
Fifth year 0.000 -0.126* 0.042 -0.057
(0.059) (0.064) (0.045) (0.059)
Sixth year 0.031 -0.084 0.050 -0.042
(0.124) (0.077) (0.114) (0.069)
Majority -0.141* 0.019 -0.148** 0.028
(0.060) (0.033) (0.051) (0.027)
President's party -0.135* 0.027 -0.181** 0.023
(0.056) (0.031) (0.046) (0.023)
Observations 435,999 435,999 435,999 435,999
Year x agency fixed effects X X X X
Legislator x agency fixed effects X X
Code
beta_total <- m_total_dnd$coefficients %>%round(3) %>% as_tibble(rownames = "beta") %>% pivot_wider(names_from = beta)

beta_con <- m_con_dnd$coefficients %>%round(3) %>% as_tibble(rownames = "beta") %>% pivot_wider(names_from = beta)

se_total <- m_total_dnd$se %>%round(3) %>% as_tibble(rownames = "se") %>% pivot_wider(names_from = se) |> mutate_all(as.numeric)

se_con <- m_con_dnd$se %>%round(3) %>% as_tibble(rownames = "se") %>% pivot_wider(names_from = se)|> mutate_all(as.numeric)

save(models_total_con, rows, cm, gm, beta_total, se_total, beta_con, se_con,
     file = here::here("models", "models_total_con.Rdata"))

Total + Constituency Service Narrative

@tab-models_total_con shows the effects of institutional power and experience on overall requests to federal agencies (Models 1 and 1) and constituency service requests (Models 3 and 4). Models 3 and 4 are identical to models 1 and 2 except that we subset the data to only legislator requests hand-coded as constituency service. Columns 2 and 4 of @tab-models_total_con provide the estimated effects from the difference-in-differences specification in @eq-diff1. More experience and receiving a committee chairship increases the total requests to federal agencies. More experience increases the level of constituency service that legislators provide. The effect of being a committee chair is positive but not significant at the .05 level.

Becoming a committee chair causes an increase of 0.25 requests per agency (95-percent confidence interval [0.07, 0.43]). Across all 92 agencies, this represents an increase of approximately 22.91 additional requests per year, 29% of the average requests per year in our data.

Subsetting to requests hand-coded as constituency service, we estimate that becoming a committee chair causes an increase of 0.02 requests per agency, but this increase is not significant at the .05 level (95-percent confidence interval [-0.1, 0.14]). Across all 92 agencies, this represents an increase of approximately 1.75 additional requests per year, 24% of the average constituency service requests per year in our data.

We estimate that the experience gained between the first and second year in Congress causes an increase of 0.09 requests per agency.
The experience gained between the first and seventh year causes an increase of 0.49 per agency. Across all 92 agencies, this represents an increase of approximately 45 additional requests per year, 35% of the average requests per year in our data. There is a smaller increase after the second year. The experience gained between the second and seventh year causes an increase of 0.4 per agency, an increase of approximately 36 additional requests per year, 29% of the average requests per year in our data.

Subsetting to requests hand-coded as constituency service, we estimate that the experience gained between the first and second year in Congress causes an increase of 0.02 constituency service requests per agency.
The experience gained between the first and seventh year causes an increase of 0.25 per agency. Across all 92 agencies, this represents an increase of approximately 23 additional constituency service requests per year, 18% of the average constituency service requests per year in our data. Again, there is a smaller increase after the second year. The experience gained between the second and seventh year causes an increase of 0.24 per agency, an increase of approximately 22 additional constituency service requests per year, 17% of the average requests per year in our data.

Model Results: Ratio

Member-level Ratio Coefficient Plots

  • Figures: figs/m-ratio-[1:2].png
Code
dcounts_ratio %<>% 
  mutate(Legislator = icpsr,
         Year = year) %>% 
  filter(year >2006 & year < 2019)

m_ratio_cross <- feglm(ratio ~
  chair + ranking_minority + prestige + majority +  presidents_party + 
  first + second + third + fourth + fifth + sixth | Year,
cluster = "Legislator",
data = dcounts_ratio)

if(testing){modelsummary::modelsummary(m_ratio_cross)}

coefplot(m_ratio_cross, horiz = T, drop = "(Intercept)") 



# Diff in diff
m_ratio_dnd <- feglm(ratio ~
  chair + ranking_minority + prestige + majority + presidents_party + 
  first + second + third + fourth + fifth + sixth | 
    Legislator + Year,
cluster = "Legislator",
data = dcounts_ratio)

if(testing){modelsummary::modelsummary(m_ratio_dnd)}

coefplot(m_ratio_dnd, horiz = T) 

1

2

figs/m-ratio-[1:4].png

Ratio Table

  • Models: tables/models_ratio.Rdata
Code
models_ratio <- list(
  "(1)" = m_ratio_cross,
  "(2)" = m_ratio_dnd
)

rows <- tibble(
  term = c("Dependent variable", 
           "Majority", 
           "President's party"),
  `(1)` = c("Ratio", 
            "✓", 
            "✓"),
  `(2)` =c("Ratio", 
           "✓", 
           "✓")
)

# all coef
rows <- tibble(
  term = c("Dependent variable"),
  `(1)` = c("Ratio"),
  `(2)` =c("Ratio")
)

# Check marks for controls 
attr(rows, 'position') <- c(0, 20,21)

# all coef
attr(rows, 'position') <- c(0)


modelsummary(models_ratio,
             notes = list("Robust standard errors in parentheses, clustered by legislator.") )
(1) (2)
† p < 0.1, * p < 0.05, ** p < 0.01
Robust standard errors in parentheses, clustered by legislator.
Dependent variable Ratio Ratio
Committee chair -0.066** -0.081**
(0.017) (0.018)
Ranking member -0.005 -0.039**
(0.014) (0.015)
Prestige committee -0.010 0.000
(0.008) (0.000)
First year 0.052** 0.041**
(0.010) (0.014)
Second year 0.056** 0.047**
(0.009) (0.013)
Third year 0.059** 0.043**
(0.010) (0.012)
Fourth year 0.027** 0.011
(0.010) (0.011)
Fifth year 0.032** 0.015
(0.010) (0.011)
Sixth year 0.029** 0.012
(0.009) (0.009)
Majority 0.022** 0.001
(0.006) (0.006)
President's party -0.030** -0.010*
(0.005) (0.004)
Observations 31,719 31,719
Year fixed effects X X
Legislator fixed effects X
Code
beta <- m_ratio_dnd$coefficients %>%round(3) %>% as_tibble(rownames = "beta") %>% pivot_wider(names_from = beta)

se <- m_ratio_dnd$se %>%round(3) %>% as_tibble(rownames = "se") %>% pivot_wider(names_from = se)

save(models_ratio, rows, cm, gm,  beta, se,
     file = here::here("models", "models_ratio.Rdata"))

Ratio Narrative

Code
-beta$chair
[1] 0.081
Code
beta$chair + se$chair*1.96
[1] -0.04572
attr(,"type")
[1] "Clustered (Legislator)"
Code
beta$chair - se$chair*1.96
[1] -0.11628
attr(,"type")
[1] "Clustered (Legislator)"
Code
-beta$ranking_minority
[1] 0.039
Code
-beta$ranking_minority + se$ranking_minority*1.96
[1] 0.0684
attr(,"type")
[1] "Clustered (Legislator)"
Code
-beta$ranking_minority - se$ranking_minority*1.96
[1] 0.0096
attr(,"type")
[1] "Clustered (Legislator)"

Column 2 of Table @tbl-models_ratio provides the estimated effects from the difference-in-differences specification. We estimate that becoming a committee chair causes the ratio of constituency service to policy work to decrease by 0.08 (95-percent confidence interval [-0.05, -0.12 ]). Becoming a ranking member of a committee causes a decrease of 0.04 in the ratio.

Predictions

  • Figures: figs/m-ratio-predicted-[1:4].png
Code
predicted <- predictions(m_ratio_cross,
                         newdata = values)



# Cross-sectional predictions
predicted %>%
ggchair() +
  labs(#title = "", # Predicted ratio of constituent to policy work\n(cross-sectional)",
       x = "",
       y = "Predicted ratio of\nconstituent to policy work",
       fill = "",
       color = "",
       shape = "")

# by tenure
predicted <- predictions(m_ratio_cross,
                         newdata = values_tenure)

predicted %>%
ggtenure() +
  labs(#title = "", # "Predicted Ratio of Constituent to Policy Work\n(cross-sectional)",
       x = "Years serving in Congress",
       y = "Predicted ratio of\nconstituent to policy work",
       fill = "",
       color = "",
       shape = "")


# Predictions for diff in diff plot 
predicted <- predictions(m_ratio_dnd,
                         newdata = values)


# diff in diff plot
predicted %>%
  ggchair() + 
  labs(#title = "", # "Predicted Ratio of Constituent to Policy Work\nDifference in Differences (Within-Legislator)",
       x = "",
       y = "Predicted ratio of\nconstituent to policy work",
       fill = "",
       color = "",
       shape = "")

# by tenure
predicted <- predictions(m_ratio_dnd,
                         newdata = values_tenure)

predicted %>%
ggtenure() +
    geom_line(aes(x = as.numeric(year_in_congress %>% str_sub(1,1)))) + 
  labs(#title = "", # "Predicted Ratio of Constituent to Policy Work\nDifference in Differences (Within-Legislator)",
       x = "Years serving in Congress",
       y = "Predicted ratio of\nconstituent to policy work",
       fill = "",
       color = "",
       shape = "")

1

2

3

4

m-ratio-predicted


Model Results: Per District

Data

The main district-level models in the paper focus on estimating the effect of legislator experience by leveraging turnover within districts. We compare the service that a district receives before and after electing a new legislator and in the following years as legislators gain experience in office.

Same party lookup table

As a robustness check, we replicate our within-district results using a subset of district-year-count observations where turnover in a district allows us to assess the partisanship of the prior member holding that seat. We interact an indicator of whether the prior member was of the same party with all other indicators (i.e., whether the member is new, serving in years 1-6, or serving longer than 6 years). We compare the service that a district receives before and after electing a new legislator and, in the following years, accounting for whether that member is of the same party. However, a structural constraint of the data means that this robustness check is limited to observations where turnover within a redistricting cycle gives us a measure of whether the legislator replaced a member of the same party or of another party.

The table below accounts for redistricting by treating post-redistricting districts as new entities, not the same district as the one with the same number prior to redistricting (i.e., we do NOT count cases where a district elects someone of the same or different party as the district with the same number had before redistricting). Since some states completely re-number their districts, there is no way to be sure that a new District 4 has any relationship to the District 4 under the previous redistricting map (though it often may have significant overlap) without creating some spatial measure of the percent of shared census tracts, or something like that, which we do not attempt.

Many NAs exist for the same party variable because we only observe it when there is turnover within a redistricting cycle. Seats that do not turn over for an entire cycle (e.g., 2002-2012) are FALSE (“0”) for “new member” and have no value for “same party.” Other districts are NA until there is turnover. Thus, adding “same party” causes significant data loss due to NAs.

Warning

Specifically, we go from 7666 to 2822 observations when including same_party in the models below.

These observations are not missing at random, so all models with the same_party variable should be interpreted with great caution.

To measure turnover in the Senate where “districts” have two members, we code same party as FALSE if there is a change in the partisanship of a state’s Senate delegation. This captures the parallel dynamic of single-member House districts. If there are two Democrats and one is replaced by a Republican, the same party is FALSE. If there are two Democrats and a Democrat is elected, the same party is TRUE. If there are a Democrat senator and a Republican senator representing a state, and the Democrat is replaced by a Republican, same_party is FALSE. As per the VoteView convention, Senate delegations are District “0” (e.g., “alabama_0”). Split Senate delegations appear as “Democratic Party;Republican Party” in the table below.

Code
# year congress crosswalk 
year_congress <- dcounts_tenure %>% distinct(year, congress)


# Add Congress variable 
dcounts_per_district %<>% 
  ungroup() %>% 
  # Add Congress variable 
  left_join(year_congress)

member_data %<>% left_join(year_congress) %>% arrange(icpsr, year)

duplicates <-distinct(dcounts_per_district, icpsr, chamber, congress, state, state_dist, new_member)  %>% count(icpsr, chamber, congress, state_dist) %>% filter(n > 1) 

# Because members of the house are new one year and not the other, we can't do "new" at the Congress level 
# inner_join(duplicates, dcounts_per_district)

# make a variable for the prior seat holder party 
party_crosswalk <- member_data  %>% 
  ungroup() %>% 
  # add new member and state_dist (accounting for redistricting)
  left_join(distinct(dcounts_per_district, icpsr, chamber, congress, state, state_dist, new_member, year)) %>% 
  # Begin at 2000 census redistricting 
  filter(year > 2001) %>% 
  # Make additional new member and state_dist to fill in NAs with "0" (i.e., 2002-2012 redistricting cycle )
  mutate(state_dist2 = paste(state, district_code, "0", sep = "_") |> 
           str_replace("0_0", "0"),
         state_dist = coalesce(state_dist, state_dist2)) %>% 
  group_by(state_dist, chamber, year) %>% 
  # combine ICPSR IDS for senators to get the senate delegation to know if it changed 
  arrange(icpsr) |> # Make sure they are in the same order 
  mutate(icpsr = unique(icpsr) |> paste(collapse = ";")) %>% # collapse senate ICPSR
  ungroup() %>% 
  group_by(state_dist, chamber) |> 
  arrange(year) |> 
  mutate(
         lag_icpsr = dplyr::lag(icpsr),
         new_member2 = icpsr != lag_icpsr,
         new_member =  coalesce(new_member, as.numeric(new_member2))) %>% 
  distinct(year, congress, state_dist, state, district_code, party, new_member #, new_member2, icpsr, lag_icpsr        
           ) %>% #FIXME DROPPING OBS HERE
  # # for Senate, capture mixed delegations 
  group_by(state_dist, year) %>% 
  arrange(party) %>% 
  mutate(state_dist_party = unique(party) |> paste(collapse = ";")) %>% 
  ungroup() %>% 
  # state_dist is missing for some obs in member_data, so here is a backup that does not account for redistricting. If we end up needing this, we should correct it to the districts accounting for redistricting
  mutate(state_dist2 = paste(state, district_code, sep = "_")) %>% 
  distinct(congress, state_dist,  state_dist_party, new_member, year #, new_member2, icpsr, lag_icpsr
  ) %>% 
  arrange(state_dist, year) %>% 
  # group by icpsr 
  group_by(state_dist) %>% 
  # create lag party var and fill it in for that member's tenure 
  mutate(lag_p = dplyr::lag(state_dist_party), 
         same_party = state_dist_party == lag_p,
         same_party = ifelse(new_member, 
                same_party,
                             NA )) %>% 
  tidyr::fill(same_party, .direction = "down")  %>% 
  ungroup()

# LOOK FOR MISSING OBS BY CONGRESS 
#member_data |> count(congress)
#party_crosswalk |> ungroup() |> count(congress, is.na(state_dist))
#party_crosswalk |> ungroup() |> filter(is.na(state_dist), congress > 109)


party_crosswalk  |>  
  select(year, state_dist, state_dist_party, lag_p, same_party, new_member)  |> 
  kablebox_long()
year state_dist state_dist_party lag_p same_party new_member
2009 NA_0 (D) NA NA NA
2010 NA_0 (D) (D) NA 0
2019 NA_0 (D);(R) (D) NA 0
2019 NA_0 (D);(R) (D);(R) TRUE 1
2020 NA_0 (D);(R) (D);(R) TRUE 0
2007 NA_1_0 (D) NA NA NA
2007 NA_1_0 (D) (D) NA 0
2008 NA_1_0 (D) (D) NA 0
2009 NA_1_0 (D);(I) (D) FALSE 1
2009 NA_1_0 (D);(I) (D);(I) FALSE 0
2010 NA_1_0 (D);(I) (D);(I) FALSE 0
2019 NA_1_0 (D) (D);(I) FALSE 1
2020 NA_1_0 (D) (D) FALSE 0
2007 alabama_0 (R) NA NA 0
2008 alabama_0 (R) (R) NA 0
2009 alabama_0 (R) (R) NA 0
2010 alabama_0 (R) (R) NA 0
2011 alabama_0 (R) (R) NA 0
2012 alabama_0 (R) (R) NA 0
2013 alabama_0 (R) (R) NA 0
2014 alabama_0 (R) (R) NA 0
2015 alabama_0 (R) (R) NA 0
2016 alabama_0 (R) (R) NA 0
2017 alabama_0 (D);(R) (R) FALSE 1
2017 alabama_0 (D);(R) (D);(R) FALSE 0
2018 alabama_0 (D);(R) (D);(R) FALSE 0
2019 alabama_0 (D);(R) (D);(R) FALSE 0
2020 alabama_0 (D);(R) (D);(R) FALSE 0
2007 alabama_1_0 (R) NA NA 0
2008 alabama_1_0 (R) (R) NA 0
2009 alabama_1_0 (R) (R) NA 0
2010 alabama_1_0 (R) (R) NA 0
2011 alabama_1_1 (R) NA NA 0
2012 alabama_1_1 (R) (R) NA 0
2013 alabama_1_1 (R) (R) NA 0
2013 alabama_1_1 (R) (R) TRUE 1
2014 alabama_1_1 (R) (R) TRUE 0
2015 alabama_1_1 (R) (R) TRUE 0
2016 alabama_1_1 (R) (R) TRUE 0
2017 alabama_1_1 (R) (R) TRUE 0
2018 alabama_1_1 (R) (R) TRUE 0
2019 alabama_1_1 (R) (R) TRUE 0
2020 alabama_1_1 (R) (R) TRUE 0
2007 alabama_2_0 (R) NA NA 0
2008 alabama_2_0 (R) (R) NA 0
2009 alabama_2_0 (D) (R) FALSE 1
2010 alabama_2_0 (D) (D) FALSE 0
2011 alabama_2_1 (R) NA NA 1
2012 alabama_2_1 (R) (R) NA 0
2013 alabama_2_1 (R) (R) NA 0
2014 alabama_2_1 (R) (R) NA 0
2015 alabama_2_1 (R) (R) NA 0
2016 alabama_2_1 (R) (R) NA 0
2017 alabama_2_1 (R) (R) NA 0
2018 alabama_2_1 (R) (R) NA 0
2019 alabama_2_1 (R) (R) NA 0
2020 alabama_2_1 (R) (R) NA 0
2007 alabama_3_0 (R) NA NA 0
2008 alabama_3_0 (R) (R) NA 0
2009 alabama_3_0 (R) (R) NA 0
2010 alabama_3_0 (R) (R) NA 0
2011 alabama_3_1 (R) NA NA 0
2012 alabama_3_1 (R) (R) NA 0
2013 alabama_3_1 (R) (R) NA 0
2014 alabama_3_1 (R) (R) NA 0
2015 alabama_3_1 (R) (R) NA 0
2016 alabama_3_1 (R) (R) NA 0
2017 alabama_3_1 (R) (R) NA 0
2018 alabama_3_1 (R) (R) NA 0
2019 alabama_3_1 (R) (R) NA 0
2020 alabama_3_1 (R) (R) NA 0
2007 alabama_4_0 (R) NA NA 0
2008 alabama_4_0 (R) (R) NA 0
2009 alabama_4_0 (R) (R) NA 0
2010 alabama_4_0 (R) (R) NA 0
2011 alabama_4_1 (R) NA NA 0
2012 alabama_4_1 (R) (R) NA 0
2013 alabama_4_1 (R) (R) NA 0
2014 alabama_4_1 (R) (R) NA 0
2015 alabama_4_1 (R) (R) NA 0
2016 alabama_4_1 (R) (R) NA 0
2017 alabama_4_1 (R) (R) NA 0
2018 alabama_4_1 (R) (R) NA 0
2019 alabama_4_1 (R) (R) NA 0
2020 alabama_4_1 (R) (R) NA 0
2007 alabama_5_0 (D) NA NA 0
2008 alabama_5_0 (D) (D) NA 0
2009 alabama_5_0 (D);(R) (D) FALSE 1
2010 alabama_5_0 (D);(R) (D);(R) FALSE 0
2011 alabama_5_1 (R) NA NA 1
2012 alabama_5_1 (R) (R) NA 0
2013 alabama_5_1 (R) (R) NA 0
2014 alabama_5_1 (R) (R) NA 0
2015 alabama_5_1 (R) (R) NA 0
2016 alabama_5_1 (R) (R) NA 0
2017 alabama_5_1 (R) (R) NA 0
2018 alabama_5_1 (R) (R) NA 0
2019 alabama_5_1 (R) (R) NA 0
2020 alabama_5_1 (R) (R) NA 0
2007 alabama_6_0 (R) NA NA 0
Code
# Add same_party variable to district counts data 
dcounts_per_district %<>% 
  left_join(party_crosswalk ) %>% 
  arrange(state_dist, congress) %>% 
  ungroup()
  
dcounts_per_district |> 
  count(same_party) |> 
  kablebox()
same_party n
FALSE 585
TRUE 1637
NA 5444
Code
# 


# dcounts_per_district |> filter(chamber == "House") |>   select(icpsr, state_dist, year, state_dist_party, lag_p, same_party, new_member)   |> kablebox()

dcounts_per_district %<>% mutate(same_party = as.numeric(same_party))

Total Letters per District

The main results showing that replacing a more experienced legislator with a new legislator yields a substantial decrease in service to the district are robust to interacting all variables with the same_party variable. Cross-sectional effects get larger but more uncertain when data are subset to district years after turnover (as required for the same_party variable). Within-district effects become more uncertain, likely due to the much smaller number of observations where same_party can be observed.

Total District-level Coefficient Plots

Figures: figs/m-district-[1:4].png

  1. cross-sectional
  2. cross-sectional with same-party variable
  3. diff-in-diff
  4. diff-in-diff with same party variable
Code
# modify var names for presentation 
dcounts_per_district %<>% 
  mutate(same_party = as.numeric(same_party),
         Legislator = icpsr,
         District = state_dist,
         Year = year)

m_district_cross <- feglm(perYear ~
  new_member + 
  second + third + fourth + fifth + sixth | Year,
cluster = "District",
data = dcounts_per_district)

if(testing){modelsummary::modelsummary(m_district_cross)}

coefplot(m_district_cross, horiz = T, drop = "(Intercept)", sub = "Reference = A member serving >6 years") 


m_district_cross_party <- feglm(perYear ~
  new_member*same_party + 
  second*same_party + third*same_party + fourth*same_party + fifth*same_party + sixth*same_party | Year,
cluster = "District",
data = dcounts_per_district)

if(testing){modelsummary::modelsummary(m_district_cross)}

coefplot(m_district_cross_party, horiz = T, drop = "(Intercept)", sub = "Reference = A member serving >6 years") 


# Diff in diff
m_district_dnd <- feglm(perYear ~
  new_member + #same_party + 
  second + third + fourth + fifth + sixth | 
    District + Year,
cluster = "District",
data = dcounts_per_district )

coefplot(m_district_dnd, horiz = T, drop = "(Intercept)", sub = "Reference = A member serving >6 years") 

# Diff in diff
m_district_dnd_party <- feglm(perYear ~
  new_member*same_party + 
  second*same_party + third*same_party + fourth*same_party + fifth*same_party + sixth*same_party | 
    District + Year,
cluster = "District",
data = dcounts_per_district )

coefplot(m_district_dnd_party, horiz = T, drop = "(Intercept)", sub = "Reference = A member serving >6 years") 



# Diff in diff
m_district_dnd_house <- feglm(perYear ~
  new_member + #same_party + 
  second + third + fourth + fifth + sixth | 
    District + Year,
cluster = "District",
data = dcounts_per_district |> filter(chamber == "House"))


# Diff in diff + party change var 
m_district_dnd_house_party <- feglm(perYear ~
  new_member*same_party + 
  second*same_party + third*same_party + fourth*same_party + fifth*same_party + sixth*same_party | 
    District + Year,
cluster = "District",
data = dcounts_per_district |> filter(chamber == "House"))

# Diff in diff
m_district_dnd_senate <- feglm(perYear ~
  new_member + #same_party + 
  second + third + fourth + fifth + sixth | 
    District + Year,
cluster = "District",
data = dcounts_per_district |> filter(chamber == "Senate"))


# Diff in diff + party change var 
m_district_dnd_senate_party <- feglm(perYear ~
  new_member*same_party + 
  second*same_party + third*same_party + fourth*same_party + fifth*same_party + sixth*same_party | 
    District + Year,
cluster = "District",
data = dcounts_per_district |> filter(chamber == "Senate"))

1

2

3

4

figs/m-district-[1:4].png

Total Per District Tables

  • Models: models/models_district.Rdata
Code
models_district <- list(
  "(1)" = m_district_cross,
  "(2)" = m_district_dnd,
  "(3)" = m_district_dnd_house,
  "(4)" = m_district_dnd_senate
)


rows <- tibble(
  term = c("Dependent variable", 
           "All districts",
           "House only",
           "Senate only"),
  `(1)` = c("Per year", 
            "✓", 
            "", 
            ""),
  `(2)` =c("Per year", 
           "✓", 
            "", 
            ""),
    `(3)` = c("Per year", 
            "", 
            "✓", 
            ""),
  `(4)` =c("Per year", 
           #"✓", 
           "", 
            "", 
            "✓")
)



# Check marks for controls 
attr(rows, 'position') <- c(0, 14:16)



modelsummary(models_district,
             notes = list("Robust standard errors in parentheses, clustered by district.") )
(1) (2) (3) (4)
† p < 0.1, * p < 0.05, ** p < 0.01
Robust standard errors in parentheses, clustered by district.
Dependent variable Per year Per year Per year Per year
New member -34.426** -24.571** -8.705** -119.168**
(4.163) (4.582) (2.467) (16.345)
Second year -30.189** -20.334** -4.400 -90.755**
(5.559) (5.226) (3.449) (21.569)
Third year -13.871** -4.120 6.567* -47.812**
(4.408) (4.516) (2.555) (15.220)
Fourth year -14.326* -4.575 9.233* -35.995†
(6.549) (5.989) (4.595) (20.834)
Fifth year -13.436** -5.257 2.226 -31.704*
(3.824) (3.829) (2.307) (13.025)
Sixth year -10.889 -2.710 5.211 -5.532
(7.816) (7.506) (5.990) (26.684)
All districts
House only
Senate only
Observations 7,666 7,666 6,224 1,442
District fixed effects X X X
Year fixed effects X X X X
Code
beta <- m_district_dnd$coefficients %>%round(3) %>% as_tibble(rownames = "beta") %>% pivot_wider(names_from = beta)

beta_cross <- m_district_dnd$coefficients %>%round(3) %>% as_tibble(rownames = "beta") %>% pivot_wider(names_from = beta)


se <- m_district_dnd$se %>%round(3) %>% as_tibble(rownames = "se") %>% pivot_wider(names_from = se)

save(models_district, rows, cm, gm,  beta, se,
     file = here::here("models", "models_district.Rdata"))
Code
models_district
$`(1)`
GLM estimation, family = gaussian, Dep. Var.: perYear
Observations: 7,666 
Fixed-effects: Year: 14
Standard-errors: Clustered (District) 
           Estimate Std. Error  t value   Pr(>|t|)    
new_member -34.4264    4.16342 -8.26877 4.6411e-16 ***
second     -30.1893    5.55936 -5.43036 7.1787e-08 ***
third      -13.8707    4.40802 -3.14671 1.7037e-03 ** 
fourth     -14.3260    6.54933 -2.18741 2.8961e-02 *  
fifth      -13.4355    3.82388 -3.51359 4.6337e-04 ***
sixth      -10.8888    7.81619 -1.39310 1.6392e-01    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Log-Likelihood: -46,143.4   Adj. Pseudo R2: 0.019098
           BIC:  92,465.6     Squared Cor.: 0.212934

$`(2)`
GLM estimation, family = gaussian, Dep. Var.: perYear
Observations: 7,666 
Fixed-effects: District: 932,  Year: 14
Standard-errors: Clustered (District) 
            Estimate Std. Error   t value   Pr(>|t|)    
new_member -24.57101    4.58239 -5.362055 1.0382e-07 ***
second     -20.33394    5.22590 -3.890997 1.0694e-04 ***
third       -4.11958    4.51611 -0.912195 3.6190e-01    
fourth      -4.57487    5.98905 -0.763873 4.4514e-01    
fifth       -5.25718    3.82904 -1.372977 1.7009e-01    
sixth       -2.71040    7.50552 -0.361120 7.1809e-01    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Log-Likelihood: -44,053.3   Adj. Pseudo R2: 0.043726
           BIC:  96,612.9     Squared Cor.: 0.543751

$`(3)`
GLM estimation, family = gaussian, Dep. Var.: perYear
Observations: 6,224 
Fixed-effects: District: 882,  Year: 14
Standard-errors: Clustered (District) 
           Estimate Std. Error   t value   Pr(>|t|)    
new_member -8.70487    2.46683 -3.528761 0.00043911 ***
second     -4.40009    3.44950 -1.275574 0.20244231    
third       6.56678    2.55464  2.570527 0.01031760 *  
fourth      9.23254    4.59542  2.009075 0.04483382 *  
fifth       2.22605    2.30709  0.964873 0.33487298    
sixth       5.21087    5.99002  0.869926 0.38457796    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Log-Likelihood: -33,578.9   Adj. Pseudo R2: 0.034629
           BIC:  75,029.1     Squared Cor.: 0.496734

$`(4)`
GLM estimation, family = gaussian, Dep. Var.: perYear
Observations: 1,442 
Fixed-effects: District: 50,  Year: 14
Standard-errors: Clustered (District) 
             Estimate Std. Error   t value   Pr(>|t|)    
new_member -119.16759    16.3446 -7.290963 2.3527e-09 ***
second      -90.75467    21.5687 -4.207705 1.0953e-04 ***
third       -47.81196    15.2200 -3.141384 2.8505e-03 ** 
fourth      -35.99453    20.8336 -1.727718 9.0337e-02 .  
fifth       -31.70363    13.0247 -2.434109 1.8619e-02 *  
sixth        -5.53195    26.6841 -0.207312 8.3663e-01    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Log-Likelihood: -8,987.6   Adj. Pseudo R2: 0.050574
           BIC: 18,477.0     Squared Cor.: 0.533896
  • Models: models/models_district_party.Rdata
Code
models_district_party <- list(
  "(1)" = m_district_cross_party,
  "(2)" = m_district_dnd_party,
  "(3)" = m_district_dnd_house_party,
  "(4)" = m_district_dnd_senate_party
)


# Check marks for controls 
attr(rows, 'position') <- c(0, 28:31)



modelsummary(models_district_party, 
             notes = list("Robust standard errors in parentheses, clustered by district.") )
(1) (2) (3) (4)
† p < 0.1, * p < 0.05, ** p < 0.01
Robust standard errors in parentheses, clustered by district.
Dependent variable Per year Per year Per year Per year
New member -29.636* -23.178* -5.726 -59.353*
(12.715) (11.591) (13.975) (24.403)
New member x same party -50.373** -23.779† 12.661 -74.273*
(18.089) (13.729) (9.430) (31.816)
Second year -46.808** -45.709** -2.477 -88.831**
(13.908) (12.498) (11.333) (30.589)
Third year -18.336 -8.392 -0.818 -0.411
(12.896) (12.174) (13.041) (30.666)
Fourth year -44.929* -42.147* -0.493 4.518
(20.686) (18.377) (17.162) (30.076)
Fifth year -13.885 -10.366 -15.299 17.073
(11.301) (12.583) (11.054) (31.988)
Sixth year -21.885 -27.370 -1.759 130.998
(31.805) (35.706) (24.924) (169.982)
Second year x same party -33.944† -9.583 22.054* 0.401
(17.711) (15.329) (10.953) (39.652)
Third year x same party -28.534 -4.330 21.610* -53.187
(17.892) (15.188) (9.678) (39.246)
Fourth year x same party -3.978 17.093 36.267* -42.671
(24.630) (20.148) (17.304) (38.065)
Fifth year x same party -22.848 -3.375 24.389* -55.532
(16.859) (15.631) (9.659) (34.705)
Sixth year x same party -6.799 12.608 37.033 -145.917
(36.816) (40.529) (32.902) (171.269)
Same party 33.793† -7.052 -18.174 27.238
(18.234) (14.041) (14.297) (21.801)
All districts
House only
Senate only
Observations 2,222 2,222 1,523 699
District fixed effects X X X
Year fixed effects X X X X
Code
save(models_district_party, rows, cm, gm,  beta, se,
     file = here::here("models", "models_district_party.Rdata"))

Total Per District Narrative

Code
beta$new_member
[1] -24.571
Code
(beta$new_member/perYear_mean_total)*100
[1] -29.25119
Code
beta$new_member + se$new_member*1.96
[1] -15.59028
attr(,"type")
[1] "Clustered (District)"
Code
beta$new_member - se$new_member*1.96
[1] -33.55172
attr(,"type")
[1] "Clustered (District)"

To account for differences in district size, demographics, and demand for constituency service, Column 2 of Table @tbl-models_district provides the estimated effects from the difference-in-differences specification from Equation @eq-district1. In this specification, we see a large causal effect of a new member taking over: Electing a new member causes a district’s constituency service to decrease by about 40%, or 24.57 letters per year (95-percent confidence interval [-15.59, -33.55 ]).

The effect of electing a new representative, however, dissipates quickly. Districts represented by a legislator in their second year of service receive significantly fewer contacts with federal agencies, but not as drastic as the difference observed in the first year. After the second year, the differences are smaller. This phenomenon–new legislators providing substantially fewer requests–persists when examining the House (Column 3) and the Senate (Column 4) separately. In short, new legislators make fewer contacts for their constituents than established legislators.

Total Per District Predictions

  • Figures: figs/m-district-predicted-[1:4].png
Code
ggdistrict <- function(predicted = predicted, party = FALSE) {
  
predicted %<>%
  # drop estimates from impossible values
      group_by(rowid, 
               #predicted, std.error, 
               estimate, conf.low, conf.high,
               same_party) %>% 
  mutate(sum = sum(new_member, second, third, fourth, fifth, sixth),
         more = ifelse(sum == 0, 1,0)) %>% 
    filter(sum < 2) %>% 
    pivot_longer(cols = c("new_member", "second", "third", "fourth", "fifth", "sixth", "more")) %>% 
    select(name, value) %>% 
    filter(value == 1) %>% 
    # clean up for presentation
    mutate(year_in_congress = name %>% 
             str_replace("more", "7\nor more") %>% 
             str_replace("sixth", "6") %>% 
             str_replace("fifth", "5") %>% 
             str_replace("fourth", "4") %>% 
             str_replace("third", "3") %>% 
             str_replace("second", "2") %>% 
             str_replace("new_member", " New\nmember") 
             ) %>% 
    ungroup()
  
# Ideally, we could plot against data, but there is so much variation that you can no longer distinguish differences in predicted values 
# predicted %<>% full_join(dcounts_tenure2 %>% mutate(predicted = NA))
  



p <- predicted %>%  
  ungroup() %>% 
  ggplot() + 
  aes(x = year_in_congress, 
            y = estimate, # predicted, 
      ymin = conf.low,# predicted - 1.96*std.error,
      ymax = conf.high) + 
  geom_pointrange(position =  position_dodge(width = -.3)  )  + 
  # geom_line() + # requires numeric...look to see how I did this on the other ones
  scale_fill_viridis_d(begin = 0, end = .6, option = "cividis") +
  scale_color_viridis_d(begin = 0, end = .6, option = "cividis") 

if(party){
p <- predicted %>%  
  mutate(same_party = as.logical(same_party)) %>% 
  ungroup() %>% 
  ggplot() + 
  aes(x = year_in_congress, 
            y = estimate, # predicted, 
      ymin = conf.low,# predicted - 1.96*std.error,
      ymax = conf.high, #predicted + 1.96*std.error,
      shape = same_party,
      color = same_party) + 
  geom_pointrange(position =  position_dodge(width = -.3)  )  + 
  scale_fill_viridis_d(begin = 0, end = .6, option = "cividis") +
  scale_color_viridis_d(begin = 0, end = .6, option = "cividis") 
}

return(p)
  
}


# dcounts_per_district %>% filter(District == "tennessee_1_1") |> View()
# dcounts_per_district %>% filter(District == "alabama_1_1") |> View()


# by year 
values <- tidyr::expand(dcounts_per_district,
                        new_member = new_member,
                        second = second,
                        third = third,
                        fourth = fourth,
                        fifth = fifth, 
                        sixth = sixth,
                        Legislator = "21376",
                        Year = "2016",
                        District = "alabama_1_1",
                        same_party = same_party
                   ) %>% 
  drop_na(same_party)

predicted <- predictions(m_district_cross,
                         newdata = values) 



# Cross-sectional predictions
predicted %>%
ggdistrict() +
  labs(#title = "Letters\nper district\n(cross-sectional)",
       x = "Years serving in Congress",
       y = "   Predicted letters\nper year per district\n(cross-sectional)",
       fill = "",
       color = "",
       shape = "") 



# Predictions with the party of the prior representative

predicted <- predictions(m_district_cross_party,
                         newdata = values) 

predicted %>%
  ggdistrict(party = TRUE) + 
  labs(# title = "",
       x = "Years serving in Congress",
       y = "   Predicted letters\nper year per district\n(cross-sectional)",
       fill = "",
       color = "Same party",
       shape = "Same party") 




# diff in diff plot

predicted <- predictions(m_district_dnd,
                         newdata = values) 


predicted %>%
ggdistrict() +
      geom_line(aes(x = as.numeric(year_in_congress %>% str_replace(" N", "1") %>% str_sub(1,1) ))) + 
  labs(#title = "Letters\nper district\n(difference-in-differences)",
       x = "Years serving in Congress",
       y = "    Predicted letters\nper year per district\n(difference-in-differneces)",
       fill = "",
       color = "",
       shape = "")



# Predictions with the Same party  

predicted <- predictions(m_district_dnd_party,
                         newdata = values) 


# diff in diff plot
predicted %>%
  ggdistrict(party = TRUE) +
        geom_line(aes(x = as.numeric(year_in_congress %>% str_replace(" N", "1") %>% str_sub(1,1) ))) + 
  labs(#title = "Letters Per District\n(difference-in-differences)",
       x = "Years serving in Congress",
       y = "   Predicted letters\nper year per district\n(difference-in-differences)",
       fill = "",
       color = "Same party",
       shape = "Same party")

1

2

3

4

figs/m-district-predicted-[1:4].png

Total Per District Predictions (House only)

  • Figures: figs/m-district-predicted-house-[1:2].png
Code
# diff in diff plot

predicted <- predictions(m_district_dnd_house,
                         newdata = values) 


predicted %>%
ggdistrict() +
        geom_line(aes(x = as.numeric(year_in_congress %>% str_replace(" N", "1") %>% str_sub(1,1) ))) + 
  labs(#title = "Letters\nper House district\n(difference-in-differences)",
       x = "Years serving in Congress",
       y = "    Predicted letters per year\nper House district\n(difference-in-differneces)",
       fill = "",
       color = "",
       shape = "")



# Predictions with the Same party  

predicted <- predictions(m_district_dnd_house_party,
                         newdata = values)

# diff in diff plot
predicted %>%
  ggdistrict(party = TRUE) +
        geom_line(aes(x = as.numeric(year_in_congress %>% str_replace(" N", "1") %>% str_sub(1,1) ))) + 
  labs(#title = "Letters\nper House district\n(difference-in-differences)",
       x = "Years serving in Congress",
       y = "   Predicted letters per year\nper House districte district\n(difference-in-differences)",
       fill = "",
       color = "Same party",
       shape = "Same party")

1

2

figs/m-district-predicted-house-[1:2].png

Total Per District Predictions (Senate only)

  • Figures: figs/m-district-predicted-senate-[1:2].png
Code
# Values for Senate seats 
values <- tidyr::expand(dcounts_per_district,
                        new_member = new_member,
                        second = second,
                        third = third,
                        fourth = fourth,
                        fifth = fifth, 
                        sixth = sixth,
                        Legislator = "49700",
                        Year = "2017",
                        District = "alabama_0",
                        same_party = same_party
                   ) %>% 
  drop_na(same_party)

# diff in diff plot

predicted <- predictions(m_district_dnd_senate,
                         newdata = values) 


predicted %>%
ggdistrict() +
        geom_line(aes(x = as.numeric(year_in_congress %>% str_replace(" N", "1") %>% str_sub(1,1) ))) + 
  labs(#title = "Letters per senator\n(difference-in-differences)",
       x = "Years serving in Congress",
       y = "   Predicted letters per year\nper Senator\n(difference-in-differneces)",
       fill = "",
       color = "",
       shape = "")



# Predictions with the Same party  

predicted <- predictions(m_district_dnd_senate_party,
                         newdata = values) 


# diff in diff plot
predicted %>%
  ggdistrict(party = TRUE) +
        geom_line(aes(x = as.numeric(year_in_congress %>% str_replace(" N", "1") %>% str_sub(1,1) ))) + 
  labs(#title = "Letters per senator\n(difference-in-differences)",
       x = "Years serving in Congress",
       y = "   Predicted letters per year\nper Senator\n(difference-in-differences)",
       fill = "",
       color = "Same party",
       shape = "Same party")

1

2

figs/m-district-predicted-senate-[1:2].png

Constituency Service Letters per District

The main results showing that replacing a more experienced legislator with a new legislator yields a substantial decrease in service to the district are robust to interacting all variables with the same_party variable. Cross-sectional effects get larger but more uncertain when data are subset to district years after turnover (as required for the same_party variable). Within-district effects become more uncertain, likely due to the much smaller number of observations where same_party can be observed.

Constituency Service District-level Coefficient Plots

Figures: figs/m-district-con-[1:4].png

  1. cross-sectional
  2. cross-sectional with same-party variable
  3. diff-in-diff
  4. diff-in-diff with same party variable
Code
m_district_con_cross <- feglm(perYear_con ~
  new_member + 
  second + third + fourth + fifth + sixth | Year,
cluster = "District",
data = dcounts_per_district)

if(testing){modelsummary::modelsummary(m_district_con_cross)}

coefplot(m_district_con_cross, horiz = T, drop = "(Intercept)", sub = "Reference = A member serving >6 years") 


m_district_con_cross_party <- feglm(perYear_con ~
  new_member*same_party + 
  second*same_party + third*same_party + fourth*same_party + fifth*same_party + sixth*same_party | Year,
cluster = "District",
data = dcounts_per_district)

if(testing){modelsummary::modelsummary(m_district_con_cross)}

coefplot(m_district_con_cross_party, horiz = T, drop = "(Intercept)", sub = "Reference = A member serving >6 years") 


# Diff in diff
m_district_con_dnd <- feglm(perYear_con ~
  new_member + #same_party + 
  second + third + fourth + fifth + sixth | 
    District + Year,
cluster = "District",
data = dcounts_per_district )

coefplot(m_district_con_dnd, horiz = T, drop = "(Intercept)", sub = "Reference = A member serving >6 years") 

# Diff in diff
m_district_con_dnd_party <- feglm(perYear_con ~
  new_member*same_party + 
  second*same_party + third*same_party + fourth*same_party + fifth*same_party + sixth*same_party | 
    District + Year,
cluster = "District",
data = dcounts_per_district )

coefplot(m_district_con_dnd_party, horiz = T, drop = "(Intercept)", sub = "Reference = A member serving >6 years") 



# Diff in diff
m_district_con_dnd_house <- feglm(perYear_con ~
  new_member + #same_party + 
  second + third + fourth + fifth + sixth | 
    District + Year,
cluster = "District",
data = dcounts_per_district |> filter(chamber == "House"))


# Diff in diff + party change var 
m_district_con_dnd_house_party <- feglm(perYear_con ~
  new_member*same_party + 
  second*same_party + third*same_party + fourth*same_party + fifth*same_party + sixth*same_party | 
    District + Year,
cluster = "District",
data = dcounts_per_district |> filter(chamber == "House"))

# Diff in diff
m_district_con_dnd_senate <- feglm(perYear_con ~
  new_member + #same_party + 
  second + third + fourth + fifth + sixth | 
    District + Year,
cluster = "District",
data = dcounts_per_district |> filter(chamber == "Senate"))


# Diff in diff + party change var 
m_district_con_dnd_senate_party <- feglm(perYear_con ~
  new_member*same_party + 
  second*same_party + third*same_party + fourth*same_party + fifth*same_party + sixth*same_party | 
    District + Year,
cluster = "District",
data = dcounts_per_district |> filter(chamber == "Senate"))

1

2

3

4

figs/m-district-con-[1:4].png

Constituency Service Per District Tables

  • Models: models/models_district_con.Rdata
Code
models_district_con <- list(
  "(1)" = m_district_con_cross,
  "(2)" = m_district_con_dnd,
  "(3)" = m_district_con_dnd_house,
  "(4)" = m_district_con_dnd_senate
)


rows <- tibble(
  term = c("Dependent variable", 
           "All districts",
           "House only",
           "Senate only"),
  `(1)` = c("Per year", 
            "✓", 
            "", 
            ""),
  `(2)` =c("Per year", 
           "✓", 
            "", 
            ""),
    `(3)` = c("Per year", 
            "", 
            "✓", 
            ""),
  `(4)` =c("Per year", 
           "", 
            "", 
            "✓")
)



# Check marks for controls 
attr(rows, 'position') <- c(0, 14:16)



modelsummary(models_district_con, 
             notes = list("Robust standard errors in parentheses, clustered by district.") ) 
(1) (2) (3) (4)
† p < 0.1, * p < 0.05, ** p < 0.01
Robust standard errors in parentheses, clustered by district.
Dependent variable Per year Per year Per year Per year
New member -17.991** -10.947** -1.170 -74.505**
(2.983) (3.492) (2.076) (12.617)
Second year -18.162** -11.118* -0.119 -56.682**
(4.707) (4.654) (3.291) (21.140)
Third year -3.676 3.267 9.653** -24.350†
(3.212) (3.637) (2.224) (12.520)
Fourth year -6.196 0.747 11.185* -21.175
(5.671) (5.476) (4.455) (18.510)
Fifth year -5.084† 0.126 5.280** -20.518†
(2.810) (3.121) (2.003) (10.634)
Sixth year -4.177 1.033 7.415 0.675
(7.056) (6.983) (5.904) (23.821)
All districts
House only
Senate only
Observations 7,666 7,666 6,224 1,442
District fixed effects X X X
Year fixed effects X X X X
Code
beta <- m_district_con_dnd$coefficients %>%round(3) %>% as_tibble(rownames = "beta") %>% pivot_wider(names_from = beta)

beta_cross <- m_district_con_dnd$coefficients %>%round(3) %>% as_tibble(rownames = "beta") %>% pivot_wider(names_from = beta)


se <- m_district_con_dnd$se %>%round(3) %>% as_tibble(rownames = "se") %>% pivot_wider(names_from = se)

save(models_district_con, rows, cm, gm,  beta, se,
     file = here::here("models", "models_district_con.Rdata"))
Code
beta$new_member
[1] -10.947
Code
beta$new_member + se$new_member*1.96
[1] -4.10268
attr(,"type")
[1] "Clustered (District)"
Code
beta$new_member - se$new_member*1.96
[1] -17.79132
attr(,"type")
[1] "Clustered (District)"
  • Models: models/models_district_con_party.Rdata
Code
models_district_con_party <- list(
  "(1)" = m_district_con_cross_party,
  "(2)" = m_district_con_dnd_party,
  "(3)" = m_district_con_dnd_house_party,
  "(4)" = m_district_con_dnd_senate_party
)


# Check marks for controls 
attr(rows, 'position') <- c(0, 28:31)



modelsummary(models_district_con_party, 
             notes = list("Robust standard errors in parentheses, clustered by district.") ) 
(1) (2) (3) (4)
† p < 0.1, * p < 0.05, ** p < 0.01
Robust standard errors in parentheses, clustered by district.
Dependent variable Per year Per year Per year Per year
New member -8.519 -1.835 -4.479 -20.841
(9.301) (11.062) (12.858) (22.389)
New member x same party -40.031** -14.008 15.526† -59.484*
(14.216) (11.964) (8.516) (28.259)
Second year -30.639** -29.618* -6.171 -66.226†
(9.894) (11.514) (10.272) (36.671)
Third year -1.819 6.611 -0.618 8.357
(9.763) (11.342) (12.004) (26.818)
Fourth year -31.456† -30.140† -1.973 9.787
(18.622) (17.442) (16.693) (25.149)
Fifth year -4.299 -5.202 -15.185 1.214
(8.872) (10.452) (9.226) (22.160)
Sixth year -18.366 -27.845 -4.419 124.006
(31.481) (35.139) (24.402) (166.703)
Second year x same party -27.758† -3.579 23.106* 17.351
(14.406) (14.053) (9.896) (42.914)
Third year x same party -22.546 0.296 21.710* -29.673
(14.324) (13.353) (8.794) (34.640)
Fourth year x same party -3.762 16.820 33.780* -28.509
(21.815) (18.457) (16.874) (31.529)
Fifth year x same party -16.480 3.085 25.572** -22.196
(13.490) (12.459) (7.641) (24.587)
Sixth year x same party -1.686 18.238 37.244 -125.666
(35.622) (39.215) (32.622) (167.604)
Same party 27.264† -7.737 -20.902 20.086
(14.139) (12.325) (13.050) (19.725)
All districts
House only
Senate only
Observations 2,222 2,222 1,523 699
District fixed effects X X X
Year fixed effects X X X X
Code
save(models_district_con_party, rows, cm, gm,  beta, se,
     file = here::here("models", "models_district_con_party.Rdata"))

Constituency Service Per District Narrative

To account for differences in district size, demographics, and demand for constituency service, Column 2 of Table @tbl-models_district_con provides the estimated effects from the difference-in-differences specification from Equation @eq-district1. In this specification, we see a large causal effect of a new member taking over: Electing a new member causes a district’s constituency service to decrease by 10.95 letters per year (95-percent confidence interval [-4.1, -17.79 ]).

The effect of electing a new representative, however, dissipates quickly. Districts represented by a legislator in their second year of service receive significantly fewer contacts with federal agencies, but not as drastic as the difference observed in the first year. After the second year, the differences are smaller. This phenomenon–new legislators providing substantially fewer requests–persists when examining the Senate (Column 4) separately. When examining the House separately, the pattern is similar but slightly different; members make few constituency service requests in their first two years before increasing to a higher level in years 3-6 and then possibly decreasing after that (since seven or more years in office is the reference category, we don’t have uncertainty bounds around this estimate). In short, new legislators make fewer contacts for their constituents than established legislators.

Constituency Service Per District Predictions

  • Figures: figs/m-district-con-predicted-[1:4].png
Code
ggdistrict <- function(predicted = predicted, party = FALSE) {
  
predicted %<>%
  # drop estimates from impossible values
      group_by(rowid, 
               #predicted, std.error, 
               estimate, conf.low, conf.high,
               same_party) %>% 
  mutate(sum = sum(new_member, second, third, fourth, fifth, sixth),
         more = ifelse(sum == 0, 1,0)) %>% 
    filter(sum < 2) %>% 
    pivot_longer(cols = c("new_member", "second", "third", "fourth", "fifth", "sixth", "more")) %>% 
    select(name, value) %>% 
    filter(value == 1) %>% 
    # clean up for presentation
    mutate(year_in_congress = name %>% 
             str_replace("more", "7\nor more") %>% 
             str_replace("sixth", "6") %>% 
             str_replace("fifth", "5") %>% 
             str_replace("fourth", "4") %>% 
             str_replace("third", "3") %>% 
             str_replace("second", "2") %>% 
             str_replace("new_member", " New\nmember") 
             ) %>% 
    ungroup()
  
# Ideally, we could plot against data, but there is so much variation that you can no longer distinguish differences in predicted values 
# predicted %<>% full_join(dcounts_tenure2 %>% mutate(predicted = NA))
  



p <- predicted %>%  
  ungroup() %>% 
  ggplot() + 
  aes(x = year_in_congress, 
            y = estimate, # predicted, 
      ymin = conf.low,# predicted - 1.96*std.error,
      ymax = conf.high) + 
  geom_pointrange(position =  position_dodge(width = -.3)  )  + 
  # geom_line() + # requires numeric...look to see how I did this on the other ones
  scale_fill_viridis_d(begin = 0, end = .6, option = "cividis") +
  scale_color_viridis_d(begin = 0, end = .6, option = "cividis") 

if(party){
p <- predicted %>%  
  mutate(same_party = as.logical(same_party)) %>% 
  ungroup() %>% 
  ggplot() + 
  aes(x = year_in_congress, 
            y = estimate, # predicted, 
      ymin = conf.low,# predicted - 1.96*std.error,
      ymax = conf.high, #predicted + 1.96*std.error,
      shape = same_party,
      color = same_party) + 
  geom_pointrange(position =  position_dodge(width = -.3)  )  + 
  scale_fill_viridis_d(begin = 0, end = .6, option = "cividis") +
  scale_color_viridis_d(begin = 0, end = .6, option = "cividis") 
}

return(p)
  
}


# dcounts_per_district %>% filter(District == "tennessee_1_1") |> View()
# dcounts_per_district %>% filter(District == "alabama_1_1") |> View()


# by year 
values <- tidyr::expand(dcounts_per_district,
                        new_member = new_member,
                        second = second,
                        third = third,
                        fourth = fourth,
                        fifth = fifth, 
                        sixth = sixth,
                        Legislator = "21376",
                        Year = "2016",
                        District = "alabama_1_1",
                        same_party = same_party
                   ) %>% 
  drop_na(same_party)

predicted <- predictions(m_district_con_cross,
                         newdata = values) 



# Cross-sectional predictions
predicted %>%
ggdistrict() +
  labs(#title = "Letters\nper district\n(cross-sectional)",
       x = "Years serving in Congress",
       y = "   Predicted letters per year\n(constituency service only)\nper district\n(cross-sectional)",
       fill = "",
       color = "",
       shape = "") 



# Predictions with the party of the prior representative

predicted <- predictions(m_district_con_cross_party,
                         newdata = values) 

predicted %>%
  ggdistrict(party = TRUE) + 
  labs(#title = "Letters\nper district\n(cross-sectional)",
       x = "Years serving in Congress",
              y = "   Predicted letters per year\n(constituency service only)\nper district\n(cross-sectional)",
       fill = "",
       color = "Same party",
       shape = "Same party") 




# diff in diff plot

predicted <- predictions(m_district_con_dnd,
                         newdata = values) 


predicted %>%
ggdistrict() +
      geom_line(aes(x = as.numeric(year_in_congress %>% str_replace(" N", "1") %>% str_sub(1,1) ))) + 
  labs(#title = "Letters\nper district\n(difference-in-differences)",
       x = "Years serving in Congress",
              y = "   Predicted letters per year\n(constituency service only)\nper district\n(difference-in-differneces)",
       fill = "",
       color = "",
       shape = "")



# Predictions with the Same party  

predicted <- predictions(m_district_con_dnd_party,
                         newdata = values) 

# diff in diff plot
predicted %>%
  ggdistrict(party = TRUE) +
        geom_line(aes(x = as.numeric(year_in_congress %>% str_replace(" N", "1") %>% str_sub(1,1) ))) + 
  labs(#title = "Letters Per District\n(difference-in-differences)",
       x = "Years serving in Congress",
              y = "   Predicted letters per year\n(constituency service only)\nper district\n(difference-in-differences)",
       fill = "",
       color = "Same party",
       shape = "Same party")

1

2

3

4

figs/m-district-con-predicted-[1:4].png

Constituency Service Per District Predictions (House only)

  • Figures: figs/m-district-con-predicted-house-[1:2].png
Code
# diff in diff plot

predicted <- predictions(m_district_con_dnd_house,
                         newdata = values) 


predicted %>%
ggdistrict() +
        geom_line(aes(x = as.numeric(year_in_congress %>% str_replace(" N", "1") %>% str_sub(1,1) ))) + 
  labs(#title = "Letters\nper House district\n(difference-in-differences)",
       x = "Years serving in Congress",
              y = "   Predicted letters per year\n(constituency service only)\nper House district\n(difference-in-differneces)",
       fill = "",
       color = "",
       shape = "")



# Predictions with the Same party  

predicted <- predictions(m_district_con_dnd_house_party,
                         newdata = values) 

# diff in diff plot
predicted %>%
  ggdistrict(party = TRUE) +
        geom_line(aes(x = as.numeric(year_in_congress %>% str_replace(" N", "1") %>% str_sub(1,1) ))) + 
  labs(#title = "Letters\nper House district\n(difference-in-differences)",
       x = "Years serving in Congress",
              y = "   Predicted letters per year\n(constituency service only)\nper House district\n(difference-in-differences)",
       fill = "",
       color = "Same party",
       shape = "Same party")

1

2

figs/m-district-con-predicted-house-[1:2].png

Constituency Service Per District Predictions (Senate only)

  • Figures: figs/m-district-con-predicted-senate-[1:2].png
Code
# Values for Senate seats 
values <- tidyr::expand(dcounts_per_district,
                        new_member = new_member,
                        second = second,
                        third = third,
                        fourth = fourth,
                        fifth = fifth, 
                        sixth = sixth,
                        Legislator = "49700",
                        Year = "2017",
                        District = "alabama_0",
                        same_party = same_party
                   ) %>% 
  drop_na(same_party)

# diff in diff plot

predicted <- predictions(m_district_con_dnd_senate,
                         newdata = values) 


predicted %>%
ggdistrict() +
        geom_line(aes(x = as.numeric(year_in_congress %>% str_replace(" N", "1") %>% str_sub(1,1) ))) + 
  labs(#title = "Letters per senator\n(difference-in-differences)",
       x = "Years serving in Congress",
              y = "   Predicted letters per year\n(constituency service only)\nper senator\n(difference-in-differneces)",
       fill = "",
       color = "",
       shape = "")



# Predictions with the Same party  

predicted <- predictions(m_district_con_dnd_senate_party,
                         newdata = values) 
# diff in diff plot
predicted %>%
  ggdistrict(party = TRUE) +
        geom_line(aes(x = as.numeric(year_in_congress %>% str_replace(" N", "1") %>% str_sub(1,1) ))) + 
  labs(#title = "Letters per senator\n(difference-in-differences)",
       x = "Years serving in Congress",
              y = "   Predicted letters per year\n(constituency service only)\nper senator\n(difference-in-differences)",
       fill = "",
       color = "Same party",
       shape = "Same party")

1

2

figs/m-district-con-predicted-senate-[1:2].png

Policy Letters per District

The main results showing that replacing a more experienced legislator with a new legislator yields a substantial decrease in policy work to the district are robust to interacting all variables with the same_party variable. Cross-sectional effects get larger but more uncertain when data are subset to district years after turnover (as required for the same_party variable). Within-district effects become more uncertain, likely due to the much smaller number of observations where same_party can be observed.

Policy District-level Coefficient Plots

Figures: figs/m-district-policy-[1:4].png

  1. cross-sectional
  2. cross-sectional with same-party variable
  3. diff-in-diff
  4. diff-in-diff with Same party variable
Code
m_district_policy_cross <- feglm(perYear_policy ~
  new_member + 
  second + third + fourth + fifth + sixth | Year,
cluster = "District",
data = dcounts_per_district)

if(testing){modelsummary::modelsummary(m_district_policy_cross)}

coefplot(m_district_policy_cross, horiz = T, drop = "(Intercept)", sub = "Reference = A member serving >6 years") 


m_district_policy_cross_party <- feglm(perYear_policy ~
  new_member*same_party + 
  second*same_party + third*same_party + fourth*same_party + fifth*same_party + sixth*same_party | Year,
cluster = "District",
data = dcounts_per_district)

if(testing){modelsummary::modelsummary(m_district_policy_cross)}

coefplot(m_district_policy_cross_party, horiz = T, drop = "(Intercept)", sub = "Reference = A member serving >6 years") 


# Diff in diff
m_district_policy_dnd <- feglm(perYear_policy ~
  new_member + #same_party + 
  second + third + fourth + fifth + sixth | 
    District + Year,
cluster = "District",
data = dcounts_per_district )

coefplot(m_district_policy_dnd, horiz = T, drop = "(Intercept)", sub = "Reference = A member serving >6 years") 

# Diff in diff
m_district_policy_dnd_party <- feglm(perYear_policy ~
  new_member*same_party + 
  second*same_party + third*same_party + fourth*same_party + fifth*same_party + sixth*same_party | 
    District + Year,
cluster = "District",
data = dcounts_per_district )

coefplot(m_district_policy_dnd_party, horiz = T, drop = "(Intercept)", sub = "Reference = A member serving >6 years") 



# Diff in diff
m_district_policy_dnd_house <- feglm(perYear_policy ~
  new_member + #same_party + 
  second + third + fourth + fifth + sixth | 
    District + Year,
cluster = "District",
data = dcounts_per_district |> filter(chamber == "House"))


# Diff in diff + party change var 
m_district_policy_dnd_house_party <- feglm(perYear_policy ~
  new_member*same_party + 
  second*same_party + third*same_party + fourth*same_party + fifth*same_party + sixth*same_party | 
    District + Year,
cluster = "District",
data = dcounts_per_district |> filter(chamber == "House"))

# Diff in diff
m_district_policy_dnd_senate <- feglm(perYear_policy ~
  new_member + #same_party + 
  second + third + fourth + fifth + sixth | 
    District + Year,
cluster = "District",
data = dcounts_per_district |> filter(chamber == "Senate"))


# Diff in diff + party change var 
m_district_policy_dnd_senate_party <- feglm(perYear_policy ~
  new_member*same_party + 
  second*same_party + third*same_party + fourth*same_party + fifth*same_party + sixth*same_party | 
    District + Year,
cluster = "District",
data = dcounts_per_district |> filter(chamber == "Senate"))

1

2

3

4

figs/m-district-policy-[1:4].png

Policy Per District Tables

  • Models: models/models_district_policy.Rdata
Code
models_district_policy <- list(
  "(1)" = m_district_policy_cross,
  "(2)" = m_district_policy_dnd,
  "(3)" = m_district_policy_dnd_house,
  "(4)" = m_district_policy_dnd_senate
)


rows <- tibble(
  term = c("Dependent variable", 
           "All districts",
           "House only",
           "Senate only"),
  `(1)` = c("Per year", 
            "✓", 
            "", 
            ""),
  `(2)` =c("Per year", 
           "✓", 
            "", 
            ""),
    `(3)` = c("Per year", 
            "", 
            "✓", 
            ""),
  `(4)` =c("Per year", 
           #"✓", 
           "", 
            "", 
            "✓")
)



# Check marks for controls 
attr(rows, 'position') <- c(0, 14:16)



modelsummary(models_district_policy, 
             notes = list("Robust standard errors in parentheses, clustered by district.") ) 
(1) (2) (3) (4)
† p < 0.1, * p < 0.05, ** p < 0.01
Robust standard errors in parentheses, clustered by district.
Dependent variable Per year Per year Per year Per year
New member -8.410** -7.247** -4.591** -19.843**
(0.641) (0.714) (0.484) (2.268)
Second year -6.417** -5.254** -2.920** -16.738**
(0.628) (0.671) (0.477) (2.460)
Third year -5.747** -4.638** -2.385** -12.948**
(0.678) (0.763) (0.545) (2.675)
Fourth year -4.372** -3.262** -1.188* -9.301**
(0.704) (0.771) (0.539) (2.882)
Fifth year -4.447** -3.284** -1.892** -7.446*
(0.672) (0.724) (0.463) (2.816)
Sixth year -3.430** -2.268** -1.197* -4.231
(0.793) (0.776) (0.468) (3.236)
All districts
House only
Senate only
Observations 7,666 7,666 6,224 1,442
District fixed effects X X X
Year fixed effects X X X X
Code
beta <- m_district_policy_dnd$coefficients %>%round(3) %>% as_tibble(rownames = "beta") %>% pivot_wider(names_from = beta)

beta_cross <- m_district_policy_dnd$coefficients %>%round(3) %>% as_tibble(rownames = "beta") %>% pivot_wider(names_from = beta)


se <- m_district_policy_dnd$se %>%round(3) %>% as_tibble(rownames = "se") %>% pivot_wider(names_from = se)

save(models_district_policy, rows, cm, gm,  beta, se,
     file = here::here("models", "models_district_policy.Rdata"))
  • Models: models/models_district_policy_party.Rdata
Code
models_district_policy_party <- list(
  "(1)" = m_district_policy_cross_party,
  "(2)" = m_district_policy_dnd_party,
  "(3)" = m_district_policy_dnd_house_party,
  "(4)" = m_district_policy_dnd_senate_party
)

# Check marks for controls 
attr(rows, 'position') <- c(0, 28:31)

modelsummary(models_district_policy_party, 
             notes = list("Robust standard errors in parentheses, clustered by district.") ) 
(1) (2) (3) (4)
† p < 0.1, * p < 0.05, ** p < 0.01
Robust standard errors in parentheses, clustered by district.
Dependent variable Per year Per year Per year Per year
New member -12.772** -12.071** -1.542 -20.611**
(2.503) (2.083) (1.316) (3.734)
New member x same party -4.850† -4.189* -0.912 -5.766
(2.686) (2.092) (1.177) (3.848)
Second year -9.224** -8.809** 1.737 -12.891*
(2.744) (2.274) (1.064) (5.343)
Third year -11.108** -8.692** -0.778 -5.372
(2.410) (2.060) (1.239) (5.610)
Fourth year -8.041** -5.944** 1.526 -1.825
(2.471) (2.231) (0.961) (6.108)
Fifth year -6.584** -2.986 -0.611 10.673
(1.924) (2.167) (0.960) (10.190)
Sixth year -1.978 1.221 2.669* 1.311
(2.613) (2.319) (1.312) (10.414)
Second year x same party -3.237 -2.710 -0.249 -6.996
(2.635) (2.163) (1.242) (5.312)
Third year x same party -2.214 -2.384 0.510 -13.233*
(2.637) (2.351) (1.262) (6.031)
Fourth year x same party -0.141 -0.619 1.327 -11.165†
(2.416) (2.372) (1.181) (6.260)
Fifth year x same party -3.510 -4.411 -0.562 -22.230*
(2.365) (2.773) (1.070) (10.544)
Sixth year x same party -2.488 -3.328 -0.950 -7.539
(2.981) (2.755) (1.531) (10.532)
Same party 2.926 -0.489 0.495 3.026
(2.738) (2.788) (1.390) (3.333)
All districts
House only
Senate only
Observations 2,222 2,222 1,523 699
District fixed effects X X X
Year fixed effects X X X X
Code
save(models_district_policy_party, rows, cm, gm,  beta, se,
     file = here::here("models", "models_district_policy_party.Rdata"))

Policy Per District Narrative

To account for differences in district size, demographics, and demand for Policy, Column 2 of Table @tbl-models_district_policy provides the estimated effects from the difference-in-differences specification from Equation @eq-district1. In this specification, we see a large causal effect of a new member taking over: Electing a new member causes a district’s Policy to decrease by 7.25 letters per year (95-percent confidence interval [-5.85, -8.65 ]).

The effect of electing a new representative, however, dissipates quickly. Districts represented by a legislator in their second year of service receive significantly fewer policy-related contacts with federal agencies, but not as drastic as the difference observed in the first year. After the second year, the differences are smaller. This phenomenon–new legislators providing substantially fewer requests–persists when examining the House (Column 3) and the Senate (Column 4) separately. In short, new legislators make fewer policy-related contacts than established legislators.

Policy Per District Predictions

  • Figures: figs/m-district-policy-predicted-[1:4].png
Code
ggdistrict <- function(predicted = predicted, party = FALSE) {
  
predicted %<>%
  # drop estimates from impossible values
      group_by(rowid, 
               #predicted, std.error, 
               estimate, conf.low, conf.high,
               same_party) %>% 
  mutate(sum = sum(new_member, second, third, fourth, fifth, sixth),
         more = ifelse(sum == 0, 1,0)) %>% 
    filter(sum < 2) %>% 
    pivot_longer(cols = c("new_member", "second", "third", "fourth", "fifth", "sixth", "more")) %>% 
    select(name, value) %>% 
    filter(value == 1) %>% 
    # clean up for presentation
    mutate(year_in_policygress = name %>% 
             str_replace("more", "7\nor more") %>% 
             str_replace("sixth", "6") %>% 
             str_replace("fifth", "5") %>% 
             str_replace("fourth", "4") %>% 
             str_replace("third", "3") %>% 
             str_replace("second", "2") %>% 
             str_replace("new_member", " New\nmember") 
             ) %>% 
    ungroup()
  
# Ideally, we could plot against data, but there is so much variation that you can no longer distinguish differences in predicted values 
p <- predicted %>%  
  ungroup() %>% 
  ggplot() + 
  aes(x = year_in_policygress, 
            y = estimate, # predicted, 
      ymin = conf.low,# predicted - 1.96*std.error,
      ymax = conf.high) + 
  geom_pointrange(position =  position_dodge(width = -.3)  )  + 
  # geom_line() + # requires numeric...look to see how I did this on the other ones
  scale_fill_viridis_d(begin = 0, end = .6, option = "cividis") +
  scale_color_viridis_d(begin = 0, end = .6, option = "cividis") 

if(party){
p <- predicted %>%  
  mutate(same_party = as.logical(same_party)) %>% 
  ungroup() %>% 
  ggplot() + 
  aes(x = year_in_policygress, 
            y = estimate, # predicted, 
      ymin = conf.low,# predicted - 1.96*std.error,
      ymax = conf.high, #predicted + 1.96*std.error,
      shape = same_party,
      color = same_party) + 
  geom_pointrange(position =  position_dodge(width = -.3)  )  + 
  scale_fill_viridis_d(begin = 0, end = .6, option = "cividis") +
  scale_color_viridis_d(begin = 0, end = .6, option = "cividis") 
}

return(p)
  
}


# dcounts_per_district %>% filter(District == "tennessee_1_1") |> View()
# dcounts_per_district %>% filter(District == "alabama_1_1") |> View()


# by year 
values <- tidyr::expand(dcounts_per_district,
                        new_member = new_member,
                        second = second,
                        third = third,
                        fourth = fourth,
                        fifth = fifth, 
                        sixth = sixth,
                        Legislator = "21376",
                        Year = "2016",
                        District = "alabama_1_1",
                        same_party = same_party
                   ) %>% 
  drop_na(same_party)

predicted <- predictions(m_district_policy_cross,
                         newdata = values) 


# Cross-sectional predictions
predicted %>%
ggdistrict() +
  labs(#title = "Letters\nper district\n(cross-sectional)",
       x = "Years serving in Congress",
       y = "   Predicted letters per year\n(policy only)\nper district\n(cross-sectional)",
       fill = "",
       color = "",
       shape = "") 



# Predictions with the party of the prior representative
predicted <- predictions(m_district_policy_cross_party,
                         newdata = values) 

predicted %>%
  ggdistrict(party = TRUE) + 
  labs(#title = "Letters\nper district\n(cross-sectional)",
       x = "Years serving in Congress",
              y = "   Predicted letters per year\n(policy only)\nper district\n(cross-sectional)",
       fill = "",
       color = "Same party",
       shape = "Same party") 




# diff in diff plot
predicted <- predictions(m_district_policy_dnd,
                         newdata = values) 


predicted %>%
ggdistrict() +
      geom_line(aes(x = as.numeric(year_in_policygress %>% str_replace(" N", "1") %>% str_sub(1,1) ))) + 
  labs(#title = "Letters\nper district\n(difference-in-differences)",
       x = "Years serving in Congress",
              y = "   Predicted letters per year\n(policy only)\nper district\n(difference-in-differneces)",
       fill = "",
       color = "",
       shape = "")



# Predictions with the Same party  
predicted <- predictions(m_district_policy_dnd_party,
                         newdata = values)

# diff in diff plot
predicted %>%
  ggdistrict(party = TRUE) +
        geom_line(aes(x = as.numeric(year_in_policygress %>% str_replace(" N", "1") %>% str_sub(1,1) ))) + 
  labs(#title = "Letters Per District\n(difference-in-differences)",
       x = "Years serving in Congress",
              y = "   Predicted letters per year\n(policy only)\nper district\n(difference-in-differences)",
       fill = "",
       color = "Same party",
       shape = "Same party")

1

2

3

4

figs/m-district-policy-predicted-[1:4].png

Policy Per District Predictions (House only)

  • Figures: figs/m-district-policy-predicted-house-[1:2].png
Code
# diff in diff plot
predicted <- predictions(m_district_policy_dnd_house,
                         newdata = values)


predicted %>%
ggdistrict() +
        geom_line(aes(x = as.numeric(year_in_policygress %>% str_replace(" N", "1") %>% str_sub(1,1) ))) + 
  labs(#title = "Letters\nper House district\n(difference-in-differences)",
       x = "Years serving in Congress",
              y = "   Predicted letters per year\n(policy only)\nper House district\n(difference-in-differneces)",
       fill = "",
       color = "",
       shape = "")



# Predictions with the Same party  
predicted <- predictions(m_district_policy_dnd_house_party,
                         newdata = values)
# diff in diff plot
predicted %>%
  ggdistrict(party = TRUE) +
        geom_line(aes(x = as.numeric(year_in_policygress %>% str_replace(" N", "1") %>% str_sub(1,1) ))) + 
  labs(#title = "Letters\nper House district\n(difference-in-differences)",
       x = "Years serving in Congress",
              y = "   Predicted letters per year\n(policy only)\nper House district\n(difference-in-differences)",
       fill = "",
       color = "Same party",
       shape = "Same party")

1

2

figs/m-district-policy-predicted-house-[1:2].png

Policy Per District Predictions (Senate only)

  • Figures: figs/m-district-policy-predicted-senate-[1:2].png
Code
# Values for Senate seats 
values <- tidyr::expand(dcounts_per_district,
                        new_member = new_member,
                        second = second,
                        third = third,
                        fourth = fourth,
                        fifth = fifth, 
                        sixth = sixth,
                        Legislator = "49700",
                        Year = "2017",
                        District = "alabama_0",
                        same_party = same_party
                   ) %>% 
  drop_na(same_party)

# diff in diff plot
predicted <- predictions(m_district_policy_dnd_senate,
                         newdata = values) 


predicted %>%
ggdistrict() +
        geom_line(aes(x = as.numeric(year_in_policygress %>% str_replace(" N", "1") %>% str_sub(1,1) ))) + 
  labs(#title = "Letters per senator\n(difference-in-differences)",
       x = "Years serving in Congress",
              y = "   Predicted letters per year\n(policy only)\nper senator\n(difference-in-differneces)",
       fill = "",
       color = "",
       shape = "")



# Predictions with the Same party  
predicted <- predictions(m_district_policy_dnd_senate_party,
                         newdata = values) 

# diff in diff plot
predicted %>%
  ggdistrict(party = TRUE) +
        geom_line(aes(x = as.numeric(year_in_policygress %>% str_replace(" N", "1") %>% str_sub(1,1) ))) + 
  labs(#title = "Letters per senator\n(difference-in-differences)",
       x = "Years serving in Congress",
              y = "   Predicted letters per year\n(policy only)\nper senator\n(difference-in-differences)",
       fill = "",
       color = "Same party",
       shape = "Same party")

1

2

figs/m-district-policy-predicted-senate-[1:2].png

Model Results: Spillover Effects Robustness Check

This is a robustness check for an observable implication of demand-driven behavior. If variation in constituent demand drives variation in legislator behavior (specifically a preference for more senior members), then we should observe spillover effects on the rest of a delegation when an experienced member is replaced with a new member. That is, if people demand more from experienced members, people should redirect demand to the next most experienced member who represents them.

Code
# new IVs: new_senator, new_proportion, new_one

dcounts_spillover <- dcounts_per_district %>% 
  # FIRST, identify districts with new members 
  group_by(state, Year) %>% 
  # size of the delegation 
  add_count(name = "n_delegation") %>% 
  mutate(
    # new IVs:
    new_senator = ifelse(chamber == "Senate" & new_member == 1, 1, NA), 
    new_proportion = sum(new_member)/n_delegation, 
    new_one = ifelse(new_member == 1, 1, NA)
  ) %>% 
  fill(new_senator, .direction = "updown") %>% 
  fill(new_one, .direction = "updown") %>% 
  mutate(new_senator =   replace_na(new_senator, 0),
         new_one = replace_na(new_one, 0) 
  ) %>% 
  group_by(state, Year, chamber) %>% 
  arrange(year, state) 

  # THEN WE DROP ALL OF THE NEW MEMBERS 
dcounts_spillover %<>%   filter(new_member == 0)  


# # inspect data 
# dcounts_spillover |> select(Year, state, chamber, new_member, new_one, new_senator, new_proportion, perYear) |> ungroup() |>
#   #filter(new_senator ==1) |>  
#   #count(new_senator, new_member) |> 
#   kablebox_long()
#     

Spillover Effect: Total

Code
m_spillover <- feglm(perYear ~
  new_senator  | Year + District,
cluster = "District",
data = dcounts_spillover)

# coefplot(m_spillover, horiz = T, drop = "(Intercept)", sub = "") 

m_spillover_proportion <- feglm(perYear ~
  new_proportion  | Year + District,
cluster = "District",
data = dcounts_spillover |> filter(chamber == "Senate"))

# coefplot(m_spillover_proportion, horiz = T, drop = "(Intercept)", sub = "") 

m_spillover_new_one <- feglm(perYear ~
  new_one  | Year + District,
cluster = "District",
data = dcounts_spillover |> filter(chamber == "Senate"))

# coefplot(m_spillover_new_one, horiz = T, drop = "(Intercept)", sub = "") 

figs/m-spillover-[1:3].png

  • Models: models/models_spillover.Rdata
Code
models_spillover <- list(
  "(1)" = m_spillover,
  "(2)" = m_spillover_proportion,
  "(3)" = m_spillover_new_one
)


rows <- tibble(
  term = c("Dependent variable", 
           "All districts",
           "Senate only"),
  `(1)` = c("Per year", 
            "✓", 
            ""),
  `(2)` =c("Per year", 
            "", 
           "✓"),
    `(3)` = c("Per year", 
            "", 
            "✓")
)



# Check marks for controls 
attr(rows, 'position') <- c(0, 9:11)


modelsummary(models_spillover,
             notes = list("Robust standard errors in parentheses, clustered by district.") ) 
(1) (2) (3)
† p < 0.1, * p < 0.05, ** p < 0.01
Robust standard errors in parentheses, clustered by district.
Dependent variable Per year Per year Per year
New senator in delegation 3.412
(2.565)
New member in delegation -14.839*
(6.874)
New proportion in delegation -16.719
(18.696)
Observations 7,074 1,380 1,380
All districts
Senate only
District fixed effects X X X
Year fixed effects X X X
Code
save(models_spillover, rows, cm, gm,  #beta, se,
     file = here::here("models", "models_spillover.Rdata"))

Spillover Effect: Constituency Service

Code
m_spillover_con <- feglm(perYear_con ~
  new_senator  | Year + District,
cluster = "District",
data = dcounts_spillover)

# coefplot(m_spillover, horiz = T, drop = "(Intercept)", sub = "") 

m_spillover_con_proportion <- feglm(perYear_con ~
  new_proportion  | Year + District,
cluster = "District",
data = dcounts_spillover |> filter(chamber == "Senate"))

# coefplot(m_spillover_proportion, horiz = T, drop = "(Intercept)", sub = "") 

m_spillover_con_new_one <- feglm(perYear_con ~
  new_one  | Year + District,
cluster = "District",
data = dcounts_spillover |> filter(chamber == "Senate"))

# coefplot(m_spillover_new_one, horiz = T, drop = "(Intercept)", sub = "") 

figs/m-spillover-con-[1:3].png

  • Models: models/models_spillover.Rdata
Code
models_spillover_con <- list(
  "(1)" = m_spillover_con,
  "(2)" = m_spillover_con_proportion,
  "(3)" = m_spillover_con_new_one
)


rows <- tibble(
  term = c("Dependent variable", 
           "All districts",
           "Senate only"),
  `(1)` = c("Per year (CS-only)", 
            "✓", 
            ""),
  `(2)` =c("Per year (CS-only)", 
            "", 
           "✓"),
    `(3)` = c("Per year (CS-only)", 
            "", 
            "✓")
)



# Check marks for controls 
attr(rows, 'position') <- c(0, 9:11)


modelsummary(models_spillover_con,
             notes = list("Robust standard errors in parentheses, clustered by district.") ) 
(1) (2) (3)
† p < 0.1, * p < 0.05, ** p < 0.01
Robust standard errors in parentheses, clustered by district.
Dependent variable Per year (CS-only) Per year (CS-only) Per year (CS-only)
New senator in delegation 3.455†
(2.077)
New member in delegation -12.535*
(6.051)
New proportion in delegation 0.583
(15.486)
Observations 7,074 1,380 1,380
All districts
Senate only
District fixed effects X X X
Year fixed effects X X X
Code
save(models_spillover_con, rows, cm, gm,  #beta, se,
     file = here::here("models", "models_spillover_con.Rdata"))
Code
models_spillover_con
$`(1)`
GLM estimation, family = gaussian, Dep. Var.: perYear_con
Observations: 7,074 
Fixed-effects: Year: 14,  District: 932
Standard-errors: Clustered (District) 
            Estimate Std. Error t value Pr(>|t|)    
new_senator  3.45519    2.07692 1.66361 0.096527 .  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Log-Likelihood: -40,271.1   Adj. Pseudo R2: 0.037264
           BIC:  88,927.7     Squared Cor.: 0.512379

$`(2)`
GLM estimation, family = gaussian, Dep. Var.: perYear_con
Observations: 1,380 
Fixed-effects: Year: 14,  District: 50
Standard-errors: Clustered (District) 
               Estimate Std. Error  t value Pr(>|t|) 
new_proportion 0.583325     15.486 0.037668  0.97011 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Log-Likelihood: -8,475.1   Adj. Pseudo R2: 0.049869
           BIC: 17,412.9     Squared Cor.: 0.523258

$`(3)`
GLM estimation, family = gaussian, Dep. Var.: perYear_con
Observations: 1,380 
Fixed-effects: Year: 14,  District: 50
Standard-errors: Clustered (District) 
        Estimate Std. Error t value Pr(>|t|)    
new_one -12.5352    6.05125 -2.0715 0.043598 *  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Log-Likelihood: -8,474.4   Adj. Pseudo R2: 0.049947
           BIC: 17,411.5     Squared Cor.: 0.523742

Spillover Effect: Policy

Code
m_spillover_policy <- feglm(perYear_policy ~
  new_senator  | Year + District,
cluster = "District",
data = dcounts_spillover)

# coefplot(m_spillover, horiz = T, drop = "(Intercept)", sub = "") 

m_spillover_policy_proportion <- feglm(perYear_policy ~
  new_proportion  | Year + District,
cluster = "District",
data = dcounts_spillover |> filter(chamber == "Senate"))

# coefplot(m_spillover_proportion, horiz = T, drop = "(Intercept)", sub = "") 

m_spillover_policy_new_one <- feglm(perYear_policy ~
  new_one  | Year + District,
cluster = "District",
data = dcounts_spillover |> filter(chamber == "Senate"))

# coefplot(m_spillover_new_one, horiz = T, drop = "(Intercept)", sub = "") 

figs/m-spillover-policy-[1:3].png

  • Models: models/models_spillover.Rdata
Code
models_spillover_policy <- list(
  "(1)" = m_spillover_policy,
  "(2)" = m_spillover_policy_proportion,
  "(3)" = m_spillover_policy_new_one
)


rows <- tibble(
  term = c("Dependent variable", 
           "All districts",
           "Senate only"),
  `(1)` = c("Per year (Policy-only)", 
            "✓", 
            ""),
  `(2)` =c("Per year (Policy-only)", 
            "", 
           "✓"),
    `(3)` = c("Per year (Policy-only)", 
            "", 
            "✓")
)



# Check marks for controls 
attr(rows, 'position') <- c(0, 9:11)


modelsummary(models_spillover_policy,
             notes = list("Robust standard errors in parentheses, clustered by district.") ) 
(1) (2) (3)
† p < 0.1, * p < 0.05, ** p < 0.01
Robust standard errors in parentheses, clustered by district.
Dependent variable Per year (Policy-only) Per year (Policy-only) Per year (Policy-only)
New senator in delegation -0.090
(0.526)
New member in delegation -1.104
(1.357)
New proportion in delegation -7.488†
(3.775)
Observations 7,074 1,380 1,380
All districts
Senate only
District fixed effects X X X
Year fixed effects X X X
Code
save(models_spillover_policy, rows, cm, gm,  #beta, se,
     file = here::here("models", "models_spillover_policy.Rdata"))

Computing Environment and Required Packages

Code
sessionInfo()
R version 4.3.0 (2023-04-21)
Platform: aarch64-apple-darwin20 (64-bit)
Running under: macOS Ventura 13.4.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.11.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/Los_Angeles
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] scales_1.4.0           ggrepel_0.9.3          kableExtra_1.4.0      
 [4] ineq_0.2-13            magrittr_2.0.3         lubridate_1.9.2       
 [7] forcats_1.0.0          stringr_1.5.1          dplyr_1.1.4           
[10] purrr_1.0.4            readr_2.1.4            tidyr_1.3.0           
[13] tibble_3.2.1           ggplot2_3.5.2          tidyverse_2.0.0       
[16] fixest_0.11.1          marginaleffects_0.12.0 modelsummary_2.2.0    

loaded via a namespace (and not attached):
 [1] gtable_0.3.3        bayestestR_0.15.0   xfun_0.52          
 [4] htmlwidgets_1.6.2   insight_1.0.1       rstatix_0.7.2      
 [7] lattice_0.21-8      tzdb_0.4.0          numDeriv_2016.8-1.1
[10] vctrs_0.6.5         tools_4.3.0         generics_0.1.3     
[13] datawizard_1.0.0    sandwich_3.0-2      fansi_1.0.6        
[16] pkgconfig_2.0.3     Matrix_1.6-4        checkmate_2.3.2    
[19] tinytable_0.6.1     data.table_1.16.4   RColorBrewer_1.1-3 
[22] lifecycle_1.0.4     compiler_4.3.0      farver_2.1.1       
[25] carData_3.0-5       htmltools_0.5.8.1   yaml_2.3.10        
[28] Formula_1.2-5       car_3.1-2           pillar_1.10.2      
[31] ggpubr_0.6.0        abind_1.4-5         nlme_3.1-162       
[34] tidyselect_1.2.1    digest_0.6.37       performance_0.13.0 
[37] stringi_1.8.7       splines_4.3.0       labeling_0.4.2     
[40] rprojroot_2.0.4     fastmap_1.2.0       grid_4.3.0         
[43] here_1.0.1          cli_3.6.5           utf8_1.2.4         
[46] broom_1.0.5         withr_3.0.2         dreamerr_1.2.3     
[49] backports_1.4.1     timechange_0.2.0    rmarkdown_2.29     
[52] ggsignif_0.6.4      zoo_1.8-12          hms_1.1.3          
[55] evaluate_1.0.3      knitr_1.50          parameters_0.24.1  
[58] viridisLite_0.4.2   mgcv_1.8-42         rlang_1.1.6        
[61] Rcpp_1.0.14         glue_1.8.0          xml2_1.3.4         
[64] svglite_2.1.1       rstudioapi_0.17.1   jsonlite_2.0.0     
[67] R6_2.6.1            tables_0.9.17       systemfonts_1.0.4  

Duration of time to process the job from start to finish

Code
Sys.time() - start_time
Time difference of 1.27248 mins