The NetworkX Package is a Python library for studying graphs and networks. It provides tools for the creation, manipulation, and study of dynamic and complex network structures. With NetworkX, we can load and store networks in many data formats, generate many types of random and classic networks, analyze network structure, build network models, design new network algorithms, draw networks, and much more. In this tutorial, we will learn how to use NetworkX to create graphs and study networks.

## Importing the NetworkX Package

In order to use the NetworkX package, we need to download it on our local machine. You can download it using the pip command.

```
pip install networkx
```

And then you can import the library as follows.

```
import networkx as nx
```

## Adding nodes to the graph

First, we will create an empty graph by calling `Graph()`

class as shown below.

```
G = nx.Graph()
```

A node in NetworkX can be any hashable object, i.e., an integer, a text string, an image, an XML object, etc. It can be a NetworkX graph also. There are 2 methods used to add nodes in graph.

This method is used to add 1 single node at a time.**add_node():**This method takes an iterable container such as list, set, etc and add multiple nodes at the same time.**add_nodes_from():**

```
import networkx as nx
G = nx.Graph()
G.add_node(1)
G.add_nodes_from([2,3,"node 5"])
print(G.nodes())
```

```
[1, 2, 3, 'node 5']
```

## Adding edges to the graph

An edge is a link between 2 nodes. These 2 methods are majorly used to add edges to the graph. Unknown nodes specified in the parameters are automatically added to the graph.

This method adds one edge at a time.*add_edge():*This method takes an iterable container of edges tuples like list, iterator, etc.**add_edges_from():**

Adding a node or an edge again to the graph will be silently ignored by NetworkX.

```
import networkx as nx
G = nx.Graph()
# Adding one edge at a time
# Node 1 and 2 will be automatically added
G.add_edge(1,2)
G.add_edge(3,2)
# Adding multiple edges at a time
G.add_edges_from([(4,2), (3,5), (5,4)])
# Adding duplicates will be ignored.
G.add_node(1)
G.add_edge(1,2)
print(G.nodes())
print(G.edges())
```

```
[1, 2, 3, 4, 5]
[(1, 2), (2, 3), (2, 4), (3, 5), (4, 5)]
```

## Removing Nodes and Edges from the graph

Similarly to adding nodes and edges, we can remove single nodes and edges at a time and multiple nodes and edges as well at a time.

This method removes one node and edges associated with that node from the graph. If the node doesn’t exist in the graph, it will raise*remove_node():*`NetworkXError`

.This method takes an iterable container and removes all nodes and edges associated with those nodes from the graph. If any node doesn’t exist in the graph, it will silently discard it without any changes.*remove_nodes_from():*This method removes one edge from the graph keeping the nodes as it is. If the edge doesn’t exist in the graph, it will raise*remove_edge():*`NetworkXError`

.This method takes an iterable container and removes edges from the graph. If any edge doesn’t exist in the graph, it will silently discard it without any changes.*remove_edges_from():*

```
import networkx as nx
G = nx.Graph()
# Creating graph
G.add_edges_from([(1, 2), (2, 3), (3, 4), (4, 1)])
G.add_edges_from([(5, 6), (5, 7), (5, 8), (7, 8)])
print(G.nodes())
print(G.edges())
# Removing edge 1-2 from graph
G.remove_edge(2, 1)
# Removing edge 3-4 and 1-4 at once
G.remove_edges_from([(3, 4), (1, 4)])
print()
print(G.nodes())
print(G.edges())
# Removing node 5 from graph
G.remove_node(5)
# Removing node 7 and 8
G.remove_nodes_from([7,8])
print()
print(G.nodes())
print(G.edges())
```

```
[1, 2, 3, 4, 5, 6, 7, 8]
[(1, 2), (1, 4), (2, 3), (3, 4), (5, 6), (5, 7), (5, 8), (7, 8)]
[1, 2, 3, 4, 5, 6, 7, 8]
[(2, 3), (5, 6), (5, 7), (5, 8), (7, 8)]
[1, 2, 3, 4, 6]
[(2, 3)]
```

## Accessing elements of graph

We can access 4 basic graph properties in NetworkX graph.

It returns the list of nodes in the graph.*G.nodes:*It returns the list of edges in the graph.*G.edges:*It returns the adjacency list for all the nodes. An adjacency list of node X contains neighboring nodes that are directly linked to node X. You can access all the neighboring nodes of a node using a subscript notation(using square brackets after*G.adj:*`G.adj`

).It returns the number of nodes linked to each node in the graph. You can access the degree of a node using a subscript notation(using square brackets after*G.degree:*`G.degree`

).

```
import networkx as nx
G = nx.Graph()
G.add_edges_from([(1,2), (1,3), (3,4), (3,5)])
print("Nodes")
print(G.nodes)
print("Edges")
print(G.edges)
print("Adjacency List")
print(G.adj)
print("Degree")
print(G.degree)
print()
print("Adjacency List for node 3")
print(G.adj[3])
print("Degree for node 3")
print(G.degree[3])
```

```
Nodes
[1, 2, 3, 4, 5]
Edges
[(1, 2), (1, 3), (3, 4), (3, 5)]
Adjacency List
{1: {2: {}, 3: {}}, 2: {1: {}}, 3: {1: {}, 4: {}, 5: {}}, 4: {3: {}}, 5: {3: {}}}
Degree
[(1, 2), (2, 1), (3, 3), (4, 1), (5, 1)]
Adjacency List for node 3
{1: {}, 4: {}, 5: {}}
Degree for node 3
3
```

## Attributes for Graph, Nodes, and Edges

Each graph, node, and edge can hold key/value attribute pairs in an associated attribute dictionary. By default these are empty, but attributes can be added or changed using `add_edge`

, `add_node`

or direct manipulation of the attribute dictionaries named `G.graph`

, `G.nodes`

, and `G.edges`

for a graph `G`

.

### 1. Graph Attributes

You can assign attributes to graph while creating it using `nx.Graph()`

.

```
import networkx as nx
G = nx.Graph(graph_description = "This is an empty graph")
print(G.graph)
# Output: {'graph_description': 'This is an empty graph'}
```

Or you can add/modify the attributes later just like a dictionary object

```
import networkx as nx
G = nx.Graph()
G.graph["description"] = "This is empty graph"
G.graph["data"] = 5
print(G.graph)
# Output: {'description': 'This is empty graph', 'data': 5}
```

### 2. Node Attributes

You can add attributes for nodes using `add_node()`

, `add_nodes_from()`

or `G.nodes`

. You can get attributes for all nodes using `G.nodes.data()`

. For a particular node use square brackets as shown.

```
import networkx as nx
G = nx.Graph()
# Using add_node
G.add_node(1, data = "data1")
# Using add_nodes_from
G.add_nodes_from([(2, {"data": "data2"}),
(3, {"data": "data3"})],
node_type = "child node")
# Adding more attributes on node 1 using G.nodes
G.nodes[1]["type"] = "root node"
print(G.nodes.data())
# Output: [(1, {'data': 'data1', 'type': 'root node'}), (2, {'node_type': 'child node', 'data': 'data2'}), (3, {'node_type': 'child node', 'data': 'data3'})]
print(G.nodes[1])
# Output: {'data': 'data1', 'type': 'root node'}
```

### 3. Edge Attributes – Making a weighted graph

You can add attributes for edges using `add_edge()`

, `add_edges_from()`

, `G.edges`

or subscript notation. By assigning attributes to edges, we can create a weighted graph as shown.

```
import networkx as nx
G = nx.Graph()
# Using add_edge
G.add_edge(1, 2, weight = 50)
# Using add_edges_from
G.add_edges_from([
(1, 3, {"weight": 70}),
(1, 4, {"weight": 100})
])
# Using subscript notation
G.add_edge(4,5)
G[4][5]["weight"] = 175
# Using G.edges
G.edges[1, 2]["weight"] = 10
print(G.edges.data())
# Output: [(1, 2, {'weight': 10}), (1, 3, {'weight': 70}), (1, 4, {'weight': 100}), (4, 5, {'weight': 175})]
```

## Visualizing NetworkX Package Graphs

We can draw graphs and visualize them in the NetworkX package using the `draw()`

method as shown.

```
import networkx as nx
G = nx.Graph()
# Using add_edge
G.add_edge(1, 2, weight = 12.5)
G.add_edge(3, 2, weight = 50.0)
G.add_edge(1, 3, weight = 17)
G.add_edge(4, 2, weight = 100)
G.add_edge(2, 5, weight = 1)
G.add_edge(4, 6, weight = 25.5)
G.add_edge(7, 4, weight = 175)
G.add_edge(5, 8, weight = 90)
nx.draw(G, with_labels= True, font_weight='bold')
```

If you want to draw graphs with weights use `draw_networkx_edge_labels()`

along with `nx.draw()`

specifying the graph, pos and edge_label attributes

```
import networkx as nx
import matplotlib.pyplot as plt
G = nx.Graph()
# Using add_edge
G.add_edge(1, 2, weight = 12.5)
G.add_edge(3, 2, weight = 50.0)
G.add_edge(1, 3, weight = 17)
G.add_edge(4, 2, weight = 100)
G.add_edge(2, 5, weight = 1)
G.add_edge(4, 6, weight = 25.5)
G.add_edge(7, 4, weight = 175)
G.add_edge(5, 8, weight = 90)
pos=nx.circular_layout(G)
nx.draw(G, pos, with_labels=True, font_weight='bold')
edge_weight = nx.get_edge_attributes(G,'weight')
nx.draw_networkx_edge_labels(G, pos, edge_labels = edge_weight)
plt.show()
```

## Creating Directed Graphs using the NetworkX Package

NetworkX also allows you to create directed graphs using `DiGraph()`

class which provides additional methods and properties specific to directed edges, e.g., `DiGraph.out_edges`

, `DiGraph.in_degree`

, `DiGraph.predecessors()`

, `DiGraph.successors()`

etc.

```
import networkx as nx
DG = nx.DiGraph()
DG.add_edges_from([(1,2), (2,3), (3,4), (4,5), (5,2), (4, 6)])
# Print edges going out from node 4
print("Out edges of node 4 are:",DG.out_edges(4))
# Print in degree of node 2
print("In Degree of node 2 is:",DG.in_degree(2))
# Print successors of node 4
print("Successors of node 4 are:",list(DG.successors(4)))
# Print predecessors of node 2
print("Predecessors of node 2 are:",list(DG.predecessors(2)))
nx.draw(DG, with_labels= True, font_weight='bold')
```

```
Out edges of node 4 are: [(4, 5), (4, 6)]
In Degree of node 2 is: 2
Successors of node 4 are: [5, 6]
Predecessors of node 2 are: [1, 5]
```

## Conclusion

In this tutorial, you learned about NetworkX package and how to use it to create, manipulate and visualize graphs. This library becomes helpful in studying complex networks and graphs. It is used by mathematicians, physicists, biologists, computer scientists, etc for study.

Thanks for reading!!