An open API service indexing awesome lists of open source software.

https://github.com/knapply/constraint


https://github.com/knapply/constraint

Last synced: 4 months ago
JSON representation

Awesome Lists containing this project

README

          

---
title: "Constraint"
author: "Brendan Knapp"
output:
github_document:
toc: true
editor_options:
chunk_output_type: console
always_allow_html: true
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, fig.align = "center",
comment = "#>")
options(width = 200)
```

```{r}
library(igraph, warn.conflicts = FALSE)
```

```{r}
strike_net <- "*Vertices 24\r\n 1 \"Xavier\" 0.8220 0.7997\r\n 2 \"Utrecht\" 0.7189 0.5679\r\n 3 \"Frank\" 0.1500 0.8500\r\n 4 \"Domingo\" 0.3480 0.1500\r\n 5 \"Norm\" 0.5938 0.5446\r\n 6 \"Hal\" 0.3316 0.6461\r\n 7 \"Russ\" 0.8359 0.4228\r\n 8 \"Karl\" 0.4469 0.8339\r\n 9 \"Bob\" 0.4608 0.5138\r\n 10 \"Quint\" 0.8500 0.4964\r\n 11 \"Wendle\" 0.8001 0.8384\r\n 12 \"Ozzie\" 0.5793 0.7488\r\n 13 \"Ike\" 0.3135 0.5410\r\n 14 \"Ted\" 0.8127 0.2755\r\n 15 \"Sam\" 0.7070 0.6662\r\n 16 \"Vern\" 0.6746 0.3554\r\n 17 \"Gill\" 0.2525 0.7417\r\n 18 \"Lanny\" 0.4151 0.6905\r\n 19 \"Mike\" 0.3520 0.4907\r\n 20 \"Carlos\" 0.2922 0.1971\r\n 21 \"Alejandro\" 0.3967 0.3307\r\n 22 \"Paul\" 0.6923 0.4682\r\n 23 \"Eduardo\" 0.3200 0.1712\r\n 24 \"John\" 0.3770 0.6861\r\n*Arcs\r\n 1 11 1.0000\r\n 1 15 1.0000\r\n 2 5 1.0000\r\n 2 7 1.0000\r\n 2 10 1.0000\r\n 2 15 1.0000\r\n 3 17 1.0000\r\n 4 20 1.0000\r\n 4 21 1.0000\r\n 4 23 1.0000\r\n 5 2 1.0000\r\n 5 9 1.0000\r\n 5 12 1.0000\r\n 5 15 1.0000\r\n 5 16 1.0000\r\n 5 22 1.0000\r\n 6 9 1.0000\r\n 6 17 1.0000\r\n 6 24 1.0000\r\n 7 2 1.0000\r\n 7 10 1.0000\r\n 7 14 1.0000\r\n 8 12 1.0000\r\n 8 18 1.0000\r\n 8 24 1.0000\r\n 9 5 1.0000\r\n 9 6 1.0000\r\n 9 13 1.0000\r\n 9 18 1.0000\r\n 9 19 1.0000\r\n 9 21 1.0000\r\n 9 24 1.0000\r\n 10 2 1.0000\r\n 10 7 1.0000\r\n 10 22 1.0000\r\n 11 1 1.0000\r\n 11 15 1.0000\r\n 12 5 1.0000\r\n 12 8 1.0000\r\n 13 9 1.0000\r\n 13 17 1.0000\r\n 13 19 1.0000\r\n 14 7 1.0000\r\n 14 16 1.0000\r\n 15 1 1.0000\r\n 15 2 1.0000\r\n 15 5 1.0000\r\n 15 11 1.0000\r\n 16 5 1.0000\r\n 16 14 1.0000\r\n 17 3 1.0000\r\n 17 6 1.0000\r\n 17 13 1.0000\r\n 17 24 1.0000\r\n 18 8 1.0000\r\n 18 9 1.0000\r\n 18 24 1.0000\r\n 19 9 1.0000\r\n 19 13 1.0000\r\n 20 4 1.0000\r\n 20 21 1.0000\r\n 20 23 1.0000\r\n 21 4 1.0000\r\n 21 9 1.0000\r\n 21 20 1.0000\r\n 21 23 1.0000\r\n 22 5 1.0000\r\n 22 10 1.0000\r\n 23 4 1.0000\r\n 23 20 1.0000\r\n 23 21 1.0000\r\n 24 6 1.0000\r\n 24 8 1.0000\r\n 24 9 1.0000\r\n 24 17 1.0000\r\n 24 18 1.0000\r\n"
temp_file <- tempfile(fileext = ".net")
readr::write_file(strike_net, temp_file)

strike_g <- as.undirected(
read_graph(temp_file, format = "pajek")
)

plot_igraph <- function(g, ...) {
plot(g, asp = 0.5,
vertex.size = 4, vertex.label.dist = 1.5, ...)
}

plot_igraph(strike_g, main = "Strike")
```

# Ego Model

## UCINET Results

```
1
Constra
int
-------
1 Xavier 1.125
2 Utrecht 0.563
3 Frank 1 ???? missing in ego.txt
4 Domingo 0.926
5 Norm 0.236
6 Hal 0.840
7 Russ 0.611
8 Karl 0.611
9 Bob 0.287
10 Quint 0.611
11 Wendle 1.125
12 Ozzie 0.500
13 Ike 0.611
14 Ted 0.500
15 Sam 0.563
16 Vern 0.500
17 Gill 0.406
18 Lanny 0.840
19 Mike 1.125
20 Carlos 0.926
21 Alejandro 0.583
22 Paul 0.500
23 Eduardo 0.926
24 John 0.522
```

```{r}
ucinet_ego_results <- c(
1.125, 0.563, 1, 0.926, 0.236, 0.840, 0.611, 0.611, 0.287, 0.611, 1.125,
0.500, 0.611, 0.500, 0.563, 0.500, 0.406, 0.840, 1.125, 0.926, 0.583, 0.500, 0.926, 0.522
)
```

## `{igraph}` Replication

Extract each vertex's ego (order = 1) network and run `igraph::constraint()` on them individually.

```{r}
ego_constraint <- function(g, .order = 1L, .round = 3L, .nm = FALSE) {
if (!is_named(g)) {
vertex_attr(g, "name") <- sprintf("n%d", seq_len(vcount(g)))
}

ego_nets <- make_ego_graph(g, order = .order) # get all ego networks
ego_names <- vertex_attr(g, "name") # grab the egos' names

# walk through each ego network and measure constraint of the ego vertex
out <- mapply(function(.ego_net, .ego_name) {
constraint(.ego_net, nodes = .ego_name)
}, ego_nets, ego_names)

out <- round(out, .round)
if (.nm) out else unname(out)
}
```

```{r}
ego_constraint(strike_g, .nm = TRUE)
```

## Confirmation

```{r}
data.frame(
name = vertex_attr(strike_g, "name"),
`UCINET "Ego"` = round(ucinet_ego_results, 3),
`ego_constraint()` = ego_constraint(strike_g),
check.names = FALSE
)
stopifnot(
identical(
round(ucinet_ego_results, 2),
ego_constraint(strike_g, .round = 2)
)
)
```

# "Whole Network"

Identical to `igraph::constraint()`.

## UCINET Results

```
3
Const
raint
-----
1 Xavier 0.953
2 Utrecht 0.405
3 Frank 1
4 Domingo 0.866
5 Norm 0.198
6 Hal 0.536
7 Russ 0.482
8 Karl 0.469
9 Bob 0.238
10 Quint 0.482
11 Wendle 0.953
12 Ozzie 0.500
13 Ike 0.506
14 Ted 0.500
15 Sam 0.464
16 Vern 0.500
17 Gill 0.326
18 Lanny 0.562
19 Mike 0.771
20 Carlos 0.866
21 Alejandro 0.583
22 Paul 0.500
23 Eduardo 0.866
24 John 0.418
```

```{r}
ucinet_whole_results <- c(
0.953, 0.405, 1, 0.866, 0.198, 0.536, 0.482, 0.469, 0.238, 0.482, 0.953,
0.500, 0.506, 0.500, 0.464, 0.500, 0.326, 0.562, 0.771, 0.866, 0.583,
0.500, 0.866, 0.418
)
```

## `{igraph}` Replication

```{r}
constraint(strike_g)
```

## Confirmation

```{r}
stopifnot(
identical(
ucinet_whole_results,
constraint(strike_g) %>% round(3L) %>% unname()
)
)
```

```{r}
data.frame(
name = vertex_attr(strike_g, "name"),
`UCINET "Whole Network"` = ucinet_whole_results,
`igraph::constraint()` = round(constraint(strike_g), 3L),
check.names = FALSE,
row.names = NULL
)
```

## "Whole Network"?

What UCINET is calling "Whole Network" is misleading.

```{r}
ego_g <- make_ego_graph(strike_g, nodes = "Xavier")[[1L]]
plot_igraph(ego_g, main = "Xavier's Ego")
```

```{r}
extended_ego_g <- make_ego_graph(strike_g, order = 2L, nodes = "Xavier")[[1L]]
plot_igraph(extended_ego_g, main = "Xavier's Extended Ego")
```

### Extended Ego

However UCINET calulates it, "Whole Network" and `igraph::constraint()` measure the "extended" ego networks (order = 2).

```{r}
extended_ego_constraint <- function(g, ...) {
ego_constraint(g, .order = 2L, ...)
}

extended_ego_constraint(strike_g)
```

#### Confirmation

```{r}
stopifnot(
identical(
constraint(strike_g) %>% round(3) %>% unname(),
extended_ego_constraint(strike_g)
)
)
stopifnot(
identical(
extended_ego_constraint(strike_g),
ucinet_whole_results
)
)
```

"Whole Network" may describe UCINET's other "Whole Network" Structural Holes metrics, but not constraint.

```{r}
data.frame(
`igraph::constraint()` = round(constraint(strike_g), 3L),
`extended_ego_constraint()` = extended_ego_constraint(strike_g),
`UCINET "Whole Network"` = ucinet_whole_results,
check.names = FALSE,
row.names = NULL
)
```

# Maximum Constraint

Either interpretation of Burt's Constraint can yield values > 1.

```{r}
el <- matrix(
c(1, 2,
1, 3,
2, 3), ncol = 2L, byrow = TRUE)

g <- graph_from_edgelist(el, directed = FALSE)
vertex_attr(g, "name") <- letters[seq_len(vcount(g))]
plot(g)
```

```{r}
data.frame(
name = vertex_attr(g, "name"),
Ego = ego_constraint(g),
`"Whole Network"/Extended Ego` = constraint(g),
check.names = FALSE,
row.names = NULL
)
```

```{r}
el <- matrix(
c(1, 2,
1, 3,
2, 3,
3, 4), ncol = 2L, byrow = TRUE)

g <- graph_from_edgelist(el, directed = FALSE)
vertex_attr(g, "name") <- letters[seq_len(vcount(g))]
plot(g)
```

```{r}
data.frame(
name = vertex_attr(g, "name"),
Ego = ego_constraint(g),
`"Whole Network"/Extended Ego` = constraint(g),
check.names = FALSE,
row.names = NULL
)
```

```{r}
el <- matrix(
c(1, 2,
1, 3,
2, 3,
3, 4,
4, 5), ncol = 2L, byrow = TRUE)

g <- graph_from_edgelist(el, directed = FALSE)
vertex_attr(g, "name") <- letters[seq_len(vcount(g))]
plot(g)
```

```{r}
data.frame(
name = vertex_attr(g, "name"),
Ego = ego_constraint(g),
`"Whole Network"/Extended Ego` = constraint(g),
check.names = FALSE,
row.names = NULL
)
```