Voting on Staggered Boards

Data step

# Connect to PostgreSQL database
library(RPostgreSQL)
drv <- dbDriver("PostgreSQL")
pg <- dbConnect(drv, dbname = "crsp")

# Pull in votes from ISS Voting Analytics 
rs <- dbGetQuery(pg, "DROP TABLE IF EXISTS destagger.votes")
rs <- dbGetQuery(pg, "
    CREATE TABLE destagger.votes AS
    SELECT * FROM issvoting.compvotes 
    WHERE itemdesc ILIKE '%declassify %' OR issagendaitemid IN ('M0215', 'S0201')
    ORDER BY issagendaitemid")

# Add PERMNO and GVKEY to the table (match to CUSIP)
rs <- dbGetQuery(pg, 
                 "ALTER TABLE destagger.votes ADD COLUMN gvkey character(6)")
rs <- dbGetQuery(pg,
                 "ALTER TABLE destagger.votes ADD COLUMN permno integer")
rs <- dbGetQuery(pg,
    "UPDATE destagger.votes AS a SET gvkey = 
        (SELECT gvkey FROM destagger.gvkeys AS b 
        WHERE b.cusip=a.cusip)")
rs <- dbGetQuery(pg,
    "UPDATE destagger.votes AS a SET permno = 
        (SELECT DISTINCT permno FROM crsp.stocknames AS b 
                 WHERE b.ncusip=substr(a.cusip,1,8))")

# Pull data into R
declassify <- dbGetQuery(pg, "SELECT * FROM destagger.votes")

# Get returns around meeting dates
dropbox.path <- path.expand("~/Dropbox/research/AGL/")
source(paste(dropbox.path,"Code/R/getEventReturns.R",sep=""))
ret_data <- getEventReturns(declassify$permno, declassify$meetingdate,
                            days.before=0, days.after=1)
## Initializing database connection...                   0.00 seconds
## Preparing data for upload to database...              0.00 seconds
## Writing list of PERMNOs and event dates to database...0.12 seconds
## Identifying relevant trading dates...                 0.18 seconds
## Compounding raw returns...                            7.84 seconds
## Compounding value-weighted market returns...          0.06 seconds
## Compounding size-decile market returns...             0.06 seconds
ret_data$row.names <- NULL
ret_data <- unique(ret_data) # Drop duplicates (multiple votes at a single meeting)

# Put return data back in the database
rs <- dbWriteTable(pg, c("destagger", "ret_data"), ret_data,
                   overwrite=TRUE, row.names=FALSE)

# Get data including a measure of shareholder support relative to "base"
# (e.g., shares outstanding) specified for each vote.
declassify <- dbGetQuery(pg, "SELECT *,
  CASE WHEN base='Outstanding' AND outstandingshares>0 THEN votedfor/outstandingshares
    WHEN base='F+A' AND votedfor+votedagainst>0
      THEN votedfor/(votedfor+votedagainst)
    WHEN base='F+A+AB' AND votedfor+votedagainst+votedabstain>0 
      THEN votedfor/(votedfor+votedagainst+votedabstain)
    END AS percent_for
  FROM destagger.votes AS a
  LEFT JOIN destagger.ret_data AS b
  USING (permno)
  WHERE a.meetingdate=b.event_date AND result IN ('Fail', 'Pass')")

Some descriptive statistics

There are 765 votes on declassification in the sample, with 18.43% being rejections by shareholders. Here they are by year.

## Descriptive statistics
declassify$year <- format(declassify$meetingdate, "%Y")
with(declassify, table(year, result))
##       result
## year   Fail Pass
##   2001   19   32
##   2002   18   33
##   2003   13   58
##   2004    8   70
##   2005   11   83
##   2006   10   99
##   2007   13   65
##   2008   20  108
##   2009   29   76

The votes are fairly well dispersed around the cutoff.
Note that these are only shareholder-sponsored votes.

# Produce a histogram of (SVT - allowable cap)
library(ggplot2)
qplot(percent_for - voterequirements, 
      data=subset(declassify,
                  percent_for <1 & percent_for > 0.01 & sponsor !="Management"), 
      binwidth=.01,
      main="Histogram: Shareholder support minus vote requirement",
      xlab="Shareholder support minus vote requirement")

plot of chunk unnamed-chunk-1

But there's virtually no response by the market to the result of the vote.

qplot(percent_for - voterequirements, ret_mkt,
      data=subset(declassify, 
                  percent_for <1 & percent_for > 0.01 & sponsor !="Management"),
      main="Market-adjusted Stock Returns: Days (0,1) Around Meeting Dates",
      xlab="Shareholder support minus vote requirement")

plot of chunk unnamed-chunk-2

Estimation and inference

source("http://iangow.me/~iangow/rd_opt_bw.R")
### (4) Calculate the treatment effect and standard errors.
rd_data <- subset(declassify, subset = percent_for < 1 & percent_for > 
    0.01 & sponsor != "Management", select = c(ret_mkt, percent_for, voterequirements))
rd_data$x <- rd_data$percent_for - rd_data$voterequirements
rd_data$y <- rd_data$ret_mkt

# First, get bandwidth
h <- with(rd_data, rd_opt_bw(y, x, c = 0))
X <- rd_data$percent_for - rd_data$voterequirements
c <- 0

## Weights based on triangular kernel
wgts <- with(rd_data, (1 - abs(X - c)/h) * (abs(X - c) <= h))
local.lm <- lm(y ~ (x >= c) * x, weights = wgts, data = rd_data)

rd.est <- coef(local.lm)[[2]]
sd.est <- sqrt(vcov(local.lm)[2, 2])

## Output
out <- c(h, rd.est, sd.est, rd.est/sd.est)
names(out) <- c("Optimal Bandwidth", "RD Estimate", "Standard Error", 
    "t-statistic")
print(out)
## Optimal Bandwidth       RD Estimate    Standard Error       t-statistic 
##          0.187689          0.002056          0.007844          0.262074 

A plot of the data with fitted values

# A function to estimate local linear regression around a point (x_i)
# using bandwidth h, the triangular kernel and limiting observations to
# those on the same side of the cutoff c. In some sense, this is purely
# visual, as the IK bandwidth is optimized for x \in [c-h, c+h] and other
# data are not involved.
ll <- function(x_i, y, x, h, c) {
    wgts <- (1 - abs(x - x_i)/h) * (abs(x - x_i) <= h) * (sign(x_i - c) == sign(x - 
        c) | x_i == c)
    lm.fitted <- lm(y ~ x, weights = wgts)
    if (sum(wgts > 0) > 10) {
        # Require 10 observations around x_i
        return(predict(lm.fitted, newdata = data.frame(x = x_i)))
    } else {
        return(NA)
    }
}

# Add the fitted value to the dataset
library(parallel)
rd_data$y_fitted <- with(rd_data, unlist(mclapply(X, ll, y, X, h, 
    c, mc.cores = 8)))

# Make a plot
library(ggplot2)
ggplot(rd_data, aes(x)) + geom_point(aes(y = y, color = x > c)) + 
    geom_line(aes(y = y_fitted, color = x > c))

plot of chunk plot