Voting on Separating Chair and CEO

Data step

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

rs <- dbGetQuery(pg, "DROP TABLE IF EXISTS destagger.split_fund_rank")
rs <- dbGetQuery(pg, "
    CREATE TABLE destagger.split_fund_rank AS
        SELECT instid, num_votes, rank() OVER (ORDER BY num_votes DESC) AS fund_rank
        FROM (
            SELECT instid, count(*) AS num_votes 
            FROM (
                SELECT DISTINCT instid, itemonagendaid 
                FROM issvoting.fund_votes
                WHERE issagendaitemid='S0107') AS b
            GROUP BY instid 
            ORDER BY num_votes DESC) AS a")

# Pull in votes from ISS Voting Analytics 
rs <- dbGetQuery(pg, "DROP TABLE IF EXISTS destagger.split_votes")
sql <- "
    CREATE TABLE destagger.split_votes AS
    SELECT * FROM issvoting.compvotes AS a
    LEFT JOIN (        
        SELECT itemonagendaid, min(fund_rank) AS rank_fund_against
        FROM issvoting.fund_votes AS a
        INNER JOIN destagger.fund_rank AS b
        USING (instid)
        WHERE fundvote='Against' 
        GROUP BY itemonagendaid) AS b
    USING (itemonagendaid)
    WHERE issagendaitemid='S0107'
    ORDER BY issagendaitemid"
rs <- dbGetQuery(pg, sql)

# Add PERMNO and GVKEY to the table (match to CUSIP)
rs <- dbGetQuery(pg, 
                 "ALTER TABLE destagger.split_votes ADD COLUMN gvkey character(6)")
rs <- dbGetQuery(pg,
                 "ALTER TABLE destagger.split_votes ADD COLUMN permno integer")
rs <- dbGetQuery(pg,
    "UPDATE destagger.split_votes AS a SET gvkey = 
        (SELECT gvkey FROM destagger.gvkeys AS b 
        WHERE b.cusip=a.cusip)")
rs <- dbGetQuery(pg,
    "UPDATE destagger.split_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.split_votes")
dbDisconnect(pg)
## [1] TRUE
library(RPostgreSQL)
drv <- dbDriver("PostgreSQL")
pg <- dbConnect(drv, dbname = "crsp")

# 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.10 seconds
## Identifying relevant trading dates...                 0.03 seconds
## Compounding raw returns...                            0.21 seconds
## Compounding value-weighted market returns...          0.01 seconds
## Compounding size-decile market returns...             0.01 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", "split_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.split_votes AS a
  LEFT JOIN destagger.split_ret_data AS b
  USING (permno)
  WHERE a.meetingdate=b.event_date AND result IN ('Fail', 'Pass')")
dbDisconnect(pg)
## [1] TRUE

Some descriptive statistics

Does ISS ever vary in its recommendation? Quite a bit, but it's “for” in about 68.78% of cases.

with(declassify, table(mgtrec, issrec))
##          issrec
## mgtrec    Against Do Not Vote For NULL None
##   Against      68           1 162    1    3
##   For           0           1   1    0    0

There are 237 votes on majority voting in the sample, with 94.09% 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    4    0
##   2002    2    0
##   2003   22    0
##   2004   35    0
##   2005   23    1
##   2006   49    2
##   2007   36    5
##   2008   25    1
##   2009   27    5

The votes are not smoothly 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), 
      binwidth=.01,
      main="Histogram: Shareholder support minus vote requirement",
      xlab="Shareholder support minus vote requirement")

plot of chunk unnamed-chunk-1

But there a hints of a 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),
      main="Market-adjusted Stock Returns: Days (0,1) Around Meeting Dates",
      xlab="Shareholder support minus vote requirement",
      color=issrec)

plot of chunk unnamed-chunk-2


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

plot of chunk unnamed-chunk-2

Estimation and inference

The variable rank_fund_against represents the rank of the largest fund (based
on number of votes) voting against a given proposal. Most proposals are opposed by the
(one or more of) the largest funds, though some are supported by all of these funds.

source("~/Dropbox/research/rdd/rd_opt_bw.R")
# source('http://iangow.me/~iangow/rd_opt_bw.R') (4) Calculate the
# treatment effect and standard errors.
table(declassify$rank_fund_against)
## 
##   1   2   3   4   5   8  39  42 135 157 227 
## 135   5   1   2   1   3   1  14  40   1   2 
rd_data <- subset(declassify, subset = percent_for < 1 & percent_for > 
    0.01, select = c(ret_mkt, percent_for, voterequirements))
rd_data$fundvote <- 1
rd_data$x <- (rd_data$percent_for - rd_data$voterequirements) * rd_data$fundvote
rd_data$y <- rd_data$ret_mkt

# First, get bandwidth
h <- with(rd_data, rd_opt_bw(y, x, c = 0))
X <- rd_data$x
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.16076           0.04190           0.01402           2.98877 

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 = 12)))

# 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

Fund voting behavior

It seems there is quite a bit of variation in how mutual funds vote.

##                                 institutionname percent_for num_votes
## 1                Fidelity Management & Research     0.32792       310
## 2                            Transamerica Funds     0.43103       303
## 3                       John Hancock Funds, LLC     0.45985       294
## 4                      BlackRock Advisors, Inc.     0.32707       282
## 5                          Prudential Financial     0.61509       279
## 6                 AST Investment Services, Inc.     0.46591       275
## 7        Jackson National Asset Management, LLC     0.37453       271
## 8                                 Phoenix Funds     0.48031       263
## 9           T. Rowe Price Associates, Inc. (MD)     0.52778       257
## 10                   AIM Management Group, Inc.     0.48594       252
## 11            Northern Trust Global Investments     0.15812       239
## 12                   IQ Investment Advisors LLC     0.22439       237
## 13            Capital Research & Management Co.     0.31858       227
## 14                 Janus Capital Management LLC     0.67488       224
## 15             RS Investment Management Co. LLC     0.56696       224
## 16                    Northwestern Mutual Funds     0.52752       221
## 17        AIG SunAmerica Asset Management Corp.     0.58152       209
## 18                    Munder Capital Management     0.62500       209
## 19            General Electric Asset Management     0.03743       189
## 20      New York Life Investment Management LLC     0.61290       188
## 21                  RiverSource Investments LLC     0.73596       184
## 22                         Vanguard Group, Inc.     0.01099       183
## 23                            Rydex Investments     0.01714       180
## 24               TIAA-CREF Asset Management LLC     0.15000       179
## 25      Variable Annuity Life Insurance Company     0.56471       171
## 26 American Century Investment Management, Inc.     0.14013       159
## 27   Charles Schwab Investment Management, Inc.     0.43312       157
## 28    Evergreen Investment Management Co., Inc.     0.71533       142
## 29                     Nationwide Fund Advisors     0.48031       133
## 30                  Nationwide Fund<a0>Advisors     0.21951        83
## 31     Charles Schwab Investment Management, In     0.48837        44
## 32      American Century Investment Management,     0.07500        41
## 33     Evergreen Investment Management Co., Inc     0.44737        38
## 34                     Nationwide Fund Advisors     0.33333        37