Assignment 2 - Network Connectivity¶

In this assignment you will go through the process of importing and analyzing an internal email communication network between employees of a mid-sized manufacturing company. Each node represents an employee and each directed edge between two nodes represents an individual email. The left node represents the sender and the right node represents the recipient. We will also store the timestamp of each email.

In [ ]:
import networkx as nx

#!head assets/email_network.txt

Question 1¶

Using networkx, load up the directed multigraph from assets/email_network.txt. Make sure the node names are strings.

This function should return a directed multigraph networkx graph.

In [ ]:
def answer_one():
    
    # YOUR CODE HERE
    G = nx.read_edgelist('assets/email_network.txt', delimiter='\t', data=[('time', int)], create_using=nx.MultiDiGraph())
    return G
In [ ]:
ans_one = answer_one()

Question 2¶

How many employees are represented in the network?

How many sender->recepient pairs of employees are there in the network such that sender sent at least one email to recepient? Note that even if a sender sent multiple messages to a recepient, they should only be counted once. You should exclude cases where an employee sent emails to themselves from this count.

This function should return a tuple with two integers (#employees, # sender->recepient pairs).

In [ ]:
def answer_two():
    # YOUR CODE HERE
    G = answer_one()
    return len(G.nodes()), len(G.edges())
In [ ]:
ans_two = answer_two()

Question 3¶

  • Part 1. Assume that information in this company can only be exchanged through email.

    When an employee sends an email to another employee, a communication channel has been created, allowing the sender to provide information to the reciever, but not viceversa.

    Based on the emails sent in the data, is it possible for information to go from every employee to every other employee?

  • Part 2. Now assume that a communication channel established by an email allows information to be exchanged both ways.

    Based on the emails sent in the data, is it possible for information to go from every employee to every other employee?

This function should return a tuple of bools (part1, part2).

In [ ]:
def answer_three():
    # YOUR CODE HERE
    G = answer_one()
    return nx.is_strongly_connected(G), nx.is_connected(G.to_undirected())
In [ ]:
ans_three = answer_three()

Question 4¶

How many nodes are in the largest weakly connected component of the graph?

This function should return an int.

In [ ]:
def answer_four():
    # YOUR CODE HERE
    G = answer_one()
    wccs = nx.weakly_connected_components(G)
    return len(max(wccs, key=len))
In [ ]:
ans_four = answer_four()

Question 5¶

How many nodes are in the largest strongly connected component?

This function should return an int

In [ ]:
def answer_five():
    # YOUR CODE HERE
    G = answer_one()
    sccs = nx.strongly_connected_components(G)
    return len(max(sccs, key=len))  
In [ ]:
ans_five = answer_five()

Question 6¶

Using the NetworkX functions strongly_connected_components and subgraph, find the subgraph of nodes in the largest strongly connected component. Call this graph G_sc.

This function should return a networkx MultiDiGraph named G_sc.

In [ ]:
def answer_six():
    # YOUR CODE HERE
    G = answer_one()
    scc_subs = max(nx.strongly_connected_components(G), key=len)
    H = G.subgraph(scc_subs)
    return H
In [ ]:
ans_six = answer_six()
assert type(ans_six) == nx.MultiDiGraph , "Your return type should be a MultiDiGraph object"

Question 7¶

What is the average distance between nodes in G_sc?

This function should return a float.

In [ ]:
def answer_seven():
    # YOUR CODE HERE
    G = answer_six()
    return nx.average_shortest_path_length(G)
In [ ]:
ans_seven = answer_seven()

Question 8¶

What is the largest possible distance between two employees in G_sc?

This function should return an int.

In [ ]:
def answer_eight():
    # YOUR CODE HERE
    G = answer_six()
    return nx.diameter(G)
In [ ]:
ans_eight = answer_eight()

Question 9¶

What is the set of nodes in G_sc with eccentricity equal to the diameter?

This function should return a set of the node(s).

In [ ]:
def answer_nine():
    # YOUR CODE HERE
    G = answer_six()
    return set(nx.periphery(G))
In [ ]:
ans_nine = answer_nine()
assert type(ans_nine) == set, "Student answer must return a set"

Question 10¶

What is the set of node(s) in G_sc with eccentricity equal to the radius?

This function should return a set of the node(s).

In [ ]:
def answer_ten():
    # YOUR CODE HERE
    G = answer_six()
    return set(nx.center(G))
In [ ]:
ans_ten = answer_ten()
assert type(ans_ten) == set, "Student answer must return a set"

Question 11¶

Which node in G_sc has the most shortest paths to other nodes whose distance equal the diameter of G_sc?

For the node with the most such shortest paths, how many of these paths are there?

This function should return a tuple (name of node, number of paths).

In [ ]:
def answer_eleven():
    # YOUR CODE HERE
    G = answer_six()
    d = nx.diameter(G)
    peripheries = nx.periphery(G)
    max_count = -1
    result_node = None
    for node in peripheries:
        count = 0
        sp = nx.shortest_path_length(G, node)
        for key, value in sp.items():
            if value == d:
                count += 1        
        if count > max_count:
            result_node = node
            max_count = count

    return result_node, max_count
In [ ]:
ans_eleven = answer_eleven()
assert type(ans_eleven) == tuple, "Student answer must be a tuple"

Question 12¶

Suppose you want to prevent communication flow from the node that you found in question 11 to node 10. What is the smallest number of nodes you would need to remove from the graph (you're not allowed to remove the node from the previous question or 10)?

This function should return an integer.

In [ ]:
def answer_twelve():
    # YOUR CODE HERE
    G = answer_six()
    center = nx.center(G)[0]
    node = answer_eleven()[0]
    return len(nx.minimum_node_cut(G, center, node))
In [ ]:
ans_twelve = answer_twelve()

Question 13¶

Convert the graph G_sc into an undirected graph by removing the direction of the edges of G_sc. Call the new graph G_un.

This function should return a networkx Graph.

In [ ]:
def answer_thirteen():
    # YOUR CODE HERE
    G = answer_six()
    undir_subgraph = G.to_undirected()
    G_un = nx.Graph(undir_subgraph)
    return G_un
In [ ]:
ans_thirteen = answer_thirteen()
assert type(ans_thirteen) == nx.Graph , "Your return type should be a Graph object"

Question 14¶

What is the transitivity and average clustering coefficient of graph G_un?

This function should return a tuple (transitivity, avg clutering).
Note: DO NOT round up your answer.

In [ ]:
def answer_fourteen():
    # YOUR CODE HERE
    G = answer_thirteen()
    return nx.transitivity(G), nx.average_clustering(G)
In [ ]:
ans_fourteen = answer_fourteen()
assert type(ans_fourteen) == tuple, "Student answer must be a tuple"