12 Centrality and Brokerage in statnet
12.1 Setup
Find and open your RStudio Project associated with this class. Begin by opening a new script. It’s generally a good idea to place a header at the top of your scripts that tell you what the script does, its name, etc.
#################################################
# What: Centrality and Brokerage in statnet
# Created: 02.28.14
# Revised: 01.31.22
#################################################
If you have not set up your RStudio Project to clear the workspace on exit, your environment may contain the objects and functions from your prior session. To clear these before beginning use the following command.
rm(list = ls())
Proceed to place the data required for this lab (SouthFront_EL.csv
, SouthFront_NL.csv
, Strike.net
, and Strikegroups.csv
) also inside your R Project folder. We have placed it in a sub folder titled data
for organizational purposes; however, this is not necessary.
In this lab we will consider a handful of actor-level measures. Specifically, we will walk through the concepts of centrality and brokerage on two different networks.
Centrality is one of SNA’s oldest concepts. When working with undirected data, a central actor can be someone who has numerous ties to other actors (degree), someone who is closer (in terms of path distance) to all other actors (closeness), someone who lies on the shortest path (geodesic) between any two actors (betweenness), or someone who has ties to other highly central actors (eigenvector). In some networks, the same actors will score high on all four measures. In others, they won’t. There are, of course, more than four measures of centrality.
For the centrality portion of this exercise, we’ll look at a subset of South Front’s YouTube network that we’ve collected using YouTube’s open API. Specifically, we will examine subscription-based ties among accounts (note the names are a string of what appears to be random combinations of letters and numbers) within South Front’s ego network (excluding South Front), which leaves us with a network of 310 subscriptions among 236 accounts. We will consider this network undirected for the “Centrality and Power” section, but directed for the “Centrality and Prestige” portion of this lab.
Next, we will turn to measures that operationalize various aspects of brokerage. For that section, we will demonstrate the concept of brokerage by looking at a communication network of a wood-processing facility where workers rejected a new compensation package and eventually went on strike. Management then brought in an outside consultant to analyze the employee’s communication structure because it felt that information about the package was not being effectively communicated to all employees by the union negotiators. The outside consultant asked all employees to indicate, on a 5-point scale, the frequency that they discussed the strike with each of their colleagues, ranging from ‘almost never’ (less than once per week) to ‘very often’ (several times per day). The consultant used 3 as a cut-off value in order to identify a tie between two employees. If at least one of two persons indicated they discussed work with a frequency of three or more, a tie between them was included in the network. The data accompany the book, “Exploratory Social Network Analysis with Pajek,” also published by Cambridge. Hence, we’ve shared the data with you as a Pajek file.
12.2 Libraries
Load the statnet library.
library(statnet)
We will also be using other libraries in this exercise such as DT, keyplayer, scales, and psych. This might be the first time you use these, so you may need to install them.
<- c("DT","keyplayer", "psych", "scales")
to_install install.packages(to_install)
If you have installed these, proceed to load CINNA, keyplayer, and psych. We will namespace functions from DT, and scales libraries (e.g., scales::rescale()
) as these have functions that mask others from statnet.
library(CINNA)
library(keyplayer)
library(psych)
12.3 Load Data
We’ve stored South Front’s YouTube network as an edge list. Go ahead and import it with the read.csv()
function, convert it to a network
object, and visualize it. For now we will import it as an undirected network.
# Read data
<- read.csv("data/SouthFront_EL.csv",
sf_el header = TRUE)
# Remove parallel edges
<- sf_el[!duplicated(cbind(pmin(sf_el$Source, sf_el$Target),
sf_el pmax(sf_el$Source, sf_el$Target))), ]
# Create graph with edge list
<- as.network(sf_el,
sf_net directed = FALSE,
loops = FALSE,
multiple = FALSE,
matrix.type = "edgelist")
# Take a look at it
sf_net
Network attributes:
vertices = 236
directed = FALSE
hyper = FALSE
loops = FALSE
multiple = FALSE
bipartite = FALSE
total edges= 309
missing edges= 0
non-missing edges= 309
Vertex attribute names:
vertex.names
Edge attribute names:
Id Label timeset Type Weight
Plot it.
gplot(sf_net,
gmode = "graph",
mode = "kamadakawai",
vertex.col = "lightblue",
usearrows = FALSE,
main = "South Front")
Next, load the Stike.net
file and Strikegroups.csv
, convert the relational data to an network
object and add the node attributes to this graph.
# Read graph
<- as.network(
strike_net x = read.paj(file = "data/Strike.net"),
directed = FALSE
)
# Read attributes
<- read.csv("data/Strikegroups.csv",
strike_attrs col.names = c("Name", "Group"))
# Add vertex attributes
<- set.vertex.attribute(strike_net,
strike_net attrname = "Group",
value = strike_attrs[["Group"]])
# Take a look at the graph object
strike_net
Network attributes:
vertices = 24
directed = TRUE
hyper = FALSE
loops = FALSE
multiple = FALSE
bipartite = FALSE
title = Strike
total edges= 76
missing edges= 0
non-missing edges= 76
Vertex attribute names:
Group vertex.names x y
Edge attribute names:
Strike
Lastly, plot the new network.
gplot(strike_net,
gmode = "graph",
mode = "kamadakawai",
main = "Strike Network",
label = network.vertex.names(strike_net),
label.col = "black",
label.cex = 0.6,
label.pos = 5,
vertex.col = strike_net %v% "Group")
12.4 Centrality and Power (Undirected Networks)
12.4.1 Degree, Closeness, Betweenness and Eigenvector Centrality
We will begin by calculating the four primary measures of centrality for undirected networks. Note that there are two closeness commands. The first is the standard measure of closeness (Freeman). Unfortunately, it doesn’t handle infinite distances, so we show how to calculate an alternative (ARD) that does. Note that our use of both closeness scores is for demonstration purposes because our network is a connected graph with a single component.
# Add centrality metrics as vertex attributes
<- set.vertex.attribute(sf_net, attrname = "degree",
sf_net value = degree(sf_net,
gmode = "graph",
ignore.eval = FALSE))
<- set.vertex.attribute(sf_net, attrname = "closeness",
sf_net value = closeness(sf_net,
gmode = "graph"))
<- set.vertex.attribute(sf_net, attrname = "ard",
sf_net value = closeness(sf_net,
gmode = "graph",
# the cmode="suminvundir" gives
# us ARD, which works with
# disconnected networks.
cmode = "suminvundir"))
<- set.vertex.attribute(sf_net, attrname = "betweenness",
sf_net value = betweenness(sf_net,
gmode = "graph"))
<- set.vertex.attribute(sf_net, attrname = "eigenvector",
sf_net value = evcent(sf_net,
# use.eigen = TRUE is not always
# necessary; we've included it
# here to get more "robust"
# results.
use.eigen = FALSE))
# Take a look at the graph object
sf_net
Network attributes:
vertices = 236
directed = FALSE
hyper = FALSE
loops = FALSE
multiple = FALSE
bipartite = FALSE
total edges= 309
missing edges= 0
non-missing edges= 309
Vertex attribute names:
ard betweenness closeness degree eigenvector vertex.names
Edge attribute names:
Id Label timeset Type Weight
12.4.2 Correlations
Let’s create a data.frame
with vertex centralities.
<- data.frame("Channel" = network.vertex.names(sf_net),
centrality "Degree" = sf_net %v% "degree",
"Closeness (Freeman)" = sf_net %v% "closeness",
"Closeness (ARD)" = sf_net %v% "ard",
"Betweenness" = sf_net %v% "betweenness",
"Eigenvector" = sf_net %v% "eigenvector")
head(centrality, n = 5)
Channel Degree Closeness..Freeman. Closeness..ARD.
1 UCYE61Gy3RxiI2hSdCmAgP9w 1 0.2952261 0.3251773
2 UCFpuO2wt_3WSrk-QG7VjUhQ 1 0.2376138 0.2522695
3 UCqxZhJewxqhB4cNsxJFjIhg 1 0.2952261 0.3251773
4 UCWNbidLi4FXBd83ixoB1v-A 1 0.2952261 0.3251773
5 UCShSHheWVd42CdiVAYn-9xQ 1 0.2516060 0.2702837
Betweenness Eigenvector
1 0 0.062031013
2 0 0.002549735
3 0 0.062031013
4 0 0.062031013
5 0 0.006221302
To run a correlation between variables, use the cor()
function. Note our data.frame
(i.e., centrality
) has six columns, including the first column containing channel names, which means we want to correlate the columns containing centrality scores only (i.e., columns 2-6).
cor(centrality[, 2:6])
Degree Closeness..Freeman. Closeness..ARD. Betweenness
Degree 1.0000000 0.4941497 0.5824820 0.9863708
Closeness..Freeman. 0.4941497 1.0000000 0.9908909 0.5346233
Closeness..ARD. 0.5824820 0.9908909 1.0000000 0.6192383
Betweenness 0.9863708 0.5346233 0.6192383 1.0000000
Eigenvector 0.7558708 0.8021207 0.8420753 0.7965928
Eigenvector
Degree 0.7558708
Closeness..Freeman. 0.8021207
Closeness..ARD. 0.8420753
Betweenness 0.7965928
Eigenvector 1.0000000
Note that, for the most part, the centrality measures correlate highly with degree, especially betweenness. The two closeness measures correlate very high with each other as well, which is a good sign that they’re tapping into the same phenomenon.
Here’s a really nice correlation function (i.e., pairs.panels()
) associated with the psych package, which you may need to install first.
pairs.panels(centrality[, 2:6])
Let’s plot the network where we vary node size by the centrality measures; note that we’ve rescaled them so that the nodes don’t get overwhelmingly big or way too small. We’ve turned off the labels, which are YouTube Channel IDs (i.e., really long), so you can see the results clearly.
par(mfrow = c(2, 3))
# Save the coordinates
<- gplot.layout.kamadakawai(sf_net,
coords layout.par = NULL)
# Plot graph with rescaled nodes
gplot(sf_net,
main = "South Front Degree",
coord = coords,
displaylabels = FALSE,
vertex.col = "lightblue",
vertex.cex = scales::rescale(sf_net %v% "degree", to = c(1, 5)),
usearrows = FALSE)
gplot(sf_net,
main = "South Front Closeness",
coord = coords,
displaylabels = FALSE,
vertex.col = "lightblue",
vertex.cex = scales::rescale(sf_net %v% "closeness", to = c(1, 5)),
usearrows = FALSE)
gplot(sf_net,
main = "South Front ARD",
coord = coords,
displaylabels = FALSE,
vertex.col = "lightblue",
vertex.cex = scales::rescale(sf_net %v% "ard", to = c(1, 5)),
usearrows = FALSE)
gplot(sf_net,
main = "South Front Betweenness",
coord = coords,
displaylabels = FALSE,
vertex.col = "lightblue",
vertex.cex = scales::rescale(sf_net %v% "betweenness", to = c(1, 5)),
usearrows = FALSE)
gplot(sf_net,
main = "South Front Eigenvector",
coord = coords,
displaylabels = FALSE,
vertex.col = "lightblue",
vertex.cex = scales::rescale(sf_net %v% "eigenvector", to = c(1, 5)),
usearrows = FALSE)
12.4.3 Interactive Table
The R package DT provides an R interface to the JavaScript library DataTables. R data objects (matrices or data frames) can be displayed as HTML table widgets. The interactive widgets provide filtering, pagination, sorting, and many other features for the tables.
We will namespace the datatable()
function from library and provide it the centrality
node table we created for the sf_net
graph.
::datatable(centrality, rownames = FALSE) DT
Modify the numeric variables, rounding them to 3 decimal places.
<- as.data.frame(
centrality sapply(names(centrality), function(s) {
<- ifelse(is.numeric(centrality[[s]]),
centrality[[s]] yes = round(centrality[s], digits = 3),
no = centrality[s])
}) )
Take a look at the table:
::datatable(centrality, rownames = FALSE) DT
You may want to “clean up” this table. Begin by looking at the datatable
arguments by reading the documentation ?DT::datatable
. Here we clean it the data in base R, then modify the HTML widget parameters.
# Order the data.frame by decreasing degree value
<- centrality[order(centrality$Degree, decreasing = TRUE), ]
centrality # Change column names for the data.frame
colnames(centrality) <- c("Channel", "Degree", "Closeness", "ARD",
"Betweenness", "Eigenvector")
# Create and HTML widget table
::datatable(centrality,
DT# The table caption
caption = "Table 1: South Front - Centrality and Power",
# Select the CSS class: https://datatables.net/manual/styling/classes
class = 'cell-border stripe',
# Show rownames?
rownames = FALSE,
# Whether/where to use/put column filters
filter = "top",
# The row/column selection mode
selection = "multiple",
# Pass along a list of initialization options
# Details here: https://datatables.net/reference/option/
options = list(
# Is the x-axis (horizontal) scrollable?
scrollX = TRUE,
# How many rows returned in a page?
pageLength = 10,
# Where in the DOM you want the table to inject various controls?
# Details here: https://legacy.datatables.net/ref#sDom
sDom = '<"top">lrt<"bottom">ip')
)
12.5 Centrality and Prestige (Directed Networks)
We will re-import the South Front data set one more time but consider it a directed network this time to look at the concepts of centrality and prestige. Specifically, make sure you use the directed = TRUE
parameter within the as.network()
function.
<- as.network(sf_el,
sf_net_d directed = TRUE,
loops = FALSE,
matrix.type = "edgelist")
Take a look at the new network
object.
sf_net_d
Network attributes:
vertices = 236
directed = TRUE
hyper = FALSE
loops = FALSE
multiple = FALSE
bipartite = FALSE
total edges= 309
missing edges= 0
non-missing edges= 309
Vertex attribute names:
vertex.names
Edge attribute names:
Id Label timeset Type Weight
12.5.1 In-N-Out Degree, Input Domain, and Proximity Prestige
Let’s first calculate in-degree and out-degree for the network. Note that statnet also has a “prestige” function, which allows you to access a variety of prestige measures (including in and out-degree). We will focus on the former for this exercise.
<- set.vertex.attribute(sf_net_d, attrname = "indeg",
sf_net_d value = degree(sf_net_d,
gmode = "digraph",
cmode = "indegree"))
<- set.vertex.attribute(sf_net_d, attrname = "outdeg",
sf_net_d value = degree(sf_net_d,
gmode = "digraph",
cmode = "outdegree"))
sf_net_d
Network attributes:
vertices = 236
directed = TRUE
hyper = FALSE
loops = FALSE
multiple = FALSE
bipartite = FALSE
total edges= 309
missing edges= 0
non-missing edges= 309
Vertex attribute names:
indeg outdeg vertex.names
Edge attribute names:
Id Label timeset Type Weight
Now, let’s vary node size of plots by in-degree and out-degree. Again, we will hide the labels so you can see patterns more clearly.
par(mfrow = c(1, 2))
# Save the coordinates
<- gplot.layout.kamadakawai(sf_net_d, layout.par = NULL)
coords
# Plot graph with rescaled nodes
gplot(sf_net_d,
main = "South Front In-Degree",
coord = coords,
displaylabels = FALSE,
vertex.col = "lightblue",
vertex.cex = scales::rescale(sf_net_d %v% "indeg", to = c(1, 5)),
usearrows = TRUE)
gplot(sf_net_d,
main = "South Front Out-Degree",
coord = coords,
displaylabels = FALSE,
vertex.col = "lightblue",
vertex.cex = scales::rescale(sf_net_d %v% "outdeg", to = c(1, 5)),
usearrows = TRUE)
We can correlate the two measures if we want. The negative correlation makes sense when we look at the in-degree and out-degree plots.
cor(sf_net_d %v% "indeg", sf_net_d %v% "outdeg")
[1] -0.31852
The prestige()
function, which we will now use, can estimate more than in- and out-degree, such as eigenvector, input domain, and proximity prestige. We will stick with the latter two for now.
# Input Domain
<- set.vertex.attribute(sf_net_d, attrname = "domain",
sf_net_d value = prestige(sf_net_d, cmode = "domain"))
# Proximity Prestige
<- set.vertex.attribute(sf_net_d, attrname = "domain.proximity",
sf_net_d value = prestige(sf_net_d,
cmode = "domain.proximity"))
Take a look at the count of values in each vector:
table(sf_net_d %v% "domain")
0 1 2 5 6 8 13 16 23 24 32 33 57 64
218 5 1 1 1 1 1 1 2 1 1 1 1 1
table(sf_net_d %v% "domain.proximity")
0
236
As we can see, however, the proximity command returns almost all 0s for input domain, and all 0s for proximity prestige.
12.5.2 Correlations
Create a data frame to display the prestige scores South Front’s YouTube network (we’ll add authorities once we move to igraph).
<- data.frame("Channel" = network.vertex.names(sf_net_d),
centrality_d "Indegree" = sf_net_d %v% "indeg",
"Outdegree" = sf_net_d %v% "outdeg",
"Input Domain" = sf_net_d %v% "domain",
"Proximity Prestige" = sf_net_d %v% "domain.proximity")
head(centrality_d)
Channel Indegree Outdegree Input.Domain Proximity.Prestige
1 UCYE61Gy3RxiI2hSdCmAgP9w 0 1 0 0
2 UCFpuO2wt_3WSrk-QG7VjUhQ 0 1 0 0
3 UCqxZhJewxqhB4cNsxJFjIhg 0 1 0 0
4 UCWNbidLi4FXBd83ixoB1v-A 0 1 0 0
5 UCShSHheWVd42CdiVAYn-9xQ 0 1 0 0
6 UCNMbegBD9OjH4Eza8vVjBMg 0 1 0 0
Take a look at the pairs.panels()
output.
pairs.panels(centrality_d[, 2:5])
12.5.3 Interactive Table
Let’s create another interactive table for our prestige-based centrality measures. Again, let’s extract the nodes data.frame
from the graph and then recode numeric variables to clean up the table.
# Round up numeric values
<- as.data.frame(
centrality_d sapply(names(centrality_d), function(s) {
<- ifelse(is.numeric(centrality_d[[s]]),
centrality_d[[s]] yes = round(centrality_d[s], digits = 3),
no = centrality_d[s])
}) )
Use datatable
and some base R to clean up the data.frame
and create a good looking widget.
<- centrality_d[order(centrality_d$Indegree, decreasing = TRUE), ]
centrality_d ::datatable(centrality_d,
DTcaption = "Table 2: South Front - Centrality and Prestige",
class = 'cell-border stripe',
rownames = FALSE,
filter = "top",
selection = "multiple",
options = list(
scrollX = TRUE,
pageLength = 10,
sDom = '<"top">lrt<"bottom">ip')
)
12.6 Brokerage
For this section, we will use the strike_net
object. Begin by plotting the network side-by-side. The initial plot is without group membership but the second highlights the groups.
par(mfrow = c(1, 2))
# Save coordinates
<- gplot.layout.kamadakawai(strike_net, layout.par = NULL)
coords
# Plot them
gplot(strike_net,
gmode = "graph",
coord = coords,
main = "Strike Network",
label = network.vertex.names(strike_net),
label.col = "black",
label.cex = 0.6,
label.pos = 5)
gplot(strike_net,
main = "Strike Network (Groups)",
gmode = "graph",
coord = coords,
label = network.vertex.names(strike_net),
label.col = "black",
label.cex = 0.6,
label.pos = 5,
vertex.col = strike_net %v% "Group")
12.6.1 Cutpoints
Next, identify cutpoints and the plot the graph with the cutpoints given a different color. The cutpoints()
function identifies the cutpoints in a graph; however, keep in mind that these may vary based on the mode
as either directed (digraph
) or undirected (graph
). The output is a logical
vector indicating cutpoint status if return.indicator = TRUE
for each vertex (TRUE
if cutpoint and FALSE
otherwise). Like before, we can assing this output vector to the graph vertices as an attribute.
<- set.vertex.attribute(strike_net, attrname = "cutpoint",
strike_net value = cutpoints(strike_net,
mode = "graph",
return.indicator = TRUE))
strike_net
Network attributes:
vertices = 24
directed = TRUE
hyper = FALSE
loops = FALSE
multiple = FALSE
bipartite = FALSE
title = Strike
total edges= 76
missing edges= 0
non-missing edges= 76
Vertex attribute names:
cutpoint Group vertex.names x y
Edge attribute names:
Strike
Now plot it.
gplot(strike_net,
main = "Strike Network (Cutpoints)",
gmode = "graph",
coord = coords,
label = network.vertex.names(strike_net),
label.col = "black",
label.cex = 0.6,
label.pos = 5,
vertex.col = strike_net %v% "cutpoint")
Let’s change-up the color a bit using an ifelse()
evaluation. If the vertex is a cutpoint, we will make it "red"
; otherwise, "lightblue"
.
gplot(strike_net,
main = "Strike Network (Cutpoints)",
gmode = "graph",
coord = coords,
label = network.vertex.names(strike_net),
label.col = "black",
label.cex = 0.6,
label.pos = 5,
vertex.col = ifelse(strike_net %v% "cutpoint", yes = "red", no = "lightblue"))
If we vary node size by betweenness centrality, we can see that a correlation exists between cutpoints and betweenness. It isn’t perfect, however.
gplot(strike_net,
main = "Strike Network (Cutpoints + Betweenness)",
gmode = "graph",
coord = coords,
label = network.vertex.names(strike_net),
label.col = "black",
label.cex = 0.6,
label.pos = 5,
vertex.col = ifelse(strike_net %v% "cutpoint", yes = "red", no = "lightblue"),
vertex.cex = scales::rescale(betweenness(strike_net), to = c(0.5, 3)))
In order to locate the graph bicomponents, we can use the bicomponent.dist()
function, which returns a named list containing multiple elements. After we identify bicomponents, we can use the names()
function to see the items the function generates.
<- bicomponent.dist(strike_net)
strike_bc names(strike_bc)
[1] "members" "membership" "csize" "cdist"
The command only identifies bicomponents of size 3 or greater, which you can see by typing, strike_bc$csize
. Typing strike_bc$membership
produces the group membership. You’ll also note that when a node belongs to more than one bicomponent, the command randomly assigns it to one of the bicomponents.
$csize strike_bc
1 2 3 4
10 8 4 3
$membership strike_bc
[1] 4 2 NA 3 2 1 2 1 1 2 4 1 1 2 4 2 1 1 1 3 3 2 3 1
You can use the $membership
element to color nodes in the graph.
gplot(strike_net,
main = "Strike Network (Bicomponents Membership)",
gmode = "graph",
coord = coords,
label = network.vertex.names(strike_net),
label.col = "black",
label.cex = 0.6,
label.pos = 5,
vertex.col = strike_bc$membership,
vertex.cex = scales::rescale(betweenness(strike_net), to = c(0.5, 3)))
12.6.2 Cutsets (Key Player)
Here we will use the keyplayer package to identify a cutset of three because the strike network is such a small one. If you run this repeatedly, you’ll notice that it will return different solutions. That’s because there are multiple solutions.
As seen in the igraph version of this lab, the influenceR package works well with igraph
objects. However, it does not work well with network
objects, which means we’d have to convert our data first using intergraph
. We’ll skip that step here and use the keyplayer
package only.
In any case, the keyplayer package offers more flexibility than the influenceR package in the sense that you can choose what centrality measure you want to use. Here we’ll use fragmentation centrality, which is what Steve Borgatti uses in the standalone program, keyplayer. We didn’t discuss fragmentation centrality above, but it measures the extent to which an individual node fragments the network if it’s removed.
To identify the key player set in keyplayer use the kpset()
function, which identifies the most central group of players in a social network based on a specified centrality measure and a target group size. The output of this function, like with many other functions, is a named list with two entries. The first, keyplayers
, provides the indices of the nodes identified as key players, which is the relevant output here.
First, take a look at the vector of indices.
<- as.matrix.network(strike_net)
strike.adj
::kpset(strike.adj,
keyplayer3,
type = "fragment")$keyplayers
[1] 5 9 17
The output tells us which actors make up the cutset. For example, an output vector with “5, 9, 17” would indicate that actors 5, 9, and 17 make up our cutset. To access the node names, you can use the get.vertex.attitute()
function paired with the [
accessor and the appropriate index.
get.vertex.attribute(strike_net, "vertex.names")[5]
[1] "Norm"
This can be further expanded to include multiple indexes at once through the c()
function.
<- get.vertex.attribute(strike_net, "vertex.names")[c(5, 9, 17)]
my_cutset my_cutset
[1] "Norm" "Bob" "Gill"
Using the indexes one could create a logical vertex attribute for the cutset.
%v% "cutset" <- ifelse(get.vertex.attribute(strike_net,
strike_net "vertex.names") %in% my_cutset,
TRUE, FALSE)
# Take a look a new vertex attribute
strike_net
Network attributes:
vertices = 24
directed = TRUE
hyper = FALSE
loops = FALSE
multiple = FALSE
bipartite = FALSE
title = Strike
total edges= 76
missing edges= 0
non-missing edges= 76
Vertex attribute names:
cutpoint cutset Group vertex.names x y
Edge attribute names:
Strike
get.vertex.attribute(strike_net, "cutset")
[1] FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE TRUE FALSE FALSE FALSE
[13] FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
Once we’ve done that, we can plot the strike network with cutsets.
gplot(strike_net,
mode = "kamadakawai",
displaylabels = TRUE,
label.cex = 0.7,
label.pos = 5,
label.col = "black",
vertex.col = get.vertex.attribute(strike_net, "cutset"),
usearrows = FALSE,
main = "Strike Network (Cutset = 3)")
You could always recode the colors based on this logical vertex attribute.
gplot(strike_net,
mode = "kamadakawai",
displaylabels = TRUE,
label.cex = 0.7,
label.pos = 5,
label.col = "black",
vertex.col = ifelse(get.vertex.attribute(strike_net, "cutset"),
"red", "lightblue"),
usearrows = FALSE,
main = "Strike Network (Cutset = 3)")
Let’s calculate the increase in fragmentation after the cutset’s removal.
<- get.inducedSubgraph(strike_net,
strike2_net v = which(
%v% "cutset" == FALSE))
strike_net
<- 1 - connectedness(strike_net)
frag_before frag_before
[1] 0
<- 1 - connectedness(strike2_net)
frag_after frag_after
[1] 0.747619
- frag_before frag_after
[1] 0.747619
Finally plot the fragmented network.
gplot(strike2_net,
mode = "kamadakawai",
displaylabels = TRUE,
label.cex = 0.7,
label.pos = 5,
label.col = "black",
vertex.col = "lightblue",
usearrows = FALSE,
main = "Fragmented Strike Network")
12.6.3 Gould and Fernandez
For the Gould and Fernandez algorithm, we need the strike group’s membership data, which we’ve provided you as an attribute file (Strikegroups.csv
) and previously imported. The brokerage()
function calculates the brokerage analysis of Gould and Fernandez on an input graph given a partition (e.g., membership) vector. Because the output is a named list, we will store it in a separate object.
<- brokerage(strike_net,
strike_gf # Pass the Group vector
"Group"]]) strike_attrs[[
Take a look at the output elements.
ls(strike_gf)
[1] "cl" "clid" "exp.gli" "exp.grp" "exp.nli" "n" "N"
[8] "raw.gli" "raw.nli" "sd.gli" "sd.grp" "sd.nli" "z.gli" "z.nli"
For a more detailed description of these elements, take a look at the documentation ??brokerage
. The relevant element in this list is raw.nli
, which includes a matrix of observed brokerage scores by vertex. Access it with the $
accessor and examine the matrix.
$raw.nli strike_gf
w_I w_O b_IO b_OI b_O t
Xavier 0 0 0 0 0 0
Utrecht 8 0 0 0 0 8
Frank 0 0 0 0 0 0
Domingo 0 0 0 0 0 0
Norm 18 0 5 5 0 28
Hal 2 0 0 0 0 2
Russ 4 0 0 0 0 4
Karl 0 0 2 2 0 4
Bob 14 0 10 10 2 36
Quint 4 0 0 0 0 4
Wendle 0 0 0 0 0 0
Ozzie 0 0 1 1 0 2
Ike 4 0 0 0 0 4
Ted 2 0 0 0 0 2
Sam 8 0 0 0 0 8
Vern 2 0 0 0 0 2
Gill 10 0 0 0 0 10
Lanny 2 0 0 0 0 2
Mike 0 0 0 0 0 0
Carlos 0 0 0 0 0 0
Alejandro 0 0 3 3 0 6
Paul 2 0 0 0 0 2
Eduardo 0 0 0 0 0 0
John 12 0 0 0 0 12
The types of brokerage roles are defined (and codified above) in terms of group membership as follows:
w_I
: Coordinator rolew_O
: Itinerant broker roleb_IO
: Gatekeeper roleb_OI
: Representative roleb_O
: Liason rolet
: Total (cumulative) role occupancy
$raw.nli strike_gf
w_I w_O b_IO b_OI b_O t
Xavier 0 0 0 0 0 0
Utrecht 8 0 0 0 0 8
Frank 0 0 0 0 0 0
Domingo 0 0 0 0 0 0
Norm 18 0 5 5 0 28
Hal 2 0 0 0 0 2
Russ 4 0 0 0 0 4
Karl 0 0 2 2 0 4
Bob 14 0 10 10 2 36
Quint 4 0 0 0 0 4
Wendle 0 0 0 0 0 0
Ozzie 0 0 1 1 0 2
Ike 4 0 0 0 0 4
Ted 2 0 0 0 0 2
Sam 8 0 0 0 0 8
Vern 2 0 0 0 0 2
Gill 10 0 0 0 0 10
Lanny 2 0 0 0 0 2
Mike 0 0 0 0 0 0
Carlos 0 0 0 0 0 0
Alejandro 0 0 3 3 0 6
Paul 2 0 0 0 0 2
Eduardo 0 0 0 0 0 0
John 12 0 0 0 0 12
Next, we will get the total brokerage score but only count representative/gatekeeper once since it is undirected network (the total score is in the 6th column, while the gatekeeper score is in the fourth).
$raw.nli[, 6] - strike_gf$raw.nli[, 4] strike_gf
Xavier Utrecht Frank Domingo Norm Hal Russ Karl
0 8 0 0 23 2 4 2
Bob Quint Wendle Ozzie Ike Ted Sam Vern
26 4 0 1 4 2 8 2
Gill Lanny Mike Carlos Alejandro Paul Eduardo John
10 2 0 0 3 2 0 12
Now, visualize the network sizing the nodes by total brokerage score.
gplot(strike_net,
gmode = "graph",
label = network.vertex.names(strike_net),
coord = coords,
label.col = "black",
label.cex = 0.6,
vertex.col = strike_attrs[["Group"]],
label.pos = 5,
vertex.cex = scales::rescale(strike_gf$raw.nli[, 6] - strike_gf$raw.nli[, 4],
to = c(1, 5)),
main = "Gould & Fernandez (Total Brokerage)")
We will hold off for now on creating an interactive table for brokerage but feel free to give it a shot on your own.
That’s all for statnet for now.