{ "cells": [ { "cell_type": "markdown", "metadata": { "hideCode": false, "hidePrompt": false, "toc": true }, "source": [ "

Table of Contents

\n", "
" ] }, { "cell_type": "markdown", "metadata": { "hideCode": false, "hidePrompt": false, "slideshow": { "slide_type": "slide" } }, "source": [ " # CS4423 : Week 02 - Lecture 1 - Networks \n", "# More on Graphs, and `networkx`\n", "\n", "[//]: $\\color{red}{\\text{DRAFT}}$\n", "Niall Madden, \n", "School of Mathematical and Statistical Sciences \n", "University of Galway\n", "\n", "(These notes are adapted from Angela Carnevale's work)\n", "\n", "This notebook is at https://www.niallmadden.ie/2425-CS4423/W02/CS4423-W02-1.ipynb\n", "You can read the HTML version at https://www.niallmadden.ie/2425-CS4423/W02/CS4423-W02-1.html\n", "\n", "
This version of this notebook was written by Niall Madden, adapted from notebooks by Angela Carnevale.
" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## News: \n", "### Labs\n", "\n", "Labs start next week, and an (reintroduction) to Python. This will run:\n", "* Tuesday at 4 in AC215 (slight chance this might get moved to Tuesday at 3), and\n", "* Wednesday at 10am in CA116a. \n", "\n", "These rooms are not labs: BYoD! (Bring Your Own Device)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Website\n", "I now plan to post all notes to https://www.niallmadden.ie/2425-CS4423/ as well as to Canvas." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## `networkx`\n", "Last week we learned a little about the `networkx` package. We'll return to that now, while also revisiting some key ideas about graphs." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As ever, we'll start with importing the `networkx` module, as well as `numpy`: more about that later. And we'll define the `opts` option dictionary." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "hideCode": false, "hidePrompt": false, "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "import networkx as nx\n", "import numpy as np\n", "opts = { \"with_labels\": True, \"node_color\": 'y' } # show labels; yellow noodes" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "To create a graph with nodes $1$, $2$, $3$, $4$, $5$, and edges between all even and odd labelled nodes:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "hideCode": false, "hidePrompt": false, "slideshow": { "slide_type": "-" } }, "outputs": [], "source": [ "K32 = nx.Graph() # makes THE empty graph\n", "K32.add_edges_from([(1, 2), (1,4), (2,3), (2,5), (3,4), (4,5)])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll later learn this is the graph $K_{3,2}$. Now draw it:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "hideCode": false, "hidePrompt": false, "slideshow": { "slide_type": "-" } }, "outputs": [], "source": [ "nx.draw(K32, **opts)" ] }, { "cell_type": "markdown", "metadata": { "hideCode": false, "hidePrompt": false, "slideshow": { "slide_type": "subslide" } }, "source": [ "We can also be lazy, and just give $2$-letter strings for the edges: this implicitly defines the nodes too. " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "hideCode": false, "hidePrompt": false, "slideshow": { "slide_type": "-" } }, "outputs": [], "source": [ "K33 = nx.Graph([\"A1\", \"A2\", \"A3\", \"B1\", \"B2\", \"B3\", \"C1\", \"C2\", \"C3\"])\n", "nx.draw(K33, **opts)" ] }, { "cell_type": "markdown", "metadata": { "hideCode": false, "hidePrompt": false, "slideshow": { "slide_type": "subslide" } }, "source": [ "### Check basic properties:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(f\"K33 has {K33.number_of_nodes()} nodes and {K33.number_of_edges()} edges\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "hideCode": false, "hidePrompt": false }, "outputs": [], "source": [ "print(f\"This is the same as saying K33 order {K33.order()} and size {K33.size()}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To list the nodes and edges (as lists)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "hideCode": false, "hidePrompt": false }, "outputs": [], "source": [ "list(K33.nodes)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "hideCode": false, "hidePrompt": false }, "outputs": [], "source": [ "list(K33.edges)" ] }, { "cell_type": "markdown", "metadata": { "hideCode": false, "hidePrompt": false, "slideshow": { "slide_type": "subslide" } }, "source": [ "A **loop** over a graph `G` will effectively loop over `G`'s nodes. As an example, (recall?) that the **degree** of a node is the number of edges incident to it (or, if you prefer, the number of neighbours)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "hideCode": false, "hidePrompt": false }, "outputs": [], "source": [ "for node in K33:\n", " print(f\"node {node} has neighbours {list(K33.neighbors(node))}\")" ] }, { "cell_type": "markdown", "metadata": { "hideCode": false, "hidePrompt": false, "slideshow": { "slide_type": "subslide" } }, "source": [ "### Adding and removing nodes and edges\n", "\n", "We say that \n", "* `G.add_node('v')` adds a node to $G$ called 'v'\n", "* `G.add_nodes_from([2, 3, 5])` adds all the nodes from a list\n", "* `G.add_nodes_from(H])` adds all the nodes from Graph $H$ to Graph $G$\n", "* `G.add_edge('x','y')` add edge from Node $x$ to Node $y$, adding one or both nodes, if needed.\n", "* `G.add_edges_from([(1,5), (2,5), (3,5)])` add edges from a list\n", "* `G.add_edges_from(H.edges)` add edges from another graph\n", "* `G.remove_edge('x','y')` remove edge from $x$ to $y$, but keep the nodes\n", "* `G.remove_node('x')` remove node $x$ and any edge it was incident to." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Neighbours and degree\n", "\n", "(As we've seen) \n", "* The **neighbours** of a node are those that it shares an edge with;\n", "* If nodes $a$ and $b$ are neighbours, we say they are **adjacent**.\n", "* the **degree of a node** is the number of edges incident to it (or, if you prefer, the number of neighbours).\n", "\n", "Let's look at an example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Edges1 = [('Aoife', 'Brian'), ('Aoife', 'Ciara'), ('Aoife', 'Daire'), ('Aoife', 'Ella'), \n", " ('Aoife', 'Finn'), ('Brian', 'Ciara'), ('Brian', 'Finn'), ('Ciara', 'Daire'), \n", " ('Daire', 'Ella'), ('Ella', 'Finn') ]\n", "G1 = nx.Graph(Edges1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "nx.draw(G1, **opts)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "This is example, which is known as a *wheel graph* is chosen because it exhibits a famous concept in Network Science: *The Friendship Paradox*: your friends (probably) have more friends, on average, than you do!\n", "\n", "Explanation:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "notes" } }, "outputs": [], "source": [ "#pos = nx.nx_agraph.graphviz_layout(G1)\n", "#nx.draw(G1, pos=pos,**opts)" ] }, { "cell_type": "markdown", "metadata": { "hideCode": false, "hidePrompt": false, "slideshow": { "slide_type": "slide" } }, "source": [ "## Important Graphs\n", "\n", "In this section we'll discuss some important examples of graphs, which we'll return to later as key examples of networks. These include\n", "* Complete Graphs\n", "* Bipartite and complete bipartite graphs\n", "* Path graphs" ] }, { "cell_type": "markdown", "metadata": { "hideCode": false, "hidePrompt": false, "slideshow": { "slide_type": "subslide" } }, "source": [ "### Complete Graphs\n", "\n", "The [**complete graph**](https://en.wikipedia.org/wiki/Complete_graph)\n", "on a vertex set $X$ is the graph with edge set all of $\\binom{X}{2}$. That is: every node is a neighbour of every other node. It is denoted $K_n$ where $n=|X|$. E.g., if $X=\\{0,1,2,3\\}$, then $K_4$ (\"the complete graph on 4 nodes\") has edges $E=\\{01, 02, 03, 12, 13, 23\\}$." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "hideCode": false, "hidePrompt": false, "slideshow": { "slide_type": "-" } }, "outputs": [], "source": [ "nodes = range(4)\n", "list(nodes) " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "hideCode": false, "hidePrompt": false, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "E4 = [(x, y) for x in nodes for y in nodes if x < y]\n", "print(E4)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "hideCode": false, "hidePrompt": false }, "outputs": [], "source": [ "K4 = nx.Graph(E4)\n", "nx.draw(K4, **opts)" ] }, { "cell_type": "markdown", "metadata": { "hideCode": false, "hidePrompt": false, "slideshow": { "slide_type": "subslide" } }, "source": [ "While it is somewhat straightforward to find all $2$-element\n", "subsets of a given set $X$ with a short `python` program,\n", "it is probably more convenient (and possibly efficient) to use a function from the\n", "`itertools` package for this purpose." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "hideCode": false, "hidePrompt": false, "slideshow": { "slide_type": "-" } }, "outputs": [], "source": [ "from itertools import combinations\n", "nodes5 = range(5)\n", "combinations(nodes5, 2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "hideCode": false, "hidePrompt": false }, "outputs": [], "source": [ "print(list(combinations(nodes5, 2)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "hideCode": false, "hidePrompt": false, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "K5 = nx.Graph(combinations(nodes5, 2))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "hideCode": false, "hidePrompt": false, "slideshow": { "slide_type": "-" } }, "outputs": [], "source": [ "nx.draw(K5, **opts)" ] }, { "cell_type": "markdown", "metadata": { "hideCode": false, "hidePrompt": false, "slideshow": { "slide_type": "subslide" } }, "source": [ "`networkx` has a built-in function to create complete graphs: `complete_graph` [[doc]](https://networkx.org/documentation/stable//reference/generated/networkx.generators.classic.complete_graph.html)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "hideCode": false, "hidePrompt": false }, "outputs": [], "source": [ "nx.draw(nx.complete_graph(\"NETWORKS\"), **opts)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "nx.draw(nx.complete_graph(22), **opts)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Bipartite Graphs\n", "\n", "A graph is **bipartite** if we can divide the node set, $X$, into two subsets $X_1$ and $X_2$ such that \n", "* $X_1 \\cap X_2 = \\emptyset$ (the sets have no edge in common)\n", "* $X_1 \\cup X_2 = X$ \n", "* For any edge $(u_1,u_2)$ we have $u_1 \\in X_1$ and $u_2 \\in X_2$. That is we only ever have edges between nodes from different sets. \n", "\n", "Such graphs are very common in Network Science, where nodes in the network represent two different types of entities. For example, we might have a graph where nodes represent students and modules, with edges between students and modules they are enrolled in, often called an **affiliation network**." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "Edges2 = [('Aoife', 'CS4423'), ('Aoife', 'CS319'), ('Aoife', 'MA432'), \n", " ('Brian', 'CS4423'), ('Brian', 'CS319'), \n", " ('Ciara', 'CS319'), ('Ciara', 'MA432'), \n", " ('Daire', 'MA432') ]\n", "G2 = nx.Graph(Edges2)\n", "nx.draw(G2, **opts)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Somehow that previous graph did not catch the essence of the network: there are two different types of node. We could make that clearer, by colouring the nodes. Here we'll do it manually (later, automatically)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "-" } }, "outputs": [], "source": [ "print(G2.nodes)\n", "color_list= ['c','y','y','y','c', 'c','c'] # y=yellow; c=cyan\n", "nx.draw(G2, node_color=color_list, with_labels=True)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Complete Bipartite Graphs\n", "\n", "A **complete bipartite graph** is a particular bipartite graph where there is an edge between every node in $X_1$ and every node in $X_2$. Such graphs are denoted $K_{m,n}$ where $|X_1|=m$ and $|X_2|=n$. (We met $K_{2,2}$ and $K_{3,3}$ earlier). \n", "\n", "As usual, there is a built-in generator: `complete_bipartite_graph` [[doc]](https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.bipartite.generators.complete_bipartite_graph.html)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "K33 = nx.complete_bipartite_graph(3,3)\n", "nx.draw(K33,**opts) " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Path Graphs\n", "\n", "The **Path Graph** with $n$ nodes, denoted $P_n$, is one where two nodes have degree 1, and the other $n-2$ have degree 2:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "P4 = nx.Graph([\"ab\", \"bc\", \"cd\", \"de\"])\n", "nx.draw(P4)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "The built-in `nerworkx`generator is called `path_graph` [[doc]](https://networkx.org/documentation/stable/reference/generated/networkx.generators.classic.path_graph.html)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "P10 = nx.path_graph(10)\n", "nx.draw(P10)" ] }, { "cell_type": "markdown", "metadata": { "hideCode": false, "hidePrompt": false, "slideshow": { "slide_type": "slide" } }, "source": [ "## Exercises\n", "\n", "1. For what values of $n$ is $K_n$ bipartite?\n", "2. For what values of $m$ and $n$ is $K_{m,n}$ bipartite?\n", "3. For what values of $n$ is $P_n$ bipartite?\n", "5. (Based on Q2(a) from the 2023/2024 CS4423 Exam paper) Let $G$ be the graph on the set of nodes \n", "$\\{1, 2, 3, 4, 5, 6\\}$ with edges $1-2$, $1-3$, $2-$4, $3-4$, $3-6$, $4-5$, $4-6$. Draw the graph $G$.\n", "Is $G$ bipartite? Justify your answer. (Note saying $a-b$ is an edge in $G$ is the same as saying $(a,b)$ is an element of its edge set).\n", "\n", "6. (Based on Q1(b) of the 2019/2019 CS4423 paper) At a party with $n=5$ people, some people know each other already while others don’t. Each of the 5 guests is asked how many friends they have at this party. Two report that they have one friend each. Two other guests have two friends each, and the fifth guest has three friends at the party. Understanding friendship as a symmetric relation, is this network possible? Why, or why not?\n", "(*Hint: recall that the sum of all node degrees is twice the number of edges in the graph*).\n", "\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
Finished here Wednesday
" ] } ], "metadata": { "celltoolbar": "Slideshow", "hide_code_all_hidden": false, "kernelspec": { "display_name": "Python (CS4423)", "language": "python", "name": "cs4423" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.8" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": true, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": true, "toc_position": {}, "toc_section_display": true, "toc_window_display": false }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 1 }