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 [2]:
with open("alliance_trade_edges.csv","r") as f:
    for i in range(6):
        print((f.readline()).rstrip())


year,nation1,nation2,alliance,trade
1950,2,20,1,1
1950,2,40,1,1
1950,2,41,1,1
1950,2,42,1,1
1950,2,70,1,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 [3]:
# 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 [4]:
counts_by_year = compute_counts_from_file("./alliance_trade_edges.csv", "./alliance_trade_nodes.csv", method="intersection")


INFO:multiplex_markov_chain:Getting counts for 1950-->1951
INFO:multiplex_markov_chain:Getting counts for 1951-->1952
INFO:multiplex_markov_chain:Getting counts for 1952-->1953
INFO:multiplex_markov_chain:Getting counts for 1953-->1954
INFO:multiplex_markov_chain:Getting counts for 1954-->1955
INFO:multiplex_markov_chain:Getting counts for 1955-->1956
INFO:multiplex_markov_chain:Getting counts for 1956-->1957
INFO:multiplex_markov_chain:Getting counts for 1957-->1958
INFO:multiplex_markov_chain:Getting counts for 1958-->1959
INFO:multiplex_markov_chain:Getting counts for 1959-->1960
INFO:multiplex_markov_chain:Getting counts for 1960-->1961
INFO:multiplex_markov_chain:Getting counts for 1961-->1962
INFO:multiplex_markov_chain:Getting counts for 1962-->1963
INFO:multiplex_markov_chain:Getting counts for 1963-->1964
INFO:multiplex_markov_chain:Getting counts for 1964-->1965
INFO:multiplex_markov_chain:Getting counts for 1965-->1966
INFO:multiplex_markov_chain:Getting counts for 1966-->1967
INFO:multiplex_markov_chain:Getting counts for 1967-->1968
INFO:multiplex_markov_chain:Getting counts for 1968-->1969
INFO:multiplex_markov_chain:Getting counts for 1969-->1970
INFO:multiplex_markov_chain:Getting counts for 1970-->1971
INFO:multiplex_markov_chain:Getting counts for 1971-->1972
INFO:multiplex_markov_chain:Getting counts for 1972-->1973
INFO:multiplex_markov_chain:Getting counts for 1973-->1974
INFO:multiplex_markov_chain:Getting counts for 1974-->1975
INFO:multiplex_markov_chain:Getting counts for 1975-->1976
INFO:multiplex_markov_chain:Getting counts for 1976-->1977
INFO:multiplex_markov_chain:Getting counts for 1977-->1978
INFO:multiplex_markov_chain:Getting counts for 1978-->1979
INFO:multiplex_markov_chain:Getting counts for 1979-->1980
INFO:multiplex_markov_chain:Getting counts for 1980-->1981
INFO:multiplex_markov_chain:Getting counts for 1981-->1982
INFO:multiplex_markov_chain:Getting counts for 1982-->1983
INFO:multiplex_markov_chain:Getting counts for 1983-->1984
INFO:multiplex_markov_chain:Getting counts for 1984-->1985
INFO:multiplex_markov_chain:Getting counts for 1985-->1986
INFO:multiplex_markov_chain:Getting counts for 1986-->1987
INFO:multiplex_markov_chain:Getting counts for 1987-->1988
INFO:multiplex_markov_chain:Getting counts for 1988-->1989
INFO:multiplex_markov_chain:Getting counts for 1989-->1990
INFO:multiplex_markov_chain:Getting counts for 1990-->1991
INFO:multiplex_markov_chain:Getting counts for 1991-->1992
INFO:multiplex_markov_chain:Getting counts for 1992-->1993
INFO:multiplex_markov_chain:Getting counts for 1993-->1994
INFO:multiplex_markov_chain:Getting counts for 1994-->1995
INFO:multiplex_markov_chain:Getting counts for 1995-->1996
INFO:multiplex_markov_chain:Getting counts for 1996-->1997
INFO:multiplex_markov_chain:Getting counts for 1997-->1998
INFO:multiplex_markov_chain:Getting counts for 1998-->1999
INFO:multiplex_markov_chain:Getting counts for 1999-->2000
INFO:multiplex_markov_chain:Getting counts for 2000-->2001
INFO:multiplex_markov_chain:Getting counts for 2001-->2002
INFO:multiplex_markov_chain:Reached end of edge list
INFO:multiplex_markov_chain:Getting counts for 2002-->2003

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


In [5]:
# check how the counts look
counts_by_year["1950"]


Out[5]:
array([1360,    2,   85,    0,    0,  100,    0,   13,   71,    0,  900,
         26,    0,   19,    0,  199])

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 [6]:
# make np array of dictionary and sum counts
counts = np.sum(np.array(list(counts_by_year.values())),axis=0)

In [7]:
print(counts)


[319388    485  19285     50    108  11964      3   1552  17205     53
 165360    707     10   1311    196  25513]

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 [8]:
from MultiplexMarkovChain import *

In [9]:
mmc = MultiplexMarkovChain(counts)

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


Out[10]:
array([  9.41561619e-01,   1.43273233e-03,   5.68552999e-02,
         1.50348455e-04,   7.99647861e-03,   8.77778593e-01,
         2.93448757e-04,   1.13931480e-01,   9.38531274e-02,
         2.94552417e-04,   9.01990411e-01,   3.86190946e-03,
         4.06895021e-04,   4.85314789e-02,   7.28711992e-03,
         9.43774506e-01])

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


Out[11]:
array([  9.38457991e-01,   2.33336637e-03,   5.90617928e-02,
         1.46850262e-04,   7.35806713e-03,   9.33433290e-01,
         4.63079478e-04,   5.87455636e-02,   8.81069685e-02,
         2.19067704e-04,   9.09412815e-01,   2.26114893e-03,
         6.90810879e-04,   8.76352253e-02,   7.13033573e-03,
         9.04543628e-01])

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


Out[12]:
array([ 339208.,   13627.,  183325.,   27030.])

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 [13]:
import matplotlib.pylab as plt

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

In [15]:
# 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:'at', 1:'At', 2:'aT', 3:'AT'}
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 0x7f001645e2b0>

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]: