# Empirical Example of Backbone Extraction: The 108th U.S. Senate

#### Zachary Neal, Michigan State University, zpneal@msu.edu

# Table of Contents

1. Introduction
2. Data
3. Extracting from a weighted projection
4. Extracting from a weighted network
5. Extracting from an unweighted network
6. Lessons
7. References

# Introduction

This vignette uses empirical data on bill sponsorship behaviors in the 108th U.S. Senate to illustrate the use of the `backbone` package to extract sparse, unweighted networks from weighted projections, weighted networks, and unweighted networks. For a more general introduction to the backbone package, please see the Introduction to Backbone vignette.

The `backbone` package can be cited as:

Neal, Z. P. (2025). backbone: An R package to Extract Network Backbones. CRAN. https://doi.org/10.32614/CRAN.package.backbone

The `backbone` package, and the `igraph` package that is also used in this example, can be loaded using:

    library(backbone)
    library(igraph)

back to Table of Contents

# Data

In the U.S. Congress, the first step in the passage of new legislation is the introduction of a bill in the chamber. When a bill is introduced, it has one or more legislators who act as “sponsors” and express their initial support. Examining bill sponsorship is useful because although formal votes are taken on only a few bills, all bills have sponsors. Patterns of co-sponsorship (i.e., legislators who sponsor bills together) can provide insight into the structure of collaboration and political alliances (Neal 2014).

This example uses data on bill sponsorship activity in the 108th U.S. Senate, which ran from 3 January 2003 until 3 January 2005. The data was generated using `incidentally::incidence.from.congress(session = 108, types = "s", format = "igraph")` (Neal 2022). It is bundled with the backbone package and can be loaded and described using:

    data(senate108)
    senate108
    #> IGRAPH 42f8dc2 UN-B 3135 19060 --
    #> + attr: name (v/c), type (v/l), party (v/c), state (v/c), last (v/c),
    #> | id (v/c), color (v/c), introduced (v/c), title (v/c), area (v/c),
    #> | sponsor.party (v/c), partisan (v/n), status (v/c)
    #> + edges from 42f8dc2 (vertex names):
    #>  [1] Sen. Akaka, Daniel K. [D-HI]--S144  Sen. Akaka, Daniel K. [D-HI]--S1612
    #>  [3] Sen. Akaka, Daniel K. [D-HI]--S1379 Sen. Akaka, Daniel K. [D-HI]--S504
    #>  [5] Sen. Akaka, Daniel K. [D-HI]--S1709 Sen. Akaka, Daniel K. [D-HI]--S1946
    #>  [7] Sen. Akaka, Daniel K. [D-HI]--S2949 Sen. Akaka, Daniel K. [D-HI]--S685
    #>  [9] Sen. Akaka, Daniel K. [D-HI]--S1786 Sen. Akaka, Daniel K. [D-HI]--S2011
    #> [11] Sen. Akaka, Daniel K. [D-HI]--S2001 Sen. Akaka, Daniel K. [D-HI]--S1143
    #> + ... omitted several edges

`senate108` is a bipartite network stored as an `igraph` object. The network contains 100 Senators (agents) and 3035 bills (artifacts), where senators are connected to the bills they sponsored. In addition to the network’s structure, the object also contains attributes of both the Senators (e.g., party affilition) and bills (e.g., title).

The network’s indicence matrix captures which Senators sponsored which bills:

    as_biadjacency_matrix(senate108)[1:5,1:5]
    #>                              S144 S1612 S1379 S504 S1709
    #> Sen. Akaka, Daniel K. [D-HI]    1     1     1    1     1
    #> Sen. Allard, Wayne [R-CO]       0     0     0    0     0
    #> Sen. Allen, George [R-VA]       0     0     1    0     0
    #> Sen. Alexander, Lamar [R-TN]    0     0     0    1     0
    #> Sen. Baucus, Max [D-MT]         1     0     1    0     0

For example, Senators Akaka and Baucus both sponsored S.144, while Senators Allard, Allen, and Alexander did not. Two features of the indicence matrix – the row sums and column sums – are particularly important:

    rowSums(as_biadjacency_matrix(senate108))[1:5]
    #> Sen. Akaka, Daniel K. [D-HI]    Sen. Allard, Wayne [R-CO]
    #>                          188                          120
    #>    Sen. Allen, George [R-VA] Sen. Alexander, Lamar [R-TN]
    #>                          203                           99
    #>      Sen. Baucus, Max [D-MT]
    #>                          188
    colSums(as_biadjacency_matrix(senate108))[1:5]
    #>  S144 S1612 S1379  S504 S1709
    #>    13    11    76    38    20

The row sums represent the Senator degrees, and thus indicate the number of bills that each Senator sponsored. For example, Sen. Allen sponsored more than twice as many bills as Sen. Alexander. The column sums represent the bill degrees, and thus indicate the number of of sponsors on each bill. For example, bill S1379 was quite popular with 76 sponsors, while bill S1612 was unpopular with only 11 sponsors.

From these data, it is possible to construct a weighted projection, which is a unipartite network in which Senators are connected to each other if they co-sponsored bills together, where the edge weights record the number of bills they co-sponsored:

    projection <- bipartite_projection(senate108, which = "false")
    V(projection)$name <- V(projection)$last #Use only last names as node labels
    as_adjacency_matrix(projection, attr = "weight")[1:5,1:5]
    #> 5 x 5 sparse Matrix of class "dgCMatrix"
    #>           AKAKA ALLARD ALLEN Alexander BAUCUS
    #> AKAKA         .     14    38        19     37
    #> ALLARD       14      .    41        22     24
    #> ALLEN        38     41     .        37     31
    #> Alexander    19     22    37         .     17
    #> BAUCUS       37     24    31        17      .
    plot(projection, vertex.label = NA, vertex.frame.color = NA, vertex.size = 3, edge.width = E(projection)$weight^.1, edge.color = rgb(0,0,0,.1), main = "Weighted Projection")

The weighted projection indicates that, for example, Allard and Allen sponsored 41 bills together, while Akaka and Allard sponsored only 14 bills together. Although these edge weights may provide some information into the frequency of collaboration between Senators, or the strength of their alliance, the density and weights make this network difficult to analyze and visualize. Thus, it can be useful to instead focus on a backbone, which retains only the most “important” edges in a sparser unweighted network.

back to Table of Contents

# Extracting from a weighted projection

Because this network is a weighted projection, and the original bipartite network is available, we can extract the backbone using `backbone_from_projection()`. The backbone extraction models implemented in this function are powerful because they take advantage of information that is contained in the bipartite network (e.g., the row sums and column sums), but that are missing in the weighted projection itself.

This function implements multiple models, but the Stochastic Degree Sequence Model (SDSM) (Neal, Domagalski, and Sagan 2021) offers a balance of statistical power and computational efficiency. The SDSM performs a statistical test on each edge weight, asking whether it is statistically significantly larger than expected if the indicence matrix were random, but with the same expected row and column sums. In this context, for each edge it asks: “Did these two Senators sponsor more bills together than would be expected if they each sponsors bills randomly, but if they sponsored the same number of bills on average, and if each bill received the same number of sponsors on average?”

We can extract the SDSM backbone at the conventional `alpha = 0.05` level of statistical significance, then plot it:

    bb1 <- backbone_from_projection(senate108, model = "sdsm", alpha = 0.05, narrative = TRUE)
    #> The backbone package for R (v3.0.2; Neal, 2025) was used to extract the unweighted backbone of the weighted projection of a bipartite network containing 100 agents and 3035 artifacts. An edge was retained in the backbone if its weight was statistically significant (alpha = 0.05) using the stochastic degree sequence model (SDSM; Neal, Domagalski, and Sagan, 2021), which reduced the number of edges by 82.36%.
    #>
    #> Neal, Z. P. 2025. backbone: An R Package to Extract Network Backbones. CRAN. https://doi.org/10.32614/CRAN.package.backbone
    #>
    #> Neal, Z. P., Domagalski, R., and Sagan, B. (2021). Comparing Alternatives to the Fixed Degree Sequence Model for Extracting the Backbone of Bipartite Projections. Scientific Reports, 11, 23929. https://doi.org/10.1038/s41598-021-03238-3
    layout <- layout_nicely(bb1)  #Get layout for backbone (we'll use it later)
    plot(bb1, vertex.label = NA, vertex.frame.color = NA, vertex.size = 3, edge.color = rgb(0,0,0,.1), layout = layout, main = "SDSM Backbone")

Importantly, we must supply the original bipartite network, `senate108`, to this function. Because we specified `narrative = TRUE`, this function also displays a narrative description of the backbone extraction process with the associated references; this text can be used in a manuscript to ensure a complete description. Unlike the weighted projection, the SDSM backbone clearly captures the partisan polarization known to structure interactions in the U.S. Senate. Republicans (red nodes) primarily collaborate with other Republicans, and Democrats (blue nodes) primarily collaborate with other Democrats. Additionally, it illustrates that there are a few bipartisan Senators (nodes bridging the two communities), as well as a few conservative-leaning Democrats (blue nodes in the red community) and liberal-leaning Republicans (red nodes in the blue community).

We can also extract a signed SDSM backbone by specifying `signed = TRUE`.

    bb1_signed <- backbone_from_projection(senate108, model = "sdsm", alpha = 0.1, narrative = TRUE, signed = TRUE)
    #> The backbone package for R (v3.0.2; Neal, 2025) was used to extract the signed backbone of the weighted projection of a bipartite network containing 100 agents and 3035 artifacts. An edge was retained in the backbone if its weight was statistically significant (alpha = 0.1) using the stochastic degree sequence model (SDSM; Neal, Domagalski, and Sagan, 2021), which reduced the number of edges by 28.79%.
    #>
    #> Neal, Z. P. 2025. backbone: An R Package to Extract Network Backbones. CRAN. https://doi.org/10.32614/CRAN.package.backbone
    #>
    #> Neal, Z. P., Domagalski, R., and Sagan, B. (2021). Comparing Alternatives to the Fixed Degree Sequence Model for Extracting the Backbone of Bipartite Projections. Scientific Reports, 11, 23929. https://doi.org/10.1038/s41598-021-03238-3
    E(bb1_signed)$color <- "green"  #Make all edges green
    E(bb1_signed)$color[which(E(bb1_signed)$sign == -1)] <- rgb(1,0,0,.05)  #Make negative edges transparent red
    plot(bb1_signed, vertex.label = NA, vertex.frame.color = NA, vertex.size = 3, layout = layout, main = "SDSM Signed Backbone")

A signed backbone retains statistically significantly strong edges as positive and retains statistically significantly weak edges as negative. We use `signed = 0.1` because extracting a signed backbone implies a two-tailed test; this makes the positive edges in the signed backbone comparable to the edges in a non-signed backbone extracted at the `alpha = 0.05` level earlier. The signed backbone highlights an even stronger form of polarization, illustrating not only that collaboration (green edges) exists within party, but that avoidance (red edges) occurs between parties.

back to Table of Contents

# Extracting from a weighted network

In some cases, we may have a weighted projection, but do not have access to the original bipartite data. In other cases, we may have a weighted network where the edge weights were measured directly, and were not generated via projection. In these cases, we can extract the backbone using `backbone_from_weighted()`. The backbone extraction models implemented in this function consider local variation in degrees and edge weights, and thereby can preserve key structural features even in networks where edge weights heterogeneous or multi-scalar.

This function implements multiple models, but the Disparity Filter (Serrano, Boguná, and Vespignani 2009) is among the most widely used, and is typically chosen as the default unless there are specific reasons to use a different model. We can extract the Disparity Filter backbone, then plot it:

    bb2 <- backbone_from_weighted(projection, model = "disparity", alpha = 0.2, narrative = TRUE)
    #> The backbone package for R (v3.0.2; Neal, 2025) was used to extract the unweighted backbone of a weighted network containing 100 nodes. An edge was retained in the backbone if its weight was statistically significant (alpha = 0.2) using the disparity filter (Serrano et al., 2009), which reduced the number of edges by 83.29%.
    #>
    #> Neal, Z. P. 2025. backbone: An R Package to Extract Network Backbones. CRAN. https://doi.org/10.32614/CRAN.package.backbone
    #>
    #> Serrano, M. A., Boguna, M., & Vespignani, A. (2009). Extracting the multiscale backbone of complex weighted networks. Proceedings of the National Academy of Sciences, 106, 6483-6488. https://doi.org/10.1073/pnas.0808904106
    plot(bb2, vertex.label = NA, vertex.frame.color = NA, vertex.size = 3, edge.color = rgb(0,0,0,.1), main = "Disparity Filter Backbone")

Notably, we supply the weighted network `projection` (not the bipartite network `senate108`) to this function. Because the weighted network is missing information that was contained in the original bipartite network (i.e., the Senator and bill degrees), it is more difficult for the Disparity Filter to identify important edges. To adjust for this, we use a more liberal `alpha = 0.2` level of statistical significance.

The Disparity Filter backbone also captures the partisan polarization in the U.S. Senate. Here, it is reflected in a dense community of collaborating Democrats, and a more diffuse community of collaborating Republicans. In the 108th U.S. Senate, Republicans held the majority. Thus, this pattern is consistent with prior findings that members of the majority can stake out more extreme positions and exhibit “strategic disloyalty”, while members of the minority party must “circle the wagons” to consolidate their power (Kirkland and Slapin 2017; Neal 2020).

back to Table of Contents

# Extracting from an unweighted network

In some cases, we might only have an unweighted network, but one that is too dense to analyze or visualize. In these cases, we can extract the backbone using `backbone_from_unweighted()`. The backbone extraction models implemented in this function follow a “recipe” of first assigning each edge a score based on their role in the structure, normalizing those edge scores, then filtering based on the scores.

This function implements multiple models, but the Local Sparsification (L-Spar) model (Satuluri, Parthasarathy, and Ruan 2011) is one option that is designed to preserve clustering, which we suspect exists in the U.S. Senate in the form of partisan polarization. This model scores edges using the Jaccard coefficient of its endpoints’ neighborhoods, normalizes these scores by ranking them from the perspective of each node, then chooses edges to retain based on the degree of each node and a sparisfication `parameter’.

Suppose we did not have the weighted projection, but instead only had an unweighted network in which Senators were connected if they sponsored 25 or more of the same bills:

    unweighted <- delete_edges(projection, which(E(projection)$weight < 25))  #Delete low-weight edges
    unweighted <- delete_edge_attr(unweighted, "weight")  #Delete edge weights to obtain an unweighted network
    unweighted <- delete_vertices(unweighted, which(degree(unweighted) < 1))  #Delete isolated nodes
    edge_density(unweighted)  #Compute density
    #> [1] 0.698927
    plot(unweighted, vertex.label = NA, vertex.frame.color = NA, vertex.size = 3, edge.color = rgb(0,0,0,.25), main = "Unweighted Network")

The unweighted network is so dense that it would be difficult to analyze and uncover any meaningful patterns, or to visualize and see any meaningful structure. However, we can extract the L-Spar backbone from this unweighted network, then plot the backbone:

    bb3 <- backbone_from_unweighted(unweighted, model = "lspar", parameter = .5, narrative = TRUE)
    #> The backbone package for R (v3.0.2; Neal, 2025) was used to extract the unweighted backbone of an unweighted network containing 98 nodes. Edges were selected for retention in the backbone using Satuluri et al's (2011) Local Sparsification backbone model (filtering parameter = 0.5), which reduced the number of edges by 79.68%.
    #>
    #> Neal, Z. P. 2025. backbone: An R Package to Extract Network Backbones. CRAN. https://doi.org/10.32614/CRAN.package.backbone
    #>
    #> Satuluri, V., Parthasarathy, S., & Ruan, Y. (2011, June). Local graph sparsification for scalable clustering. In Proceedings of the 2011 ACM SIGMOD International Conference on Management of data (pp. 721-732). https://doi.org/10.1145/1989323.1989399
    plot(bb3, vertex.label = NA, vertex.frame.color = NA, vertex.size = 3, edge.color = rgb(0,0,0,.25), main = "L-Spar Backbone")

Even based on the very limited information still left in this unweighted network (after the bipartite original has been lost, and the weights in the projection have been lost), the L-Spar backbone still captures the partisan structure of the U.S. Senate.

back to Table of Contents

# Lessons

This brief empirical example illustrates the utility of the backbone package. There are a couple broad lessons to consider when extracting a network backbone:

- Different backbone extraction models use different amounts of information to decide which edges to retain. Use a backbone extraction model that incorporates all of the available information. For example, if the original network is a weighted projection, then extract the backbone from the original bipartite network or hypergraph using `backbone_from_projection()`. Only extract a backbone using `backbone_from_unweighted()` if no information about edge weights is available.

- The backbone extraction model and its specifications impact the backbone that is obtained. Therefore, clearly identify and cite the backbone model that was used, and completely describe both the original network and how the model was specified. This is simplified by specifying `narrative = TRUE`, which automatically generates and displays such a description.

# References

Kirkland, Justin H, and Jonathan B Slapin. 2017. “Ideology and Strategic Party Disloyalty in the US House of Representatives.” Electoral Studies 49: 26–37. https://doi.org/https://doi.org/10.1016/j.electstud.2017.07.006.
Neal, Zachary P. 2014. “The Backbone of Bipartite Projections: Inferring Relationships from Co-Authorship, Co-Sponsorship, Co-Attendance and Other Co-Behaviors.” Social Networks 39 (October): 84–97. https://doi.org/10.1016/j.socnet.2014.06.001.
———. 2020. “A Sign of the Times? Weak and Strong Polarization in the US Congress, 1973–2016.” Social Networks 60: 103–12. https://doi.org/10.1016/j.socnet.2018.07.007.
———. 2022. “Constructing Legislative Networks in r Using Incidentally and Backbone.” Connections 42 (1). https://doi.org/10.2478/connections-2019.026.
Neal, Zachary P, Rachel Domagalski, and Bruce Sagan. 2021. “Comparing Alternatives to the Fixed Degree Sequence Model for Extracting the Backbone of Bipartite Projections.” Scientific Reports 11: 23929. https://doi.org/10.1038/s41598-021-03238-3.
Satuluri, Venu, Srinivasan Parthasarathy, and Yiye Ruan. 2011. “Local Graph Sparsification for Scalable Clustering.” In Proceedings of the 2011 ACM SIGMOD International Conference on Management of Data, 721–32. https://doi.org/10.1145/1989323.1989399.
Serrano, M Ángeles, Marián Boguná, and Alessandro Vespignani. 2009. “Extracting the Multiscale Backbone of Complex Weighted Networks.” Proceedings of the National Academy of Sciences 106 (16): 6483–88. https://doi.org/10.1073/pnas.0808904106.