In [1]:
# show plots inside the notebook 
%matplotlib inline

Example: Network of alliance and trade between countries

This notebook shows a step by step example of how to construct the Multiplex Markov Chain from longitudinal data on a multiplex network with two layers. The Multiplex Markov chain helps to detect "dynamical spillover". See the following paper for details:

In our dataset, the nodes are countries in the world and the two layers are alliance and trade. The data starts in 1950 and ends in 2003. The input data has alliance as the first layer and trade as the second layer. Here are the first few lines of the file that stores the data about edges present in the network.


In [33]:
with open("groom_status_edges.csv","r") as f:
    for i in range(6):
        print((f.readline()).rstrip())


week,monkey1,monkey2,grooming,status
0,38412,39269,1,0
0,37830,34839,1,0
0,40541,27993,0,1
0,37977,41261,1,0
0,39925,35532,0,1

The multiplex network has two layers. Therefore, every edge in this network has four possible states: no alliance or trade (state:0 {labelled at}), only alliance (state:1 {labelled At}), only trade (state:2 {labelled aT}) or both alliance and trade (state:3 {labelled AT}). Therefore, from one year to the next, there are sixteen possible transitions. The transition from state $S_t = \{0,1,2,3\}$ to state $S_{t+1} = \{0,1,2,3\}$ is denoted as transition $i = 4S_t + S_{t+1}$.


In [28]:
# first convert the multiplex network data into transition counts of a Markov chain
from extract_counts import *

The following gives us a dictionary with the index as the year. The entry corresponding to the year gives us the counts associated with that year to the next, say $y \to y+1$. There are several instances in the dataset where a country exists in year $y$ but not in year $y+1$ and vice versa. The parameter "method" decides how we handle such cases. The parameter accepts two values: "union", "intersection". When we choose "union" the node is included in the counts if it is present in at least one of the timesteps. If we choose "intersection" the node is included in the counts only if it is present in both years.


In [35]:
counts_by_year = compute_counts_from_file("./agg_status_edges.csv", "./ags_nodes.csv", method="intersection")


WARNING:multiplex_markov_chain:Line not in proper format. Ignoring 1,39085,0,1
INFO:multiplex_markov_chain:Getting counts for 0-->1
WARNING:multiplex_markov_chain:Line not in proper format. Ignoring 2,33116,1,0
INFO:multiplex_markov_chain:Getting counts for 1-->2
WARNING:multiplex_markov_chain:Line not in proper format. Ignoring 3,40262,1,0
INFO:multiplex_markov_chain:Getting counts for 2-->3
INFO:multiplex_markov_chain:Getting counts for 3-->4
INFO:multiplex_markov_chain:Getting counts for 4-->5
INFO:multiplex_markov_chain:Reached end of edge list
INFO:multiplex_markov_chain:Getting counts for 5-->6

An array (of size $16$) which contains the counts of each of the $16$ possible transitions is associated with each year.


In [15]:
# check how the counts look
counts_by_year["0"]


Out[15]:
array([3764,  173,  108,    8,  167,  116,    3,    5,  164,   13,   16,
          0,   10,    7,    3,    3])

Having obtained the counts, we can now construct the Multiplex Markov chain, compute the null model and check for transitions that are significantly different due to interaction between the two layers. Since our purpose here is to illustrate how to use the code, we will accumulate all the counts from 1950 to 2003. We do a more careful analysis of whether such an aggregation is justified in the article ().


In [16]:
# make np array of dictionary and sum counts
counts = np.sum(np.array(list(counts_by_year.values())),axis=0)

In [17]:
print(counts)


[23288   945   918    35   943   641    38    14   907    41    95     4
    35    26     6     3]

The above counts are what we will use to construct our Multiplex Markov chain and the corresponding null model. We import the corresponding class and construct the Multiplex Markov chain.


In [18]:
from MultiplexMarkovChain import *

In [19]:
mmc = MultiplexMarkovChain(counts)

In [20]:
# print the parameters of the Markov chain associated with the transitions (this is the probability of transition i)
mmc.get_parameters()


Out[20]:
array([ 0.92453355,  0.03755459,  0.03648273,  0.00142914,  0.57560976,
        0.39146341,  0.02378049,  0.00914634,  0.86393911,  0.03996194,
        0.09134158,  0.00475737,  0.48648649,  0.36486486,  0.09459459,
        0.05405405])

In [21]:
#print the corresponding null probability
mmc.get_null_prob()


MultiplexMarkovChain.py:231: UserWarning: Some of the state totals are less than 100. Gaussian approximation may not be justified.
  warn("Some of the state totals are less than 100. Gaussian approximation may not be justified.")
Out[21]:
array([ 0.92485491,  0.03764136,  0.03603703,  0.0014667 ,  0.57648342,
        0.38601285,  0.02246271,  0.01504102,  0.86729299,  0.03529861,
        0.09359895,  0.00380945,  0.54060375,  0.36198785,  0.05834238,
        0.03906602])

In [22]:
# print state totals
mmc.get_state_totals()


Out[22]:
array([ 25186.,   1636.,   1047.,     70.])

In order determine if spillover exists between the alliance and trade layer we need to check if the transition parameters of the Multiplex Markov chain are "substantially" different from those of the null model. We can do this by checking if the confidence interval for the Multiplex Markov chain and the null model overlap. In this example (and in the article) we choose the 99% confidence interval. Since our state totals are of the order of $10^4$, we can construct the confidence interval using a Gaussian approximation.

Note: For a Gaussian distribution the 99% confidence interval is given by $\mu \pm 2.58 \sigma$, where $\mu$ is the mean and $\sigma$ is the standard deviation.


In [23]:
import matplotlib.pylab as plt

In [24]:
z_alpha = 2.58 # change to alter level of confidence

In [26]:
# Plot to check for overlap in confidence intervals
plt.clf()
fig = plt.figure(figsize=(16, 8))
y = mmc.get_parameters()
y_std = z_alpha*mmc.get_std_dev()
y_null = mmc.get_null_prob()
y_null_std = yerr=z_alpha*mmc.get_null_std_dev()
state_labels = {0:'gs', 1:'Gs', 2:'gS', 3:'GS'}
xlabels = []
# prepare transition label
for i in range(4):
    for j in range(4):
        index = 4*i + j
        ax = plt.subplot2grid((4,4),(i,j))
        ax.errorbar([1], y[index], yerr=y_std[index], fmt="o", label="MMC")
        ax.errorbar([1], y_null[index], yerr=y_null_std[index], fmt="s", color="r", label="Null")
        ttext = state_labels[i] + "-->" + state_labels[j]
        ax.set_title(ttext)
        ax.tick_params(axis='x', which='both', bottom='off', top='off', labelbottom='off') #don't display xticks
fig.subplots_adjust(wspace=0.4)


<matplotlib.figure.Figure at 0x14e82550>

From the above panel of figures we can determine which of these transitions exhibit dynamical spillover. We discuss the implications of the spillover in the article that can be found


In [15]: