<jupyter_start><jupyter_text>Welcome to NetworkX<jupyter_code>import networkx as nx
import matplotlib.pyplot as plt

%matplotlib notebook<jupyter_output><empty_output><jupyter_text>Simple example<jupyter_code>G = nx.Graph()

G.add_edge('A', 'B', weight=4)
G.add_edge('B', 'D', weight=2)
G.add_edge('A', 'C', weight=3)
G.add_edge('C', 'D', weight=4)

nx.shortest_path(G, 'A', 'D', weight='weight')<jupyter_output><empty_output><jupyter_text>Basic Graph Data Structures  - `Graph`: Undirected Graphs - `DiGraph`: Directed Graphs - `MultiGraph`: Undirected multigraphs, ie graphs which allow for multiple edges between nodes - `MultiDiGraph`: Directed Multigraphs Each of these has the same basic structure, attributes and features, with a few minor differences.  Creating Graphs<jupyter_code>G = nx.Graph()
D = nx.DiGraph()
M = nx.MultiGraph()
MD = nx.MultiDiGraph()<jupyter_output><empty_output><jupyter_text>You can also add attributes to a graph during creation, either by providing a dictionary, or simply using keyword arguments<jupyter_code>G = nx.Graph(date='2008-01-10', name="test")
G
type(G)
G.graph  # graph attribute<jupyter_output><empty_output><jupyter_text>The graph attribute is just a dictionary, so you can add and delete more information from it.<jupyter_code>G.graph['size'] = (5, 10)
del G.graph['date']
G.graph<jupyter_output><empty_output><jupyter_text>Nodes  Adding Nodes There are two main functions for adding nodes. `add_node`, and `add_nodes_from`. The former takes single values, and the latter takes any iterable (list, set, iterator, generator). Nodes can be of any _immutable_ type. This means numbers (ints and floats complex), strings, bytes, tuples or frozen sets. They cannot be _mutable_, such as lists, dictionaries or sets. Nodes in the same graph do not have to be of the same type<jupyter_code># Adding single nodes of various types
G.add_node(0)
G.add_node('A')
G.add_node(('x', 3.14))

# Adding collections of nodes
G.add_nodes_from([2,4,6])
G.add_nodes_from(set([i for i in range(10,13)]))<jupyter_output><empty_output><jupyter_text>Listing Nodes<jupyter_code>G.nodes()  # networkx views are read-only, they are automatically updated and maintained
G.number_of_nodes()<jupyter_output><empty_output><jupyter_text>Sometimes to save memory we might only want to access a list of nodes one at a time, so we can use an _iterator_.<jupyter_code>for v in G.nodes():
    print(v)<jupyter_output>0
2
4
6
('x', 3.14)
10
11
12
A<jupyter_text>Checking whether nodes are in a Graph<jupyter_code>13 in G
10 in G
G.has_node('B')
G.has_node(('x', 3.14))<jupyter_output><empty_output><jupyter_text>Node attributes You can also add attributes to nodes when you create new nodes using keyword arguments to the `add_node` and `add_nodes_from` function.<jupyter_code>G.add_node('Spam', company='Hormel', food='meat')
G.nodes()
G.node['Spam']  # check node attributes<jupyter_output><empty_output><jupyter_text>When using `add_nodes_from` you provide a tuple with the first element being the node, and the second being a dictionary of attributes for that node. You can also add attributes which will be applied to all added nodes using keyword arguments<jupyter_code>G.add_nodes_from([('Bologna',{'company':'<NAME>'}),
                  ('Bacon',{'company':'Wright'}),
                  ('Sausage',{'company':'<NAME>'})],
                  food='meat'  # this attr will be applied to all added nodes
                )
G.nodes()<jupyter_output><empty_output><jupyter_text>To list node attributes you need to provide the `data=True` keyword to the `nodes` functions<jupyter_code>G.nodes(data=True)  # list every node with its node attr<jupyter_output><empty_output><jupyter_text>Attributes are stored in a special dictionary within the graph called `node` you can access, edit and remove attributes there<jupyter_code>G.node['Spam']
G.node['Spam']['Delicious'] = True
G.node[6]['integer'] = True
G.nodes(data=True)
del G.node[6]['integer']  # remove node attr
G.nodes(data=True)<jupyter_output><empty_output><jupyter_text>Similiarly, you can remove nodes with the `remove_node` and `remove_nodes_from` functions<jupyter_code>G.remove_node(6)
G.remove_nodes_from([10,11,12])
G.nodes()
G.clear()  # clear the graph
G.nodes()<jupyter_output><empty_output><jupyter_text>Exercises  Repeated Nodes 1. What happens when you add nodes to a graph that already exist? - nothing happens2. What happens when you add nodes to the graph that already exist but have new attributes? - update?3. What happens when you add nodes to a graph with attributes different from existing nodes? - overwrite?4. Try removing a node that doesn't exist, what happens? - Traceback  The FizzBuzz Graph Using the spaces provided below to make a new graph, `FizzBuzz`. Add nodes labeled 0 to 100 to the graph. Each node should have an attribute 'fizz' and 'buzz'. If the nodes label is divisble by 3 `fizz=True` if it is divisble by 5 `buzz=True`, otherwise both are false.  Edges Adding edges is similar to adding nodes. They can be added, using either `add_edge` or `add_edges_from`. They can also have attributes in the same way nodes can. If you add an edge that includes a node that doesn't exist it will create it for you<jupyter_code>G.clear()
G.add_edge('Shanghai','Houston', distance=14000)
G.add_edge('Tokyo','Shanghai', distance=2000)
G.add_edge('Halifax','San Diego', distance=9000)
nx.draw_networkx(G)
G.number_of_edges()
G.nodes()
G.edges()
G.add_edges_from([(i, i + 2) for i in range(2,8,2)])
G.nodes()
G.edges()
nx.draw_networkx(G)
G.edges(data=True)<jupyter_output><empty_output><jupyter_text>Removing edges is accomplished by using the `remove_edge` or `remove_edges_from` function. Remove edge attributes can be done by indexing into the graph<jupyter_code>G['Shanghai']['Houston']  # check edge attributes
del G['Shanghai']['Houston']['distance']
G['Shanghai']['Houston']
G.remove_edge(2,4)  # removing edges will not remove nodes
G.edges(data=True)
G.nodes()<jupyter_output><empty_output><jupyter_text>You can check for the existance of edges with `has_edge`<jupyter_code>G.has_edge(2,4)
G.has_edge('Shanghai','Tokyo')<jupyter_output><empty_output><jupyter_text>For directed graphs, ordering matters. `add_edge(u,v)` will add an edge from `u` to `v`<jupyter_code>D.add_nodes_from(range(10))
D
D.graph
D.nodes()
D.edges()
D.add_edges_from([(i, (i + 1) % 10) for i in range(0,10)])  # make a circle
nx.draw_networkx(D)
D.clear()
D.add_cycle(range(0,10))  # shortcut for making a circle
nx.draw_networkx(D)
D.edges()
D.has_edge(0,1)
D.has_edge(1,0)<jupyter_output><empty_output><jupyter_text>You can also access edges for only a subset of nodes by passing edges a collection of nodes<jupyter_code>D.edges([3,4,5])  # find edges in a directed graph with heads in [3,4,5]<jupyter_output><empty_output><jupyter_text>Multigraphs Multigraphs can have multiple edges between any two nodes. They are referenced by a key.<jupyter_code>M.add_edge(0,1)
M.add_edge(0,1)
M.edges()<jupyter_output><empty_output><jupyter_text>The keys of the edges can be accessed by using the keyword `keys=True`. This will give a tuple of `(u,v,k)`, with the edge being `u` and `v` and the key being `k`.<jupyter_code>M.edges(keys=True)<jupyter_output><empty_output><jupyter_text>`MultiGraphs` and `MultiDiGraphs` are similar to `Graphs` and `DiGraphs` in most respects  Adding Graph Motifs In addition to adding nodes and edges one at a time networkx has some convenient functions for adding complete subgraphs. But beware, these may be removed, or the API changed in the future.<jupyter_code>G.edges()
G.add_cycle(range(100,110))
G.edges()<jupyter_output><empty_output><jupyter_text>Basic Graph Properties  Node and Edge Counts The _order_ of a graph is the number of nodes, it can be accessed by calling `G.order()` or using the builtin length function: `len(G)`.<jupyter_code>print(G.nodes())
G.order()  # n
len(G)<jupyter_output><empty_output><jupyter_text>The number of edges is usually referred to as the _size_ of the graph, and can be accessed by `G.size()`. You could also find out by calling `len(G.edges())`, but this is much slower.<jupyter_code>print(G.edges())
G.size()<jupyter_output><empty_output><jupyter_text>For multigraphs it counts the number of edges including multiplicity<jupyter_code>M.edges(keys=True)
M.size()<jupyter_output><empty_output><jupyter_text>Node Neighbors Node neighbors can be accessed via the `neighbors`<jupyter_code>G.neighbors('Shanghai')
list(G.neighbors('Shanghai'))<jupyter_output><empty_output><jupyter_text>In the case of directed graphs, neighbors are only those originating at the node.<jupyter_code>D.add_edges_from([(0,i) for i in range(5,10)])
D.neighbors(0)
list(D.neighbors(0))<jupyter_output><empty_output><jupyter_text>For multigraphs, neighbors are only reported once.<jupyter_code>list(M.neighbors(0))<jupyter_output><empty_output><jupyter_text>Degree The degree of a graph can be found using the `degree` function for undirected graphs, and `in_degree` and `out_degree` for directed graphs. They both return a dictionary with the node as the keys of the dictionary and the degree as the value<jupyter_code>G.degree()
D.in_degree()
D.out_degree()<jupyter_output><empty_output><jupyter_text>Both of these can be called on a single node or a subset of nodes if not all degrees are needed<jupyter_code>D.in_degree(5)
D.out_degree([0,1,2])<jupyter_output><empty_output><jupyter_text>You can also calculate weighted degree. To do this each edge has to have specific attribute to be used as a weight.<jupyter_code>WG = nx.Graph(name='Weighted Graph')
WG.graph
WG.add_star(range(5))
WG.add_star(range(5,10))
nx.draw_networkx(WG)
WG.add_edges_from([(i, 2 * i % 10) for i in range(10)])
nx.draw_networkx(WG)
WG.nodes()
WG.edges()
for (u,v) in WG.edges():
    WG[u][v]['product'] = (u + 1) * (v + 1)  # add edge attribute
WG.degree(weight='product')  # for weighted graphs, the degree of a node is the sum of its edge weights.<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11533066132264529, 'fraction_numerical': 0.013927855711422845, 'mean_word_length': 6.253633720930233, 'pattern_counts': {'":': 0, '<': 126, '<?xml version=': 0, '>': 126, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '31', 'dir': 'jupyter-structured-clean-dedup', 'id': '361162', 'license': 'CC0-1.0', 'n_tokens_mistral': 3511, 'n_tokens_neox': 3387, 'n_words': 1063, 'path': 'misc/NetworkX workshop/I. The Graph Data Structures.ipynb', 'repo_name': 'neo-mashiro/BU', 'size': '9989'}