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-SOVsutilize the “observed” normal vectors and their reflections to determine the proportion of times a voter pivots.- Traditional
SOVsutilize all angles of the vote from 0 to 360 degrees for each dimension greater than 1. - The package works for 1 to 4 dimensions, weighted voting, and various voting thresholds.
The package distinguishes between simple and absolute k-majority voting thresholds (Dougherty and Edward 2004):
- Absolute k-majority: Requires the yeas (weighted or unweighted) to
exceed a fixed number
q, which treats abstentions as “nays.” - Simple k-majority: Requires the ratio of yeas to (yeas + nays) to
exceed a proportion
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.
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 (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)"
)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 (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
##### 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
##### 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
##### 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)"
)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
##### 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() 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)"
)- The function will derive unit normals, rescale if needed, and auto-correct polarity based on the votes.
- Normals vs. Midpoints: provide one or the other, not both. With midpoints, the code infers the normal vector and utilizes distance of the midpoint to the origin.
- Scaling: If your ideals/midpoints are outside the unit circle, the function rescales them consistently, no extra work needed.
- Votes: Keep at least one 1 and one 0 per dataset (overall), and feel free to include a 9 to show ‘’attended but didn’t vote’’; it’s very instructive.
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.






