An Intro to Graph Theory. Graph Theory is the study of graphs... | by Steven Dye | Towards Data Science

Graph Theory is the study of graphs which are mathematical structures used to model pairwise relations between objects. These graphs are made up of nodes (also called points and vertices) which usually represent an object or a person, and edges (also called lines or links) which represent the relationship between the nodes. Graphs have many uses in machine learning, so this blog post will take look at how to make graphs, some critical measurements about graphs and how they are calculated, and how to perform these calculations using Python’s NetworkX package.
Creating a graph with the NetworkX package isn’t too difficult, just make sure that it is imported along with matplotlib so that you can plot it.
import networkx as nximport matplotlib.pyplot as pltG = nx.Graph()G.add_node('A')nx.draw(G, with_labels=True)plt.show()
And just like that, you have a drawing of a graph! We can make these graphs more complex by adding more nodes and edges to them.
G = nx.Graph()G.add_edge('A','B')nx.draw(G, with_labels=True)plt.show()
Adding edges allows us to examine the relationships between the nodes. A complete overview of the NetworkX package can be found here. The edge created here is an undirected edge, meaning that the relationship between nodes A and B are equal. What if we want to make a directional edge? We can do that with the DiGraph function.
G = nx.DiGraph()G.add_edge('A','B')nx.draw(G, with_labels=True)plt.show()
The result looks like the graph above, except now the edge has an arrow head on it, which shows us the direction that the edge is pointing. A directed edge can represent many things in the real world such as social media influence or the spread of an infectious disease.
While pretty pictures are fun to look at, it can be difficult to gain insight from them, especially if they have a large number of nodes and edges. Luckily there are measurements we can take of graphs that can give us valuable information. Let’s draw a more complicated graph so we can get a better understanding of these measurements.
G = nx.Graph()G.add_edge('A','B')G.add_edge('A','C')G.add_edge('A','D')G.add_edge('B','C')G.add_edge('B','E')G.add_edge('B','F')G.add_edge('C','E')G.add_edge('C','D')G.add_edge('C','F')G.add_edge('E','F')G.add_edge('D','G')G.add_edge('F','G')G.add_edge('G','H')G.add_edge('G','I')G.add_edge('H','J')G.add_edge('H','K')G.add_edge('H','L')G.add_edge('J','M')G.add_edge('J','N')G.add_edge('J','O')G.add_edge('J','K')G.add_edge('J','L')G.add_edge('M','N')G.add_edge('M','L')G.add_edge('N','O')G.add_edge('O','P')G.add_edge('P','Q')nx.draw(G, with_labels=True)plt.show()
Centrality measurements give us information on how important nodes are in a graph. Depending on where the node is, how many connections it has, and what other nodes it is connected to, they can have a wide range of influences on a graph. Here will take a look a some the more popular centrality measurements and how they’re calculated.
Degree centrality measures the number of edges attached to a node. It is used to determine what nodes are most connected. In directed graphs, degree centrality is broken up into in-degree for incoming edges and out-degree for outgoing. To calculate the normalized degree centrality of a node, you just add up the number of edges that attached to the node and divide it by the total number of nodes minus one. Mathematically, if we want to find the degree centrality of node x, we can use the equation
where N is the number of nodes on the graph and a has a value or either 0 or 1, depending whether or not the nodes x and y share an edge. We can use the following code to find the degree centrality measurements for each node.
for node in G.nodes():    print(node, nx.degree_centrality(G)[node])
This gives us the result
Which shows us that node J has the largest degree centrality. This is easy to verify visually as J as the most edges connected to it with 6.
Closeness centrality measures the mean distance from one node to any other node. The more central a node is, the closer it is to all the other nodes. The closeness of a node is typically referred to in its normalized form, which is given by the equation.
Where N is the number of nodes on the graph, and d(y, x) is the distance between vertices x and y. In graph theory, distance is the number of edges in the shortest path. In large graphs, the -1 becomes minuscule, so it is typically dropped. Using the code
for node in G.nodes():    print(node, nx.closeness_centrality(G, node))
we see that node H as the highest closeness centrality, which means that it is closest to the most nodes than all the other nodes.
Betweenness Centrality: Measures the number of shortest paths that the node lies on. This centrality is usually used to determine the flow of information through the graph. The higher the number, the more information flows through it. The betweenness centrality can be calculated with the equation
where the denominator is the number of shortest paths between vertices u and v, while the numerator is the number of shortest paths between vertices u and v that pass through vertex x. Typically this measurement is rescaled and is normally done so by dividing the value by the number of pairs that do not include x, leaving us a final value between 0 and 1. The value is divided by (N-1)(N-2) for directed graphs and (N-1)(N-2)/2 for undirected graphs. With the code
for node in G.nodes():     print(node, nx.betweenness_centrality(G)[node])
we see that node G has the highest betweenness centrality in our graph, which means that it lies on the most shortest paths.
Eigenvector Centrality: Measures the node’s relative influence in the network, or how well a node is connected to other highly connected nodes. It is the mathematical measurement of the phrase “It’s not what you know but who you know.”
Calculating the eigenvector centrality is a little bit (or a lot) more complicated then the other centralities. Mathematically the eigenvector centrality is calculated with the equation
where λ is the largest eigenvalue calculated, M(x) is a set of neighbors to vertex x, y is a neighboring vertex, and G is the graph being evaluated. a takes a value or either 0 or 1 depending on whether or not x and y are neighbors. This expression is a solution to the eigenvector equation
In this case A is the adjacency matrix, which essentially counts the number of connections between nodes in matrix form, λ is the eigenvalue mentioned above, and x is the eigenvector that we are solving for.
When dealing with large matrices, in order to avoid finding eigenvalue solutions to large degree polynomials, the eigenvector is found through an iterative process. This explain this process more clearly, let’s look at a simple example. Let’s start with the simple graph below.
Here we have 4 nodes A, B, C, and D with edges between (A, B), (A, C), (A, D), and (B, C). The adjacency matrix looks like
where the elements are lined up as A, B, C, and D in both rows and columns direction. The numbers in the matrix represent how many edges connect each node. For example, the top left number is 0 because there are 0 edges that connect A to A. Since our graph is not directional, it makes since that our matrix would be symmetric.
From here we multiply this matrix by our initial vector, which is just a vector of 1’s. This give us the result
from here we take the normalized vector and plug it back into the equation by replacing the initial vector with it. Repeating this process gives us
This process continues until the eigenvector converges onto a stable solution. In this example’s case, the final solution is
Now that we understand how the process happens, we can calculate the eigenvalue centrality of our original graph. We can do this with the code
for node in G.nodes():     print(node, nx.eigenvector_centrality(G, max_iter=1000)[node])
which shows us that node C has the largest eigenvector centrality. It is interesting to note that maximum of all four centrality measurements are on separate nodes.
There is plenty more to learn about graphs and centrality measurements, and I hope this post gets you started on your graph adventure.