# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# # Network triads with networkx
#
# This Jupyter notebook uses the [networkx Python library](https://networkx.github.io/) to do a census of the different kinds of triads in a directed network imported from a Gephi edge list.

# ## Step 1. Import modules

#networkx is used to create and analyze the network
import networkx as nx
#os lets you change the working directory
import os
#csv lets your import data from a CSV file
import csv

# ## Step 2. Specify source directory and edge file
# Replace `/Users/qad/Documents/aknet` with the path to the working directory below to the directory where you have your source files.
#
# For instance, the default path to the Documents directory is (substituting your user name on the computer for YOUR-USER-NAME):
#
# - On Mac: '/Users/YOUR-USER-NAME/Documents'
# - On Windows: 'C:\\\Users\\\YOUR-USER-NAME\\\Documents'
#
# Then, replace `AK-I.csv` with the name of the Gephi edge list file that you want to use. It should end in .csv, and have a header row that reads *Source*, *Target*, *Weight*. It can have additional columns, but needs to have those three columns, in that order. If your file does not have a header row (but is laid out in the right order), type a `#` in front of the line that reads `next(edgereader)`. 

#Put the path to the directory with your edge file below
os.chdir('/Users/qad/Documents/aknet')
#Put the name of your edge file below
edges = 'AK-I.csv'

# ## Step 3. Create graph
# Run the cells below to instantiate a directed graph, and populate it with data from your edge file.

G = nx.DiGraph()

with open(edges, 'r') as edgefile:
    edgereader = csv.reader(edgefile)
    next(edgereader)
    for row in edgereader:
        
        weight = {'weight':int(row[2])}
        G.add_edges_from([(row[0],row[1],weight)])

# Run the cells below to see the edges of the network you've created, as well as the in-degree and out-degree of the nodes.

#Shows all edges
G.edges()

#Shows in-degree
G.in_degree(weight='weight')

#Shows out-degree
G.out_degree(weight='weight')

# ## Step 4. Generate lists of triads
# There are 16 different types of triads that networkx can calculate for directed nteworks, and each has a numeric identifier:
# ![network triads](https://i.stack.imgur.com/9Xo0R.png)
# Looking at the diagram, it shouldn't be a surprise that there are thousands of instances of some of these triads (especially 003). 
#
# Run the code cell below to see how many instances of each triad type can be found in your network.

nx.triadic_census(G)

# Once you know how many instances there are of some kinds of triads, you'll probably want to know what those triads are. networkx doesn't have a function to do this out of the box, but the code below (from [this Stack Overflow answer](https://stackoverflow.com/a/56124663)) creates the function. Run the code cell below.

# +
import itertools
def _tricode(G, v, u, w):
    """Returns the integer code of the given triad.

    This is some fancy magic that comes from Batagelj and Mrvar's paper. It
    treats each edge joining a pair of `v`, `u`, and `w` as a bit in
    the binary representation of an integer.

    """
    combos = ((v, u, 1), (u, v, 2), (v, w, 4), (w, v, 8), (u, w, 16),
              (w, u, 32))
    return sum(x for u, v, x in combos if v in G[u])

#: The integer codes representing each type of triad.
#: Triads that are the same up to symmetry have the same code.
TRICODES = (1, 2, 2, 3, 2, 4, 6, 8, 2, 6, 5, 7, 3, 8, 7, 11, 2, 6, 4, 8, 5, 9,
            9, 13, 6, 10, 9, 14, 7, 14, 12, 15, 2, 5, 6, 7, 6, 9, 10, 14, 4, 9,
            9, 12, 8, 13, 14, 15, 3, 7, 8, 11, 7, 12, 14, 15, 8, 14, 13, 15,
            11, 15, 15, 16)

#: The names of each type of triad. The order of the elements is
#: important: it corresponds to the tricodes given in :data:`TRICODES`.
TRIAD_NAMES = ('003', '012', '102', '021D', '021U', '021C', '111D', '111U',
               '030T', '030C', '201', '120D', '120U', '120C', '210', '300')

#: A dictionary mapping triad code to triad name.
TRICODE_TO_NAME = {i: TRIAD_NAMES[code - 1] for i, code in enumerate(TRICODES)}

triad_nodes = {name: set([]) for name in TRIAD_NAMES}
m = {v: i for i, v in enumerate(G)}
for v in G:
    vnbrs = set(G.pred[v]) | set(G.succ[v])
    for u in vnbrs:
        if m[u] > m[v]:
            unbrs = set(G.pred[u]) | set(G.succ[u])
            neighbors = (vnbrs | unbrs) - {u, v}
            not_neighbors = set(G.nodes()) - neighbors - {u, v}
            # Find dyadic triads
            for w in not_neighbors:
                if v in G[u] and u in G[v]:
                    triad_nodes['102'].add(tuple(sorted([u, v, w])))
                else:
                    triad_nodes['012'].add(tuple(sorted([u, v, w])))
            for w in neighbors:
                if m[u] < m[w] or (m[v] < m[w] < m[u] and
                                   v not in G.pred[w] and
                                   v not in G.succ[w]):
                    code = _tricode(G, v, u, w)
                    triad_nodes[TRICODE_TO_NAME[code]].add(
                        tuple(sorted([u, v, w])))
# find null triads
all_tuples = set()
for s in triad_nodes.values():
    all_tuples = all_tuples.union(s)
triad_nodes['003'] = set(itertools.combinations(G.nodes(), 3)).difference(all_tuples)
# -

# In the code cell below, you can replace `210` with the identifier for any triad type to see what those triads are:
# ![network triads](https://i.stack.imgur.com/9Xo0R.png)

print(triad_nodes['210'])

# ## Suggested citation
# <NAME>. *Network Triads with Networkx*. Jupyter notebook. https://github.com/quinnanya/network-triads. 2019.

StarcoderdataJupyterScriptsDedupFiltered