R package sov calculates vote-specific Shapley-Owen
values (vs-SOVs) and traditional Shapely-Owen values (SOVs) for
assemblies with weighted voting, various voting thresholds, and
different numbers of dimensions.
This program calculates vs-SOVs and traditional
SOVs in multidimensional space.
vs-SOVs utilize the “observed” normal vectors and their
reflections to determine the proportion of times a voter pivots.SOVs utilize all angles of the vote from 0
to 360 degrees for each dimension greater than 1.The package distinguishes between simple and absolute k-majority voting thresholds (Dougherty and Edward 2004):
q, which treats
abstentions as “nays.”pr, thereby it ignores
abstentions.You can toggle between these modes using the absolute
argument:
# To set an absolute threshold (e.g., US House majority):
vs_sov(..., absolute = TRUE, q = 218)
# To set a simple threshold (e.g., simple majority of those voting):
vs_sov(..., absolute = FALSE, pr = 0.5001)Functions vs_sov() and sov() use
package-estimated inputs from W-NOMINATE (wnominate),
Optimal Classification (oc), or MCMCpack
(MCMCpack), with a function identifying which input is
provided.
Functions vs_sov_user() and sov_user() use
ideal points, and other information, provided by the user.
You can install the development version of sov from GitHub with:
# install.packages("pak")
pak::pak("emmabbn/sov")
library(sov)The following examples provide an overview of the package’s core functions and the type of data that can be used in these examples. We use simplified models to clearly illustrate the geometry of the Shapley-Owen Value (SOV) and vote-specific Shapley-Owen Value (vs-SOV) calculations.
vs_sov_user()Here, we demonstrate the use of vs_sov_user() with the
simplest case: three voters in a one-dimensional policy space, voting on
two roll calls. We use the user-defined input function,
vs_sov_user(), to calculate the vs-SOVs under
simple majority rule.
The ideal points and roll call specifications for the first example are set up below. Ideal points are defined at 0.7, 0.0, and -0.7. The normal vectors of the roll calls point to the right (+1) and to the left (-1), respectively.
## --- Ideals: 3 voters in 1D -----------------------------------------------
i1 <- 0.7
i2 <- 0.0
i3 <- -0.7
ideals <- cbind(coord1D = c(i1, i2, i3))
rownames(ideals) <- paste0("i", 1:3)
## --- Normals: 2 roll calls (x+, x-) ---------------------------------------
nv1 <- 1; nv2 <- -1
normals <- cbind(dim1 = c(nv1, nv2))
rownames(normals) <- paste0("RC", 1:2)
## --- Votes: 1=yea, 0=nay --------------------------------------------------
votes <- cbind(
RC1 = c(1, 0, 0),
RC2 = c(0, 1, 1)
)
rownames(votes) <- rownames(ideals)
## --- Equal voting weights (each voter's vote is worth 1) -------------------
vw <- rep(1, nrow(ideals))As a reminder, this example illustrates a case with three voters in a
one-dimensional policy space, voting on two roll calls. We set the
required majority (\(pr\)) to be \(50\% + \epsilon\). The results show that
voter \(i_2\) (the median voter at 0.0)
has the highest VS-SOV, reflecting their central position
in this simple policy space.
##### EX1: Vote-specific SOV (simple majority among attendees in 1D) #####
out_ex1 <- vs_sov_user(
ideals = ideals,
normals = normals,
votes = votes,
absolute = FALSE, # simple k-majority
pr = 0.5001, # strict majority of attendees
vw = vw,
dec = 3
)
# aggregate results by member
out_ex1$pivot_summary
#> name coord1D num_pivots vs_sov
#> 1 i1 0.7 0 0
#> 2 i2 0.0 2 1
#> 3 i3 -0.7 0 0
# pivot name(s) by roll call:
out_ex1$pivot_by_rc
#> Position RC_num Pivot
#> 1 1 RC1 i2
#> 2 2 RC2 i2
# normal vectors and corresponding angles:
out_ex1$nv_and_angles
#> Position RC_num normVector1D Angle_Radians Angle_Degrees
#> 1 1 RC1 1 0.000000 0
#> 2 2 RC2 -1 3.141593 180
#### CONTENTS
# pivot_summary: VS-SOV (resp. SOV) for each voter.
# pivot_by_rc: pivot name(s) for each roll call (ties shown in multiple columns).
# nv_and_angles: the normal vector per roll call (resp. angles).
# Plot
labels_ex1 <- setNames(out_ex1$pivot_summary$vs_sov, out_ex1$pivot_summary$name)
sov:::plot_sov_geometry(
ideals = ideals,
normals = normals,
label_values = labels_ex1,
digits = 3,
main = "EX1: 1D VS-SOV (Simple Majority)"
)
vs_sov_user()Examples 2-4 expand the space to two dimensions (2D) using 5 voters and 3 roll calls. They illustrate the use of simple versus absolute majority rule (EX2 and EX4), the use of midpoints in place of normal vectors (EX2 and EX3), and a supermajority rule (EX4).
The ideal points and roll call specifications for examples 2-4 are set up below:
## --- Ideals: 5 voters in 2D -----------------------------------------------
i1 <- c( 0.7, 0.7)
i2 <- c(-0.5, 0.5)
i3 <- c(-0.7, -0.7)
i4 <- c( 0.5, -0.5)
i5 <- c( 0.0, 0.0)
ideals <- rbind(i1, i2, i3, i4, i5)
rownames(ideals) <- paste0("i", 1:5)
colnames(ideals) <- c("coord1D","coord2D")
## --- Normals: 3 roll calls (x+, y+, x-) -----------------------------------
nv1 <- c( 1, 0)
nv2 <- c( 0, 1)
nv3 <- c(-1, 0)
normals <- rbind(nv1, nv2, nv3)
rownames(normals) <- paste0("RC", 1:3)
## --- Votes: 1=yea, 0=nay, 9=attend but did not vote -----------------------
votes <- cbind(
RC1 = c(1,0,0,1,9), # Note voter i5 attends but doesn't vote (9)
RC2 = c(1,1,0,0,0),
RC3 = c(0,1,1,0,0)
)
rownames(votes) <- rownames(ideals)
## --- Equal voting weights (each voter's vote is worth 1) -------------------
vw <- rep(1, nrow(ideals))The second example illustrates the use of vs_sov_user()
in two-dimensional space, requiring the simple majority (\(pr\)) to be \(50\% + \epsilon\). The vs-SOV
calculation determines the influence of each voter based on the
proximity of their ideal point to the three roll call cut lines,
weighted by the voter’s attendance. Voter \(i_5\) (at the origin) pivots all the roll
calls among the attendees.
##### EX2: Simple majority example in 2D #####
out_ex2 <- vs_sov_user(
ideals = ideals,
normals = normals, # or use `midpoints = ...` if you prefer
votes = votes,
absolute = FALSE, # simple k-majority
pr = 0.5001, # strict majority of attendees
vw = vw,
dec = 3
)
# aggregate results by member
out_ex2$pivot_summary
#> name coord1D coord2D num_pivots vs_sov
#> 1 i1 0.7 0.7 0 0
#> 2 i2 -0.5 0.5 0 0
#> 3 i3 -0.7 -0.7 0 0
#> 4 i4 0.5 -0.5 0 0
#> 5 i5 0.0 0.0 3 1
# Per-roll-call pivot names:
out_ex2$pivot_by_rc
#> Position RC_num Pivot
#> 1 1 RC1 i5
#> 2 2 RC2 i5
#> 3 3 RC3 i5
# Normals + angles (degrees/radians) used for each roll call:
out_ex2$nv_and_angles
#> Position RC_num normVector1D normVector2D Angle_Radians Angle_Degrees
#> 1 1 RC1 1 0 0.000000 0
#> 2 2 RC2 0 1 1.570796 90
#> 3 3 RC3 -1 0 3.141593 180
# Plot
labels_ex2 <- setNames(out_ex2$pivot_summary$vs_sov, out_ex2$pivot_summary$name)
sov:::plot_sov_geometry(
ideals = ideals,
normals = normals,
label_values = labels_ex2,
digits = 3,
main = "EX2: 2D VS-SOV (Simple Majority, Normals)"
)
Example 3 demonstrates how midpoints can be used to infer the roll call geometry in place of normal vectors. A midpoint is the orthogonal intersection of the normal vector and cut hyperplane. A cut hyperplane separates the yeas from the nays based on observed votes. Again, voter \(i_5\) (at the origin) pivots all the roll calls among the attendees.
##### EX3: Using midpoints instead of normals in 2D #####
## --- Midpoints corresponding to the normal vectors above -------------------------
# Each row is where a cutplane intersects a normal vector
mid1 <- c( 0.1, 0.0) # RC1
mid2 <- c( 0.0, 0.1) # RC2
mid3 <- c(-0.1, 0.0) # RC3
midpoints <- rbind(mid1, mid2, mid3)
rownames(midpoints) <- paste0("RC", 1:3)
colnames(midpoints) <- c("coord1D","coord2D")
out_ex3 <- vs_sov_user(
ideals = ideals,
midpoints = midpoints, # <- used in place of normals
votes = votes,
absolute = FALSE,
pr = 0.5001,
vw = vw,
dec = 3
)
out_ex3$pivot_summary
#> name coord1D coord2D num_pivots vs_sov
#> 1 i1 0.7 0.7 0 0
#> 2 i2 -0.5 0.5 0 0
#> 3 i3 -0.7 -0.7 0 0
#> 4 i4 0.5 -0.5 0 0
#> 5 i5 0.0 0.0 3 1
out_ex3$pivot_by_rc
#> Position RC_num Pivot
#> 1 1 RC1 i5
#> 2 2 RC2 i5
#> 3 3 RC3 i5
out_ex3$nv_and_angles
#> Position RC_num normVector1D normVector2D Angle_Radians Angle_Degrees
#> 1 1 v1 1 0 0.000000 0
#> 2 2 v2 0 1 1.570796 90
#> 3 3 v3 -1 0 3.141593 180
# Plot
vs_labels_mid <- setNames(out_ex3$pivot_summary$vs_sov, out_ex3$pivot_summary$name)
sov:::plot_sov_geometry(
ideals = ideals,
midpoints = midpoints,
label_values = vs_labels_mid,
digits = 3,
main = "EX3: 2D VS-SOV (Simple Majority, Midpoints)"
)
Example 4 illustrates how vs-SOVs can be calculated with
an absolute supermajority rule threshold of 4/5th. Note the use of \(q\). Although this example is similar to
example 2, the 4/5ths rule makes \(i_2\) pivotal on the second and third roll
calls, while \(i_4\) is pivotal on the
first roll call – counting in the direction of the normal vector.
##### EX4: Supermajority (4/5ths) absolute example in 2D #####
out_ex4 <- vs_sov_user(
ideals = ideals,
normals = normals,
votes = votes,
absolute = TRUE, # absolute k-majority
q = 4, # 4 of 5
vw = vw,
dec = 3
)
out_ex4$pivot_summary
#> name coord1D coord2D num_pivots vs_sov
#> 1 i1 0.7 0.7 0 0.0000000
#> 2 i2 -0.5 0.5 2 0.6666667
#> 3 i3 -0.7 -0.7 0 0.0000000
#> 4 i4 0.5 -0.5 1 0.3333333
#> 5 i5 0.0 0.0 0 0.0000000
out_ex4$pivot_by_rc
#> Position RC_num Pivot
#> 1 1 RC1 i4
#> 2 2 RC2 i2
#> 3 3 RC3 i2
# Plot
vs_labels_ex4 <- setNames(out_ex4$pivot_summary$vs_sov, out_ex4$pivot_summary$name)
sov:::plot_sov_geometry(
ideals = ideals,
normals = normals,
label_values = vs_labels_ex4,
digits = 3,
main = "EX4: 2D VS-SOV (4/5ths)"
)
sov_user()Traditional SOVs involve integration over the entire
policy space. Functions sov_user() and sov()
approximate that integration by calculating the proportion of angles and
individual pivots for each angle rotated through the space. For these
functions, users only supply ideal points and an attendance vector
(i.e., a vector of who is always present). You do not supply roll-call
information.
Example 5 illustrates the use of sov_user() which
calculates traditional Shapley-Owen Values (SOVs) using
user supplied inputs. The result shows the voter’s influence over all
possible angles assuming those angles increase in increments of 5
degrees and are equally likely.
##### EX5: Traditional SOVs in 2D #####
## --- Attendance: 1 = included, NA = excluded -------------------------------
## Example: exclude i5 from the analysis
av <- c(1, 1, 1, 1, NA)
## --- Equal voting weights (each voter's vote is worth 1) -------------------
vw <- rep(1, nrow(ideals))
## --- Traditional SOV (simple majority among the four voters included) ------
out_ex5 <- sov_user(
ideals = ideals,
av = av,
absolute = FALSE, # simple k-majority
pr = 0.5001,
vw = vw,
nPoints1 = 72, # 360 degrees divided into 72 equal sized increments
nPoints2 = 72,
dec = 3
)
# aggregate results by member
out_ex5$pivot_summary
#> name coord1D coord2D num_pivots sov
#> 1 i1 0.7 0.7 15 0.2083333
#> 2 i2 -0.5 0.5 21 0.2916667
#> 3 i3 -0.7 -0.7 15 0.2083333
#> 4 i4 0.5 -0.5 21 0.2916667
#> 5 i5 0.0 0.0 0 0.0000000
# Angles examined and names of pivotal voters by direction (uncomment to view)
# out_ex5$pivot_by_angle
# Tip: switch to an absolute quota by setting absolute = TRUE and q = 3.
# Those parameters would require 3 yeas regardless of who's included in the attendance vector, av.
labels_ex5 <- setNames(out_ex5$pivot_summary$sov, out_ex5$pivot_summary$name)
sov:::plot_sov_geometry(
ideals = ideals,
label_values = labels_ex5,
digits = 3,
main = "EX5: 2D SOV (Traditional SOV)"
)
Vote-specific SOVs (VS-SOVs) can also be calculated from
package estimated output, estimates using
vs_sov(). The function can differentiate
estimates produced by W-NOMINATE, Optimal Classification,
and MCMCpack.
Example 6 illustrates the use of vs_sov() based on
W-NOMINATE. Note, it starts by fabricating a tiny WNOMINATE-like object
built from the same 2-D ideals and roll calls used in Example 2. Users
that have estimated ideal points using W-NOMINATE (resp.,
oc or MCMCpack) will only provide \(estimates\). They will not need all of
these preliminaries.
##### EX6: VS-SOVs in 2D using W-NOMINATE OUTPUT #####
## --- Fabricate a minimal W-NOM like object called *estimates* -----------------------
# Spreads pick the normal directions; midpoints place the cutplane.
spreads <- rbind(
c( 1, 0), # RC1: normal along +x
c( 0, 1), # RC2: normal along +y
c(-1, 0) # RC3: normal along -x
)
midpoints <- rbind(
c( 0.10, 0.00), # RC1 cut near the origin on x
c( 0.00, -0.10), # RC2 cut slightly below origin on y
c( 0.05, 0.00) # RC3 cut near the origin on x
)
rownames(spreads) <- rownames(midpoints) <- paste0("RC", 1:3)
# Legislators must include coord1D/coord2D plus GMP and CC.
# The latter helps the function identify the type of estimate.
# ideals from above
leg <- data.frame(
coord1D = ideals[, 1],
coord2D = ideals[, 2],
GMP = 0.5,
CC = 0.5,
row.names = rownames(ideals),
check.names = FALSE
)
# Rollcalls must include GMP and the WNOM fields midpoint*D and spread*D.
rc <- data.frame(
GMP = rep(0.5, nrow(midpoints)),
midpoint1D = midpoints[, 1],
midpoint2D = midpoints[, 2],
spread1D = spreads[, 1],
spread2D = spreads[, 2],
row.names = rownames(midpoints),
check.names = FALSE
)
# Dimensional weights (first must be 1); here we weight both dimensions equally.
weights <- c(1, 1)
# Minimal WNOM-like object
estimates <- list(legislators = leg, rollcalls = rc, weights = weights)
class(estimates) <- "nomObject" # not strictly required by SOV
## --- Votes: 1=yea, 0=nay, 9=attend-no-vote, NA=absent ----------------------
votes <- cbind(
RC1 = c(1, 0, 0, 1, 9), # include one '9' to illustrate attendance w/o voting
RC2 = c(1, 1, 0, 0, 0),
RC3 = c(0, 1, 1, 0, 0)
)
rownames(votes) <- rownames(ideals)
## --- Equal voting weights (each voter's vote is worth 1) -------------------
vw <- rep(1, nrow(ideals))
## --- VS-SOV from WNOM-like estimates (simple majority among attendees) -----
out_ex6 <- vs_sov(
estimates = estimates,
votes = votes,
absolute = FALSE, # simple k-majority
pr = 0.5001,
vw = vw,
dec = 3
)
# aggregate results by member
out_ex6$pivot_summary
#> name coord1D coord2D num_pivots vs_sov
#> 1 i1 0.7 0.7 0 0
#> 2 i2 -0.5 0.5 0 0
#> 3 i3 -0.7 -0.7 0 0
#> 4 i4 0.5 -0.5 0 0
#> 5 i5 0.0 0.0 3 1
# Pivot names by roll call:
out_ex6$pivot_by_rc
#> Position RC_num Pivot
#> 1 1 RC1 i5
#> 2 2 RC2 i5
#> 3 3 RC3 i5
# Normals and angles (derived from spreads & midpoints):
out_ex6$nv_and_angles
#> Position RC_num normVector1D normVector2D Angle_Radians Angle_Degrees
#> 1 1 RC1 1 0 0.000000 0
#> 2 2 RC2 0 1 1.570796 90
#> 3 3 RC3 -1 0 3.141593 180
# Plot (uses the 'spreads' as normals -- after normalization)
labels_ex6 <- setNames(out_ex6$pivot_summary$vs_sov, out_ex6$pivot_summary$name)
normals_ex6 <- as.matrix(spreads)
normals_ex6 <- normals_ex6 / sqrt(rowSums(spreads^2)) # Use spreads here to normalize spreads
sov:::plot_sov_geometry(
ideals = ideals,
normals = normals_ex6,
label_values = labels_ex6,
digits = 3,
main = "EX6: VS-SOV (W-NOMINATE Input)"
)
The following is an extension of example 6, illustrating how the functions can write output to an excel file.
##### Excel export (optional) #####
vs_sov_user(
ideals, normals, votes,
absolute = FALSE, pr = 0.5001, vw = vw,
print_results = TRUE, # writes an .xlsx workbook
out_dir = "output" # include a path to your output directory.
)
# Note: If "out_dir" is missing, a path will be created to a subfolder called "output."sov()sov() calculates traditional SOVs (SOVs)
from package estimated output, called estimates. The
function can differentiate estimates produced by
W-NOMINATE, Optimal Classification, and MCMCpack.
Example 7 illustrates the use of sov() based on
W-NOMINATE output.
##### EX7: SOVs in 2D using W-NOMINATE OUTPUT #####
av <- c(1, 1, 1, 1, NA);
names(av) <- rownames(ideals)
vw <- rep(1, nrow(ideals))
out_ex7 <- sov(
estimates = estimates,
av = av,
absolute = FALSE,
pr = 0.5001,
vw = vw,
nPoints1 = 72,
nPoints2 = 72,
dec = 3
)
# aggregate results by member
out_ex7$pivot_summary
#> name coord1D coord2D num_pivots sov
#> 1 i1 0.7 0.7 15 0.2083333
#> 2 i2 -0.5 0.5 21 0.2916667
#> 3 i3 -0.7 -0.7 15 0.2083333
#> 4 i4 0.5 -0.5 21 0.2916667
#> 5 i5 0.0 0.0 0 0.0000000
# Angles examined and names of pivotal voters by direction (uncomment to view)
# out_ex7$pivot_by_angle
# Plot
labels_ex7 <- setNames(out_ex7$pivot_summary$sov, out_ex7$pivot_summary$name)
sov:::plot_sov_geometry(
ideals = ideals,
label_values = labels_ex7,
digits = 3,
main = "EX7: Traditional SOV (W-NOMINATE Input)"
)
Bibina, Emma and Keith L. Dougherty. 2025. “Pivotal Voters at the U.S. Constitutional Convention: Shapley-Owen Values Reconsidered” (working paper).
Dougherty, Keith and Julian Edward. 2004. “The Pareto efficiency and expected costs of k-majority rules.” Politics, Philosophy, and Economics 3(2): 161-89.
Godfrey, Joseph, Bernard Grofman, and Scott L. Feld. 2011. “Applications of Shapley-Owen Values and the Spatial Copeland Winner.” Political Analysis 19(3): 306-324.
Owen, Guillermo, and Lloyd S. Shapley. 1989. “Optimal Location of Candidates in Ideological Space.” International Journal of Game Theory 18(3): 339-356.