sov

R-CMD-check

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.

Description

This program calculates vs-SOVs and traditional SOVs in multidimensional space.

Voting Thresholds

The package distinguishes between simple and absolute k-majority voting thresholds (Dougherty and Edward 2004):

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.

Installation

You can install the development version of sov from GitHub with:

# install.packages("pak")
pak::pak("emmabbn/sov")
library(sov)

Examples

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.

Single-Dimension & 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.

Inputs and Setup

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

Estimation

Example 1

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)"
)

Multidimensional & 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).

Inputs and Setup

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

Estimation

Example 2

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

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

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)"
)

Multidimensional & 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

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)"
)

Multidimensional & vs_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

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)"
)

Excel Export (EX6)

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."

Multidimensional & 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

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)"
)

Tips

References

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.