"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "SPiw3Y6utILf"
},
"source": [
"## Let's think about how we store network data\n",
"\n",
"By now you should have a working Juptyer Lab/Notebook and be using Python 3, either through Google Colaboratory or your local Python environment. If not, go back to the previous assignment to make sure that you have a working environment!\n",
"\n",
"When working with network data with code, it's important to think about the data structure—how to represent the data in our computer. To do so, a useful exercise is to think about _what we will need to do with the data_. For instance, obviously, once we have our network data loaded, we should be able to answer questions like \"how many nodes are there?\" and \"how many edges are there?\". We would also want to be able to ask \"who are the neighbors of node 1?\" and \"what is the degree of node 1?\".\n",
"\n",
"### Node and edge lists or sets\n",
"\n",
"The most immediate (but not a good) way to store network data is to use a list or a set. For instance, we can store the nodes and edges in a list:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"node_list = [1, 2, 3, 4, 5, 6]\n",
"edge_list = [(1, 2), (1, 3), (2, 3), (2, 4), (3, 4), (4, 5), (4, 6)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Or we can use sets."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"node_set = set(node_list)\n",
"edge_set = set(edge_list)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can easily check whether a node/edge is in the network or not. However, it is quite cumbersome to do other operations like finding all neighbors of a node or finding the degree of a node."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"def neighbors_from_list_or_set(nodes, edges):\n",
" neighbors = {node: set() for node in nodes}\n",
" for edge in edges:\n",
" neighbors[edge[0]].add(edge[1])\n",
" neighbors[edge[1]].add(edge[0])\n",
" return neighbors"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The code is not so bad, but still we have to traverse all edges to figure out the neighbors of a single node. This is horribly inefficient, especially when we have a large network. \n",
"\n",
"### Adjacency matrix\n",
"\n",
"Another way to store network data is to use an adjacency matrix. An adjacency matrix is a matrix where each row and column represents a node, and the value of the matrix at row $i$ and column $j$ is 1 if there is an edge from node $i$ to node $j$, and 0 otherwise. (we can also store the continuous edge weight instead of 1 or 0.)\n",
"\n",
"$$ A_{ij} = \\begin{cases} 1 & \\text{if there is an edge from node $i$ to node $j$} \\\\ 0 & \\text{otherwise} \\end{cases} $$\n",
"\n",
"For instance, we can use `numpy` to create a small adjacency matrix. "
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0 1 1]\n",
" [1 0 1]\n",
" [1 1 0]]\n"
]
}
],
"source": [
"import numpy as np\n",
"\n",
"adj_matrix = np.array([[0, 1, 1],\n",
" [1, 0, 1],\n",
" [1, 1, 0]])\n",
"\n",
"print(adj_matrix)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"What could be the problem with this approach? \n",
"\n",
"One problem is that the adjacency matrix is not very space efficient. If we have $n$ nodes, we need to store $n^2$ values (a lot of them will be zero). I must say that there are some clever ways to store the adjacency matrix more efficiently (e.g., using sparse matrix) and it may well be the best way to store and compute network data for some cases. However, in general, it is not the easiest or most efficient way to store network data. \n",
"\n",
"Another operation that is not so easy to do with adjacency matrix is to find the neighbors of a node or compute the degree of a node because we need to traverse all columns or rows of the matrix. Can you write a function that calculates the degree of a node using the adjacency matrix?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q: Write a function that calculates the degree of a node using the adjacency matrix.**"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"def calculate_degree(adj_matrix, node):\n",
" # YOUR SOLUTION HERE"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Adjacency list or set\n",
"\n",
"A pretty good solution is to use an adjacency list or set. The idea is to associate each node with a list or set of nodes that are connected to it. In Python, we can use a dictionary to store (node, neighbrs) as (key, value) pair. For instance,"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"node2neighbors = {1: {2, 3}, 2: {1, 3, 4}, 3: {1, 2, 4}, 4: {2, 3, 5, 6}, 5: {4}, 6: {4}}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now the operations that we discussed above can be done quite easily and efficiently. Can you fill in the code below? \n",
"\n",
"**Q: fill in the blanks below to perform basic network operations using adjacency set data structure.**"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"def degree(node, node2neighbors):\n",
" # degree (number of neighbors) of a node in a network\n",
" # YOUR SOLUTION HERE\n",
"\n",
"def node_set(node2neighbors):\n",
" # return the set of all nodes in the network\n",
" # YOUR SOLUTION HERE\n",
"\n",
"def is_connected(node2neighbors, i, j):\n",
" # return True if i and j are connected, False otherwise\n",
" # YOUR SOLUTION HERE"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Friendship paradox with `networkx`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`networkx` is one of the most famous Python libraries for network analysis. It provides all the basic functions for network analysis. It's written in Python and thus slower than some other libraries, but it's nice to use, inspect, and learn. \n",
"\n",
"Let's see how we can use `networkx` to create a network and perform basic network operations by thinking about the friendship paradox. We are going to work through some problems and examples in this notebook. The goal is empirically testing the friendship paradox with multiple types of networks. \n",
"\n",
"A few tasks will be left to you to complete and submit in your own notebook. Before we dive into creating our own paradox we will go over some basic commands used in the `networkx` library. "
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "7nApvbTEuUva"
},
"source": [
"First of all, you can ensure that all results are exactly reproducible by fixing the seed for the random number generator. This is a common technique for checking your computation that involves non-deterministic methods. "
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"id": "8c4yDVEytILg"
},
"outputs": [],
"source": [
"# Please use this random seed for submission.\n",
"import random\n",
"random.seed(42) \n",
" \n",
"import numpy as np # noqa: E402\n",
"np.random.seed(42)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "kOsewzl4uPao"
},
"source": [
"## Step 1: Using networkx\n",
"\n",
"We are going to start by importing the networkx module:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"id": "8wk0jYLZtILf"
},
"outputs": [],
"source": [
"import networkx as nx"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "H714NlibtILg"
},
"source": [
"`import xxxxx as xx` is a common way to use widely-used Python libraries. By importing this way, we can use any of the library's classes and functions by prepending the name of the class or function with: \n",
"\n",
"```\n",
"nx.\n",
"```\n",
"\n",
"Networkx has extensive documention with many examples. Whenever unsure about the module, go to [the official documentation page](https://networkx.org/documentation/stable/index.html#) and search for the keywords that you are unsure about. In particular, [the official tutorial](https://networkx.org/documentation/stable/tutorial.html) is the best place to learn about the basic usage of `networkx` library. \n",
"\n",
"Let's start by making a simple undirected graph by hand:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"id": "2m6t3WActILg"
},
"outputs": [],
"source": [
"# Creates an instance of a networkx graph.\n",
"my_first_graph = nx.Graph() \n",
"\n",
"# Lets add some nodes to the graph\n",
"my_first_graph.add_node(1)\n",
"my_first_graph.add_node(2)\n",
"my_first_graph.add_node(3)\n",
"\n",
"# Now lets add some connections\n",
"my_first_graph.add_edge(1, 2)\n",
"my_first_graph.add_edge(3, 2)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Ct1SA_xWtILg"
},
"source": [
"We now have our first graph, which contains 3 nodes and 2 edges. We can look at it too by using `draw()` function. By the way, depending on the environment, you may have to import `matplotlib` first (see the `networkx` tutorial). \n",
"\n",
"`Networkx` is underpowered for network visualization and it is rarely used for any serious network visualization. However, it has basic visualization capacity that is perfectly adequate for examining small networks. "
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 319
},
"id": "Y01UL2Uev_7G",
"outputId": "983b4925-b218-4502-97ef-6a893e7dee2b"
},
"outputs": [
{
"data": {
"image/png": "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",
"text/plain": [
"