Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:30:10 +08:00
commit f0bd18fb4e
824 changed files with 331919 additions and 0 deletions

View File

@@ -0,0 +1,383 @@
# NetworkX Graph Algorithms
## Shortest Paths
### Single Source Shortest Paths
```python
# Dijkstra's algorithm (weighted graphs)
path = nx.shortest_path(G, source=1, target=5, weight='weight')
length = nx.shortest_path_length(G, source=1, target=5, weight='weight')
# All shortest paths from source
paths = nx.single_source_shortest_path(G, source=1)
lengths = nx.single_source_shortest_path_length(G, source=1)
# Bellman-Ford (handles negative weights)
path = nx.bellman_ford_path(G, source=1, target=5, weight='weight')
```
### All Pairs Shortest Paths
```python
# All pairs (returns iterator)
for source, paths in nx.all_pairs_shortest_path(G):
print(f"From {source}: {paths}")
# Floyd-Warshall algorithm
lengths = dict(nx.all_pairs_shortest_path_length(G))
```
### Specialized Shortest Path Algorithms
```python
# A* algorithm (with heuristic)
def heuristic(u, v):
# Custom heuristic function
return abs(u - v)
path = nx.astar_path(G, source=1, target=5, heuristic=heuristic, weight='weight')
# Average shortest path length
avg_length = nx.average_shortest_path_length(G)
```
## Connectivity
### Connected Components (Undirected)
```python
# Check if connected
is_connected = nx.is_connected(G)
# Number of components
num_components = nx.number_connected_components(G)
# Get all components (returns iterator of sets)
components = list(nx.connected_components(G))
largest_component = max(components, key=len)
# Get component containing specific node
component = nx.node_connected_component(G, node=1)
```
### Strong/Weak Connectivity (Directed)
```python
# Strong connectivity (mutually reachable)
is_strongly_connected = nx.is_strongly_connected(G)
strong_components = list(nx.strongly_connected_components(G))
largest_scc = max(strong_components, key=len)
# Weak connectivity (ignoring direction)
is_weakly_connected = nx.is_weakly_connected(G)
weak_components = list(nx.weakly_connected_components(G))
# Condensation (DAG of strongly connected components)
condensed = nx.condensation(G)
```
### Cuts and Connectivity
```python
# Minimum node/edge cut
min_node_cut = nx.minimum_node_cut(G, s=1, t=5)
min_edge_cut = nx.minimum_edge_cut(G, s=1, t=5)
# Node/edge connectivity
node_connectivity = nx.node_connectivity(G)
edge_connectivity = nx.edge_connectivity(G)
```
## Centrality Measures
### Degree Centrality
```python
# Fraction of nodes each node is connected to
degree_cent = nx.degree_centrality(G)
# For directed graphs
in_degree_cent = nx.in_degree_centrality(G)
out_degree_cent = nx.out_degree_centrality(G)
```
### Betweenness Centrality
```python
# Fraction of shortest paths passing through node
betweenness = nx.betweenness_centrality(G, weight='weight')
# Edge betweenness
edge_betweenness = nx.edge_betweenness_centrality(G, weight='weight')
# Approximate for large graphs
approx_betweenness = nx.betweenness_centrality(G, k=100) # Sample 100 nodes
```
### Closeness Centrality
```python
# Reciprocal of average shortest path length
closeness = nx.closeness_centrality(G)
# For disconnected graphs
closeness = nx.closeness_centrality(G, wf_improved=True)
```
### Eigenvector Centrality
```python
# Centrality based on connections to high-centrality nodes
eigenvector = nx.eigenvector_centrality(G, max_iter=1000)
# Katz centrality (variant with attenuation factor)
katz = nx.katz_centrality(G, alpha=0.1, beta=1.0)
```
### PageRank
```python
# Google's PageRank algorithm
pagerank = nx.pagerank(G, alpha=0.85)
# Personalized PageRank
personalization = {node: 1.0 if node in [1, 2] else 0.0 for node in G}
ppr = nx.pagerank(G, personalization=personalization)
```
## Clustering
### Clustering Coefficients
```python
# Clustering coefficient for each node
clustering = nx.clustering(G)
# Average clustering coefficient
avg_clustering = nx.average_clustering(G)
# Weighted clustering
weighted_clustering = nx.clustering(G, weight='weight')
```
### Transitivity
```python
# Overall clustering (ratio of triangles to triads)
transitivity = nx.transitivity(G)
```
### Triangles
```python
# Count triangles per node
triangles = nx.triangles(G)
# Total number of triangles
total_triangles = sum(triangles.values()) // 3
```
## Community Detection
### Modularity-Based
```python
from networkx.algorithms import community
# Greedy modularity maximization
communities = community.greedy_modularity_communities(G)
# Compute modularity
modularity = community.modularity(G, communities)
```
### Label Propagation
```python
# Fast community detection
communities = community.label_propagation_communities(G)
```
### Girvan-Newman
```python
# Hierarchical community detection via edge betweenness
comp = community.girvan_newman(G)
limited = itertools.takewhile(lambda c: len(c) <= 10, comp)
for communities in limited:
print(tuple(sorted(c) for c in communities))
```
## Matching and Covering
### Maximum Matching
```python
# Maximum cardinality matching
matching = nx.max_weight_matching(G)
# Check if matching is valid
is_matching = nx.is_matching(G, matching)
is_perfect = nx.is_perfect_matching(G, matching)
```
### Minimum Vertex/Edge Cover
```python
# Minimum set of nodes covering all edges
min_vertex_cover = nx.approximation.min_weighted_vertex_cover(G)
# Minimum edge dominating set
min_edge_dom = nx.approximation.min_edge_dominating_set(G)
```
## Tree Algorithms
### Minimum Spanning Tree
```python
# Kruskal's or Prim's algorithm
mst = nx.minimum_spanning_tree(G, weight='weight')
# Maximum spanning tree
mst_max = nx.maximum_spanning_tree(G, weight='weight')
# Enumerate all spanning trees
all_spanning = nx.all_spanning_trees(G)
```
### Tree Properties
```python
# Check if graph is tree
is_tree = nx.is_tree(G)
is_forest = nx.is_forest(G)
# For directed graphs
is_arborescence = nx.is_arborescence(G)
```
## Flow and Capacity
### Maximum Flow
```python
# Maximum flow value
flow_value = nx.maximum_flow_value(G, s=1, t=5, capacity='capacity')
# Maximum flow with flow dict
flow_value, flow_dict = nx.maximum_flow(G, s=1, t=5, capacity='capacity')
# Minimum cut
cut_value, partition = nx.minimum_cut(G, s=1, t=5, capacity='capacity')
```
### Cost Flow
```python
# Minimum cost flow
flow_dict = nx.min_cost_flow(G, demand='demand', capacity='capacity', weight='weight')
cost = nx.cost_of_flow(G, flow_dict, weight='weight')
```
## Cycles
### Finding Cycles
```python
# Simple cycles (for directed graphs)
cycles = list(nx.simple_cycles(G))
# Cycle basis (for undirected graphs)
basis = nx.cycle_basis(G)
# Check if acyclic
is_dag = nx.is_directed_acyclic_graph(G)
```
### Topological Sorting
```python
# Only for DAGs
try:
topo_order = list(nx.topological_sort(G))
except nx.NetworkXError:
print("Graph has cycles")
# All topological sorts
all_topo = nx.all_topological_sorts(G)
```
## Cliques
### Finding Cliques
```python
# All maximal cliques
cliques = list(nx.find_cliques(G))
# Maximum clique (NP-complete, approximate)
max_clique = nx.approximation.max_clique(G)
# Clique number
clique_number = nx.graph_clique_number(G)
# Number of maximal cliques containing each node
clique_counts = nx.node_clique_number(G)
```
## Graph Coloring
### Node Coloring
```python
# Greedy coloring
coloring = nx.greedy_color(G, strategy='largest_first')
# Different strategies: 'largest_first', 'smallest_last', 'random_sequential'
coloring = nx.greedy_color(G, strategy='smallest_last')
```
## Isomorphism
### Graph Isomorphism
```python
# Check if graphs are isomorphic
is_isomorphic = nx.is_isomorphic(G1, G2)
# Get isomorphism mapping
from networkx.algorithms import isomorphism
GM = isomorphism.GraphMatcher(G1, G2)
if GM.is_isomorphic():
mapping = GM.mapping
```
### Subgraph Isomorphism
```python
# Check if G1 is subgraph isomorphic to G2
is_subgraph_iso = nx.is_isomorphic(G1, G2.subgraph(nodes))
```
## Traversal Algorithms
### Depth-First Search (DFS)
```python
# DFS edges
dfs_edges = list(nx.dfs_edges(G, source=1))
# DFS tree
dfs_tree = nx.dfs_tree(G, source=1)
# DFS predecessors
dfs_pred = nx.dfs_predecessors(G, source=1)
# Preorder and postorder
preorder = list(nx.dfs_preorder_nodes(G, source=1))
postorder = list(nx.dfs_postorder_nodes(G, source=1))
```
### Breadth-First Search (BFS)
```python
# BFS edges
bfs_edges = list(nx.bfs_edges(G, source=1))
# BFS tree
bfs_tree = nx.bfs_tree(G, source=1)
# BFS predecessors and successors
bfs_pred = nx.bfs_predecessors(G, source=1)
bfs_succ = nx.bfs_successors(G, source=1)
```
## Efficiency Considerations
### Algorithm Complexity
- Many algorithms have parameters to control computation time
- For large graphs, consider approximate algorithms
- Use `k` parameter to sample nodes in centrality calculations
- Set `max_iter` for iterative algorithms
### Memory Usage
- Iterator-based functions (e.g., `nx.simple_cycles()`) save memory
- Convert to list only when necessary
- Use generators for large result sets
### Numerical Precision
When using weighted algorithms with floating-point numbers, results are approximate. Consider:
- Using integer weights when possible
- Setting appropriate tolerance parameters
- Being aware of accumulated rounding errors in iterative algorithms

View File

@@ -0,0 +1,378 @@
# NetworkX Graph Generators
## Classic Graphs
### Complete Graphs
```python
# Complete graph (all nodes connected to all others)
G = nx.complete_graph(n=10)
# Complete bipartite graph
G = nx.complete_bipartite_graph(n1=5, n2=7)
# Complete multipartite graph
G = nx.complete_multipartite_graph(3, 4, 5) # Three partitions
```
### Cycle and Path Graphs
```python
# Cycle graph (nodes arranged in circle)
G = nx.cycle_graph(n=20)
# Path graph (linear chain)
G = nx.path_graph(n=15)
# Circular ladder graph
G = nx.circular_ladder_graph(n=10)
```
### Regular Graphs
```python
# Empty graph (no edges)
G = nx.empty_graph(n=10)
# Null graph (no nodes)
G = nx.null_graph()
# Star graph (one central node connected to all others)
G = nx.star_graph(n=19) # Creates 20-node star
# Wheel graph (cycle with central hub)
G = nx.wheel_graph(n=10)
```
### Special Named Graphs
```python
# Bull graph
G = nx.bull_graph()
# Chvatal graph
G = nx.chvatal_graph()
# Cubical graph
G = nx.cubical_graph()
# Diamond graph
G = nx.diamond_graph()
# Dodecahedral graph
G = nx.dodecahedral_graph()
# Heawood graph
G = nx.heawood_graph()
# House graph
G = nx.house_graph()
# Petersen graph
G = nx.petersen_graph()
# Karate club graph (classic social network)
G = nx.karate_club_graph()
```
## Random Graphs
### Erdős-Rényi Graphs
```python
# G(n, p) model: n nodes, edge probability p
G = nx.erdos_renyi_graph(n=100, p=0.1, seed=42)
# G(n, m) model: n nodes, exactly m edges
G = nx.gnm_random_graph(n=100, m=500, seed=42)
# Fast version (for large sparse graphs)
G = nx.fast_gnp_random_graph(n=10000, p=0.0001, seed=42)
```
### Watts-Strogatz Small-World
```python
# Small-world network with rewiring
# n nodes, k nearest neighbors, rewiring probability p
G = nx.watts_strogatz_graph(n=100, k=6, p=0.1, seed=42)
# Connected version (guarantees connectivity)
G = nx.connected_watts_strogatz_graph(n=100, k=6, p=0.1, tries=100, seed=42)
```
### Barabási-Albert Preferential Attachment
```python
# Scale-free network (power-law degree distribution)
# n nodes, m edges to attach from new node
G = nx.barabasi_albert_graph(n=100, m=3, seed=42)
# Extended version with parameters
G = nx.extended_barabasi_albert_graph(n=100, m=3, p=0.5, q=0.2, seed=42)
```
### Power Law Degree Sequence
```python
# Power law cluster graph
G = nx.powerlaw_cluster_graph(n=100, m=3, p=0.1, seed=42)
# Random power law tree
G = nx.random_powerlaw_tree(n=100, gamma=3, seed=42, tries=1000)
```
### Configuration Model
```python
# Graph with specified degree sequence
degree_sequence = [3, 3, 3, 3, 2, 2, 2, 1, 1, 1]
G = nx.configuration_model(degree_sequence, seed=42)
# Remove self-loops and parallel edges
G = nx.Graph(G)
G.remove_edges_from(nx.selfloop_edges(G))
```
### Random Geometric Graphs
```python
# Nodes in unit square, edges if distance < radius
G = nx.random_geometric_graph(n=100, radius=0.2, seed=42)
# With positions
pos = nx.get_node_attributes(G, 'pos')
```
### Random Regular Graphs
```python
# Every node has exactly d neighbors
G = nx.random_regular_graph(d=3, n=100, seed=42)
```
### Stochastic Block Model
```python
# Community structure model
sizes = [50, 50, 50] # Three communities
probs = [[0.25, 0.05, 0.02], # Within and between community probabilities
[0.05, 0.35, 0.07],
[0.02, 0.07, 0.40]]
G = nx.stochastic_block_model(sizes, probs, seed=42)
```
## Lattice and Grid Graphs
### Grid Graphs
```python
# 2D grid
G = nx.grid_2d_graph(m=5, n=7) # 5x7 grid
# 3D grid
G = nx.grid_graph(dim=[5, 7, 3]) # 5x7x3 grid
# Hexagonal lattice
G = nx.hexagonal_lattice_graph(m=5, n=7)
# Triangular lattice
G = nx.triangular_lattice_graph(m=5, n=7)
```
### Hypercube
```python
# n-dimensional hypercube
G = nx.hypercube_graph(n=4)
```
## Tree Graphs
### Random Trees
```python
# Random tree with n nodes
G = nx.random_tree(n=100, seed=42)
# Prefix tree (tries)
G = nx.prefix_tree([[0, 1, 2], [0, 1, 3], [0, 4]])
```
### Balanced Trees
```python
# Balanced r-ary tree of height h
G = nx.balanced_tree(r=2, h=5) # Binary tree, height 5
# Full r-ary tree with n nodes
G = nx.full_rary_tree(r=3, n=100) # Ternary tree
```
### Barbell and Lollipop Graphs
```python
# Two complete graphs connected by path
G = nx.barbell_graph(m1=5, m2=3) # Two K_5 graphs with 3-node path
# Complete graph connected to path
G = nx.lollipop_graph(m=7, n=5) # K_7 with 5-node path
```
## Social Network Models
### Karate Club
```python
# Zachary's karate club (classic social network)
G = nx.karate_club_graph()
```
### Davis Southern Women
```python
# Bipartite social network
G = nx.davis_southern_women_graph()
```
### Florentine Families
```python
# Historical marriage and business networks
G = nx.florentine_families_graph()
```
### Les Misérables
```python
# Character co-occurrence network
G = nx.les_miserables_graph()
```
## Directed Graph Generators
### Random Directed Graphs
```python
# Directed Erdős-Rényi
G = nx.gnp_random_graph(n=100, p=0.1, directed=True, seed=42)
# Scale-free directed
G = nx.scale_free_graph(n=100, seed=42)
```
### DAG (Directed Acyclic Graph)
```python
# Random DAG
G = nx.gnp_random_graph(n=20, p=0.2, directed=True, seed=42)
G = nx.DiGraph([(u, v) for (u, v) in G.edges() if u < v]) # Remove backward edges
```
### Tournament Graphs
```python
# Random tournament (complete directed graph)
G = nx.random_tournament(n=10, seed=42)
```
## Duplication-Divergence Models
### Duplication Divergence Graph
```python
# Biological network model (protein interaction networks)
G = nx.duplication_divergence_graph(n=100, p=0.5, seed=42)
```
## Degree Sequence Generators
### Valid Degree Sequences
```python
# Check if degree sequence is valid (graphical)
sequence = [3, 3, 3, 3, 2, 2, 2, 1, 1, 1]
is_valid = nx.is_graphical(sequence)
# For directed graphs
in_sequence = [2, 2, 2, 1, 1]
out_sequence = [2, 2, 1, 2, 1]
is_valid = nx.is_digraphical(in_sequence, out_sequence)
```
### Creating from Degree Sequence
```python
# Havel-Hakimi algorithm
G = nx.havel_hakimi_graph(degree_sequence)
# Configuration model (allows multi-edges/self-loops)
G = nx.configuration_model(degree_sequence)
# Directed configuration model
G = nx.directed_configuration_model(in_degree_sequence, out_degree_sequence)
```
## Bipartite Graphs
### Random Bipartite
```python
# Random bipartite with two node sets
G = nx.bipartite.random_graph(n=50, m=30, p=0.1, seed=42)
# Configuration model for bipartite
G = nx.bipartite.configuration_model(deg1=[3, 3, 2], deg2=[2, 2, 2, 2], seed=42)
```
### Bipartite Generators
```python
# Complete bipartite
G = nx.complete_bipartite_graph(n1=5, n2=7)
# Gnmk random bipartite (n, m nodes, k edges)
G = nx.bipartite.gnmk_random_graph(n=10, m=8, k=20, seed=42)
```
## Operators on Graphs
### Graph Operations
```python
# Union
G = nx.union(G1, G2)
# Disjoint union
G = nx.disjoint_union(G1, G2)
# Compose (overlay)
G = nx.compose(G1, G2)
# Complement
G = nx.complement(G1)
# Cartesian product
G = nx.cartesian_product(G1, G2)
# Tensor (Kronecker) product
G = nx.tensor_product(G1, G2)
# Strong product
G = nx.strong_product(G1, G2)
```
## Customization and Seeding
### Setting Random Seed
Always set seed for reproducible graphs:
```python
G = nx.erdos_renyi_graph(n=100, p=0.1, seed=42)
```
### Converting Graph Types
```python
# Convert to specific type
G_directed = G.to_directed()
G_undirected = G.to_undirected()
G_multi = nx.MultiGraph(G)
```
## Performance Considerations
### Fast Generators
For large graphs, use optimized generators:
```python
# Fast ER graph (sparse)
G = nx.fast_gnp_random_graph(n=10000, p=0.0001, seed=42)
```
### Memory Efficiency
Some generators create graphs incrementally to save memory. For very large graphs, consider:
- Using sparse representations
- Generating subgraphs as needed
- Working with adjacency lists or edge lists instead of full graphs
## Validation and Properties
### Checking Generated Graphs
```python
# Verify properties
print(f"Nodes: {G.number_of_nodes()}")
print(f"Edges: {G.number_of_edges()}")
print(f"Density: {nx.density(G)}")
print(f"Connected: {nx.is_connected(G)}")
# Degree distribution
degree_sequence = sorted([d for n, d in G.degree()], reverse=True)
```

View File

@@ -0,0 +1,283 @@
# NetworkX Graph Basics
## Graph Types
NetworkX supports four main graph classes:
### Graph (Undirected)
```python
import networkx as nx
G = nx.Graph()
```
- Undirected graphs with single edges between nodes
- No parallel edges allowed
- Edges are bidirectional
### DiGraph (Directed)
```python
G = nx.DiGraph()
```
- Directed graphs with one-way connections
- Edge direction matters: (u, v) ≠ (v, u)
- Used for modeling directed relationships
### MultiGraph (Undirected Multi-edge)
```python
G = nx.MultiGraph()
```
- Allows multiple edges between same node pairs
- Useful for modeling multiple relationships
### MultiDiGraph (Directed Multi-edge)
```python
G = nx.MultiDiGraph()
```
- Directed graph with multiple edges between nodes
- Combines features of DiGraph and MultiGraph
## Creating and Adding Nodes
### Single Node Addition
```python
G.add_node(1)
G.add_node("protein_A")
G.add_node((x, y)) # Nodes can be any hashable type
```
### Bulk Node Addition
```python
G.add_nodes_from([2, 3, 4])
G.add_nodes_from(range(100, 110))
```
### Nodes with Attributes
```python
G.add_node(1, time='5pm', color='red')
G.add_nodes_from([
(4, {"color": "red"}),
(5, {"color": "blue", "weight": 1.5})
])
```
### Important Node Properties
- Nodes can be any hashable Python object: strings, tuples, numbers, custom objects
- Node attributes stored as key-value pairs
- Use meaningful node identifiers for clarity
## Creating and Adding Edges
### Single Edge Addition
```python
G.add_edge(1, 2)
G.add_edge('gene_A', 'gene_B')
```
### Bulk Edge Addition
```python
G.add_edges_from([(1, 2), (1, 3), (2, 4)])
G.add_edges_from(edge_list)
```
### Edges with Attributes
```python
G.add_edge(1, 2, weight=4.7, relation='interacts')
G.add_edges_from([
(1, 2, {'weight': 4.7}),
(2, 3, {'weight': 8.2, 'color': 'blue'})
])
```
### Adding from Edge List with Attributes
```python
# From pandas DataFrame
import pandas as pd
df = pd.DataFrame({'source': [1, 2], 'target': [2, 3], 'weight': [4.7, 8.2]})
G = nx.from_pandas_edgelist(df, 'source', 'target', edge_attr='weight')
```
## Examining Graph Structure
### Basic Properties
```python
# Get collections
G.nodes # NodeView of all nodes
G.edges # EdgeView of all edges
G.adj # AdjacencyView for neighbor relationships
# Count elements
G.number_of_nodes() # Total node count
G.number_of_edges() # Total edge count
len(G) # Number of nodes (shorthand)
# Degree information
G.degree() # DegreeView of all node degrees
G.degree(1) # Degree of specific node
list(G.degree()) # List of (node, degree) pairs
```
### Checking Existence
```python
# Check if node exists
1 in G # Returns True/False
G.has_node(1)
# Check if edge exists
G.has_edge(1, 2)
```
### Accessing Neighbors
```python
# Get neighbors of node 1
list(G.neighbors(1))
list(G[1]) # Dictionary-like access
# For directed graphs
list(G.predecessors(1)) # Incoming edges
list(G.successors(1)) # Outgoing edges
```
### Iterating Over Elements
```python
# Iterate over nodes
for node in G.nodes:
print(node, G.nodes[node]) # Access node attributes
# Iterate over edges
for u, v in G.edges:
print(u, v, G[u][v]) # Access edge attributes
# Iterate with attributes
for node, attrs in G.nodes(data=True):
print(node, attrs)
for u, v, attrs in G.edges(data=True):
print(u, v, attrs)
```
## Modifying Graphs
### Removing Elements
```python
# Remove single node (also removes incident edges)
G.remove_node(1)
# Remove multiple nodes
G.remove_nodes_from([1, 2, 3])
# Remove edges
G.remove_edge(1, 2)
G.remove_edges_from([(1, 2), (2, 3)])
```
### Clearing Graph
```python
G.clear() # Remove all nodes and edges
G.clear_edges() # Remove only edges, keep nodes
```
## Attributes and Metadata
### Graph-Level Attributes
```python
G.graph['name'] = 'Social Network'
G.graph['date'] = '2025-01-15'
print(G.graph)
```
### Node Attributes
```python
# Set at creation
G.add_node(1, time='5pm', weight=0.5)
# Set after creation
G.nodes[1]['time'] = '6pm'
nx.set_node_attributes(G, {1: 'red', 2: 'blue'}, 'color')
# Get attributes
G.nodes[1]
G.nodes[1]['time']
nx.get_node_attributes(G, 'color')
```
### Edge Attributes
```python
# Set at creation
G.add_edge(1, 2, weight=4.7, color='red')
# Set after creation
G[1][2]['weight'] = 5.0
nx.set_edge_attributes(G, {(1, 2): 10.5}, 'weight')
# Get attributes
G[1][2]
G[1][2]['weight']
G.edges[1, 2]
nx.get_edge_attributes(G, 'weight')
```
## Subgraphs and Views
### Subgraph Creation
```python
# Create subgraph from node list
nodes_subset = [1, 2, 3, 4]
H = G.subgraph(nodes_subset) # Returns view (references original)
# Create independent copy
H = G.subgraph(nodes_subset).copy()
# Edge-induced subgraph
edge_subset = [(1, 2), (2, 3)]
H = G.edge_subgraph(edge_subset)
```
### Graph Views
```python
# Reverse view (for directed graphs)
G_reversed = G.reverse()
# Convert between directed/undirected
G_undirected = G.to_undirected()
G_directed = G.to_directed()
```
## Graph Information and Diagnostics
### Basic Information
```python
print(nx.info(G)) # Summary of graph structure
# Density (ratio of actual edges to possible edges)
nx.density(G)
# Check if graph is directed
G.is_directed()
# Check if graph is multigraph
G.is_multigraph()
```
### Connectivity Checks
```python
# For undirected graphs
nx.is_connected(G)
nx.number_connected_components(G)
# For directed graphs
nx.is_strongly_connected(G)
nx.is_weakly_connected(G)
```
## Important Considerations
### Floating Point Precision
Once graphs contain floating point numbers, all results are inherently approximate due to precision limitations. Small arithmetic errors can affect algorithm outcomes, particularly in minimum/maximum computations.
### Memory Considerations
Each time a script starts, graph data must be loaded into memory. For large datasets, this can cause performance issues. Consider:
- Using efficient data formats (pickle for Python objects)
- Loading only necessary subgraphs
- Using graph databases for very large networks
### Node and Edge Removal Behavior
When a node is removed, all edges incident with that node are automatically removed as well.

View File

@@ -0,0 +1,441 @@
# NetworkX Input/Output
## Reading Graphs from Files
### Adjacency List Format
```python
# Read adjacency list (simple text format)
G = nx.read_adjlist('graph.adjlist')
# With node type conversion
G = nx.read_adjlist('graph.adjlist', nodetype=int)
# For directed graphs
G = nx.read_adjlist('graph.adjlist', create_using=nx.DiGraph())
# Write adjacency list
nx.write_adjlist(G, 'graph.adjlist')
```
Example adjacency list format:
```
# node neighbors
0 1 2
1 0 3 4
2 0 3
3 1 2 4
4 1 3
```
### Edge List Format
```python
# Read edge list
G = nx.read_edgelist('graph.edgelist')
# With node types and edge data
G = nx.read_edgelist('graph.edgelist',
nodetype=int,
data=(('weight', float),))
# Read weighted edge list
G = nx.read_weighted_edgelist('weighted.edgelist')
# Write edge list
nx.write_edgelist(G, 'graph.edgelist')
# Write weighted edge list
nx.write_weighted_edgelist(G, 'weighted.edgelist')
```
Example edge list format:
```
# source target
0 1
1 2
2 3
3 0
```
Example weighted edge list:
```
# source target weight
0 1 0.5
1 2 1.0
2 3 0.75
```
### GML (Graph Modelling Language)
```python
# Read GML (preserves all attributes)
G = nx.read_gml('graph.gml')
# Write GML
nx.write_gml(G, 'graph.gml')
```
### GraphML Format
```python
# Read GraphML (XML-based format)
G = nx.read_graphml('graph.graphml')
# Write GraphML
nx.write_graphml(G, 'graph.graphml')
# With specific encoding
nx.write_graphml(G, 'graph.graphml', encoding='utf-8')
```
### GEXF (Graph Exchange XML Format)
```python
# Read GEXF
G = nx.read_gexf('graph.gexf')
# Write GEXF
nx.write_gexf(G, 'graph.gexf')
```
### Pajek Format
```python
# Read Pajek .net files
G = nx.read_pajek('graph.net')
# Write Pajek format
nx.write_pajek(G, 'graph.net')
```
### LEDA Format
```python
# Read LEDA format
G = nx.read_leda('graph.leda')
# Write LEDA format
nx.write_leda(G, 'graph.leda')
```
## Working with Pandas
### From Pandas DataFrame
```python
import pandas as pd
# Create graph from edge list DataFrame
df = pd.DataFrame({
'source': [1, 2, 3, 4],
'target': [2, 3, 4, 1],
'weight': [0.5, 1.0, 0.75, 0.25]
})
# Create graph
G = nx.from_pandas_edgelist(df,
source='source',
target='target',
edge_attr='weight')
# With multiple edge attributes
G = nx.from_pandas_edgelist(df,
source='source',
target='target',
edge_attr=['weight', 'color', 'type'])
# Create directed graph
G = nx.from_pandas_edgelist(df,
source='source',
target='target',
create_using=nx.DiGraph())
```
### To Pandas DataFrame
```python
# Convert graph to edge list DataFrame
df = nx.to_pandas_edgelist(G)
# With specific edge attributes
df = nx.to_pandas_edgelist(G, source='node1', target='node2')
```
### Adjacency Matrix with Pandas
```python
# Create DataFrame from adjacency matrix
df = nx.to_pandas_adjacency(G, dtype=int)
# Create graph from adjacency DataFrame
G = nx.from_pandas_adjacency(df)
# For directed graphs
G = nx.from_pandas_adjacency(df, create_using=nx.DiGraph())
```
## NumPy and SciPy Integration
### Adjacency Matrix
```python
import numpy as np
# To NumPy adjacency matrix
A = nx.to_numpy_array(G, dtype=int)
# With specific node order
nodelist = [1, 2, 3, 4, 5]
A = nx.to_numpy_array(G, nodelist=nodelist)
# From NumPy array
G = nx.from_numpy_array(A)
# For directed graphs
G = nx.from_numpy_array(A, create_using=nx.DiGraph())
```
### Sparse Matrix (SciPy)
```python
from scipy import sparse
# To sparse matrix
A = nx.to_scipy_sparse_array(G)
# With specific format (csr, csc, coo, etc.)
A_csr = nx.to_scipy_sparse_array(G, format='csr')
# From sparse matrix
G = nx.from_scipy_sparse_array(A)
```
## JSON Format
### Node-Link Format
```python
import json
# To node-link format (good for d3.js)
data = nx.node_link_data(G)
with open('graph.json', 'w') as f:
json.dump(data, f)
# From node-link format
with open('graph.json', 'r') as f:
data = json.load(f)
G = nx.node_link_graph(data)
```
### Adjacency Data Format
```python
# To adjacency format
data = nx.adjacency_data(G)
with open('graph.json', 'w') as f:
json.dump(data, f)
# From adjacency format
with open('graph.json', 'r') as f:
data = json.load(f)
G = nx.adjacency_graph(data)
```
### Tree Data Format
```python
# For tree graphs
data = nx.tree_data(G, root=0)
with open('tree.json', 'w') as f:
json.dump(data, f)
# From tree format
with open('tree.json', 'r') as f:
data = json.load(f)
G = nx.tree_graph(data)
```
## Pickle Format
### Binary Pickle
```python
import pickle
# Write pickle (preserves all Python objects)
with open('graph.pkl', 'wb') as f:
pickle.dump(G, f)
# Read pickle
with open('graph.pkl', 'rb') as f:
G = pickle.load(f)
# NetworkX convenience functions
nx.write_gpickle(G, 'graph.gpickle')
G = nx.read_gpickle('graph.gpickle')
```
## CSV Files
### Custom CSV Reading
```python
import csv
# Read edges from CSV
G = nx.Graph()
with open('edges.csv', 'r') as f:
reader = csv.DictReader(f)
for row in reader:
G.add_edge(row['source'], row['target'], weight=float(row['weight']))
# Write edges to CSV
with open('edges.csv', 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow(['source', 'target', 'weight'])
for u, v, data in G.edges(data=True):
writer.writerow([u, v, data.get('weight', 1.0)])
```
## Database Integration
### SQL Databases
```python
import sqlite3
import pandas as pd
# Read from SQL database via pandas
conn = sqlite3.connect('network.db')
df = pd.read_sql_query("SELECT source, target, weight FROM edges", conn)
G = nx.from_pandas_edgelist(df, 'source', 'target', edge_attr='weight')
conn.close()
# Write to SQL database
df = nx.to_pandas_edgelist(G)
conn = sqlite3.connect('network.db')
df.to_sql('edges', conn, if_exists='replace', index=False)
conn.close()
```
## Graph Formats for Visualization
### DOT Format (Graphviz)
```python
# Write DOT file for Graphviz
nx.drawing.nx_pydot.write_dot(G, 'graph.dot')
# Read DOT file
G = nx.drawing.nx_pydot.read_dot('graph.dot')
# Generate directly to image (requires Graphviz)
from networkx.drawing.nx_pydot import to_pydot
pydot_graph = to_pydot(G)
pydot_graph.write_png('graph.png')
```
## Cytoscape Integration
### Cytoscape JSON
```python
# Export for Cytoscape
data = nx.cytoscape_data(G)
with open('cytoscape.json', 'w') as f:
json.dump(data, f)
# Import from Cytoscape
with open('cytoscape.json', 'r') as f:
data = json.load(f)
G = nx.cytoscape_graph(data)
```
## Specialized Formats
### Matrix Market Format
```python
from scipy.io import mmread, mmwrite
# Read Matrix Market
A = mmread('graph.mtx')
G = nx.from_scipy_sparse_array(A)
# Write Matrix Market
A = nx.to_scipy_sparse_array(G)
mmwrite('graph.mtx', A)
```
### Shapefile (for Geographic Networks)
```python
# Requires pyshp library
# Read geographic network from shapefile
G = nx.read_shp('roads.shp')
# Write to shapefile
nx.write_shp(G, 'network')
```
## Format Selection Guidelines
### Choose Based on Requirements
**Adjacency List** - Simple, human-readable, no attributes
- Best for: Simple unweighted graphs, quick viewing
**Edge List** - Simple, supports weights, human-readable
- Best for: Weighted graphs, importing/exporting data
**GML/GraphML** - Full attribute preservation, XML-based
- Best for: Complete graph serialization with all metadata
**JSON** - Web-friendly, JavaScript integration
- Best for: Web applications, d3.js visualizations
**Pickle** - Fast, preserves Python objects, binary
- Best for: Python-only storage, complex attributes
**Pandas** - Data analysis integration, DataFrame operations
- Best for: Data processing pipelines, statistical analysis
**NumPy/SciPy** - Numerical computation, sparse matrices
- Best for: Matrix operations, scientific computing
**DOT** - Visualization, Graphviz integration
- Best for: Creating visual diagrams
## Performance Considerations
### Large Graphs
For large graphs, consider:
```python
# Use compressed formats
import gzip
with gzip.open('graph.adjlist.gz', 'wt') as f:
nx.write_adjlist(G, f)
with gzip.open('graph.adjlist.gz', 'rt') as f:
G = nx.read_adjlist(f)
# Use binary formats (faster)
nx.write_gpickle(G, 'graph.gpickle') # Faster than text formats
# Use sparse matrices for adjacency
A = nx.to_scipy_sparse_array(G, format='csr') # Memory efficient
```
### Incremental Loading
For very large graphs:
```python
# Load graph incrementally from edge list
G = nx.Graph()
with open('huge_graph.edgelist') as f:
for line in f:
u, v = line.strip().split()
G.add_edge(u, v)
# Process in chunks
if G.number_of_edges() % 100000 == 0:
print(f"Loaded {G.number_of_edges()} edges")
```
## Error Handling
### Robust File Reading
```python
try:
G = nx.read_graphml('graph.graphml')
except nx.NetworkXError as e:
print(f"Error reading GraphML: {e}")
except FileNotFoundError:
print("File not found")
G = nx.Graph()
# Check if file format is supported
if os.path.exists('graph.txt'):
with open('graph.txt') as f:
first_line = f.readline()
# Detect format and read accordingly
```

View File

@@ -0,0 +1,529 @@
# NetworkX Graph Visualization
## Basic Drawing with Matplotlib
### Simple Visualization
```python
import networkx as nx
import matplotlib.pyplot as plt
# Create and draw graph
G = nx.karate_club_graph()
nx.draw(G)
plt.show()
# Save to file
nx.draw(G)
plt.savefig('graph.png', dpi=300, bbox_inches='tight')
plt.close()
```
### Drawing with Labels
```python
# Draw with node labels
nx.draw(G, with_labels=True)
plt.show()
# Custom labels
labels = {i: f"Node {i}" for i in G.nodes()}
nx.draw(G, labels=labels, with_labels=True)
plt.show()
```
## Layout Algorithms
### Spring Layout (Force-Directed)
```python
# Fruchterman-Reingold force-directed algorithm
pos = nx.spring_layout(G, seed=42)
nx.draw(G, pos=pos, with_labels=True)
plt.show()
# With parameters
pos = nx.spring_layout(G, k=0.5, iterations=50, seed=42)
```
### Circular Layout
```python
# Arrange nodes in circle
pos = nx.circular_layout(G)
nx.draw(G, pos=pos, with_labels=True)
plt.show()
```
### Random Layout
```python
# Random positioning
pos = nx.random_layout(G, seed=42)
nx.draw(G, pos=pos, with_labels=True)
plt.show()
```
### Shell Layout
```python
# Concentric circles
pos = nx.shell_layout(G)
nx.draw(G, pos=pos, with_labels=True)
plt.show()
# With custom shells
shells = [[0, 1, 2], [3, 4, 5, 6], [7, 8, 9]]
pos = nx.shell_layout(G, nlist=shells)
```
### Spectral Layout
```python
# Use eigenvectors of graph Laplacian
pos = nx.spectral_layout(G)
nx.draw(G, pos=pos, with_labels=True)
plt.show()
```
### Kamada-Kawai Layout
```python
# Energy-based layout
pos = nx.kamada_kawai_layout(G)
nx.draw(G, pos=pos, with_labels=True)
plt.show()
```
### Planar Layout
```python
# For planar graphs only
if nx.is_planar(G):
pos = nx.planar_layout(G)
nx.draw(G, pos=pos, with_labels=True)
plt.show()
```
### Tree Layouts
```python
# For tree graphs
if nx.is_tree(G):
pos = nx.nx_agraph.graphviz_layout(G, prog='dot')
nx.draw(G, pos=pos, with_labels=True)
plt.show()
```
## Customizing Node Appearance
### Node Colors
```python
# Single color
nx.draw(G, node_color='red')
# Different colors per node
node_colors = ['red' if G.degree(n) > 5 else 'blue' for n in G.nodes()]
nx.draw(G, node_color=node_colors)
# Color by attribute
colors = [G.nodes[n].get('value', 0) for n in G.nodes()]
nx.draw(G, node_color=colors, cmap=plt.cm.viridis)
plt.colorbar()
plt.show()
```
### Node Sizes
```python
# Size by degree
node_sizes = [100 * G.degree(n) for n in G.nodes()]
nx.draw(G, node_size=node_sizes)
# Size by centrality
centrality = nx.degree_centrality(G)
node_sizes = [3000 * centrality[n] for n in G.nodes()]
nx.draw(G, node_size=node_sizes)
```
### Node Shapes
```python
# Draw nodes separately with different shapes
pos = nx.spring_layout(G)
# Circle nodes
nx.draw_networkx_nodes(G, pos, nodelist=[0, 1, 2],
node_shape='o', node_color='red')
# Square nodes
nx.draw_networkx_nodes(G, pos, nodelist=[3, 4, 5],
node_shape='s', node_color='blue')
nx.draw_networkx_edges(G, pos)
nx.draw_networkx_labels(G, pos)
plt.show()
```
### Node Borders
```python
nx.draw(G, pos=pos,
node_color='lightblue',
edgecolors='black', # Node border color
linewidths=2) # Node border width
plt.show()
```
## Customizing Edge Appearance
### Edge Colors
```python
# Single color
nx.draw(G, edge_color='gray')
# Different colors per edge
edge_colors = ['red' if G[u][v].get('weight', 1) > 0.5 else 'blue'
for u, v in G.edges()]
nx.draw(G, edge_color=edge_colors)
# Color by weight
edges = G.edges()
weights = [G[u][v].get('weight', 1) for u, v in edges]
nx.draw(G, edge_color=weights, edge_cmap=plt.cm.Reds)
```
### Edge Widths
```python
# Width by weight
edge_widths = [3 * G[u][v].get('weight', 1) for u, v in G.edges()]
nx.draw(G, width=edge_widths)
# Width by betweenness
edge_betweenness = nx.edge_betweenness_centrality(G)
edge_widths = [5 * edge_betweenness[(u, v)] for u, v in G.edges()]
nx.draw(G, width=edge_widths)
```
### Edge Styles
```python
# Dashed edges
nx.draw(G, style='dashed')
# Different styles per edge
pos = nx.spring_layout(G)
strong_edges = [(u, v) for u, v in G.edges() if G[u][v].get('weight', 0) > 0.5]
weak_edges = [(u, v) for u, v in G.edges() if G[u][v].get('weight', 0) <= 0.5]
nx.draw_networkx_nodes(G, pos)
nx.draw_networkx_edges(G, pos, edgelist=strong_edges, style='solid', width=2)
nx.draw_networkx_edges(G, pos, edgelist=weak_edges, style='dashed', width=1)
plt.show()
```
### Directed Graphs (Arrows)
```python
# Draw directed graph with arrows
G_directed = nx.DiGraph([(1, 2), (2, 3), (3, 1)])
pos = nx.spring_layout(G_directed)
nx.draw(G_directed, pos=pos, with_labels=True,
arrows=True,
arrowsize=20,
arrowstyle='->',
connectionstyle='arc3,rad=0.1')
plt.show()
```
## Labels and Annotations
### Node Labels
```python
pos = nx.spring_layout(G)
# Custom labels
labels = {n: f"N{n}" for n in G.nodes()}
nx.draw_networkx_labels(G, pos, labels=labels, font_size=12, font_color='white')
# Font customization
nx.draw_networkx_labels(G, pos,
font_size=10,
font_family='serif',
font_weight='bold')
```
### Edge Labels
```python
pos = nx.spring_layout(G)
nx.draw_networkx_nodes(G, pos)
nx.draw_networkx_edges(G, pos)
# Edge labels from attributes
edge_labels = nx.get_edge_attributes(G, 'weight')
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels)
plt.show()
# Custom edge labels
edge_labels = {(u, v): f"{u}-{v}" for u, v in G.edges()}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels)
```
## Advanced Drawing Techniques
### Combining Draw Functions
```python
# Full control by separating components
pos = nx.spring_layout(G, seed=42)
# Draw edges
nx.draw_networkx_edges(G, pos, alpha=0.3, width=1)
# Draw nodes
nx.draw_networkx_nodes(G, pos,
node_color='lightblue',
node_size=500,
edgecolors='black')
# Draw labels
nx.draw_networkx_labels(G, pos, font_size=10)
# Remove axis
plt.axis('off')
plt.tight_layout()
plt.show()
```
### Subgraph Highlighting
```python
pos = nx.spring_layout(G)
# Identify subgraph to highlight
subgraph_nodes = [1, 2, 3, 4]
subgraph = G.subgraph(subgraph_nodes)
# Draw main graph
nx.draw_networkx_nodes(G, pos, node_color='lightgray', node_size=300)
nx.draw_networkx_edges(G, pos, alpha=0.2)
# Highlight subgraph
nx.draw_networkx_nodes(subgraph, pos, node_color='red', node_size=500)
nx.draw_networkx_edges(subgraph, pos, edge_color='red', width=2)
nx.draw_networkx_labels(G, pos)
plt.axis('off')
plt.show()
```
### Community Coloring
```python
from networkx.algorithms import community
# Detect communities
communities = community.greedy_modularity_communities(G)
# Assign colors
color_map = {}
colors = ['red', 'blue', 'green', 'yellow', 'purple', 'orange']
for i, comm in enumerate(communities):
for node in comm:
color_map[node] = colors[i % len(colors)]
node_colors = [color_map[n] for n in G.nodes()]
pos = nx.spring_layout(G)
nx.draw(G, pos=pos, node_color=node_colors, with_labels=True)
plt.show()
```
## Creating Publication-Quality Figures
### High Resolution Export
```python
plt.figure(figsize=(12, 8))
pos = nx.spring_layout(G, seed=42)
nx.draw(G, pos=pos,
node_color='lightblue',
node_size=500,
edge_color='gray',
width=1,
with_labels=True,
font_size=10)
plt.title('Graph Visualization', fontsize=16)
plt.axis('off')
plt.tight_layout()
plt.savefig('publication_graph.png', dpi=300, bbox_inches='tight')
plt.savefig('publication_graph.pdf', bbox_inches='tight') # Vector format
plt.close()
```
### Multi-Panel Figures
```python
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
# Different layouts
layouts = [nx.circular_layout(G), nx.spring_layout(G), nx.spectral_layout(G)]
titles = ['Circular', 'Spring', 'Spectral']
for ax, pos, title in zip(axes, layouts, titles):
nx.draw(G, pos=pos, ax=ax, with_labels=True, node_color='lightblue')
ax.set_title(title)
ax.axis('off')
plt.tight_layout()
plt.savefig('layouts_comparison.png', dpi=300)
plt.close()
```
## Interactive Visualization Libraries
### Plotly (Interactive)
```python
import plotly.graph_objects as go
# Create positions
pos = nx.spring_layout(G)
# Edge trace
edge_x = []
edge_y = []
for edge in G.edges():
x0, y0 = pos[edge[0]]
x1, y1 = pos[edge[1]]
edge_x.extend([x0, x1, None])
edge_y.extend([y0, y1, None])
edge_trace = go.Scatter(
x=edge_x, y=edge_y,
line=dict(width=0.5, color='#888'),
hoverinfo='none',
mode='lines')
# Node trace
node_x = [pos[node][0] for node in G.nodes()]
node_y = [pos[node][1] for node in G.nodes()]
node_trace = go.Scatter(
x=node_x, y=node_y,
mode='markers',
hoverinfo='text',
marker=dict(
showscale=True,
colorscale='YlGnBu',
size=10,
colorbar=dict(thickness=15, title='Node Connections'),
line_width=2))
# Color by degree
node_adjacencies = [len(list(G.neighbors(node))) for node in G.nodes()]
node_trace.marker.color = node_adjacencies
fig = go.Figure(data=[edge_trace, node_trace],
layout=go.Layout(
showlegend=False,
hovermode='closest',
margin=dict(b=0, l=0, r=0, t=0)))
fig.show()
```
### PyVis (Interactive HTML)
```python
from pyvis.network import Network
# Create network
net = Network(notebook=True, height='750px', width='100%')
# Add nodes and edges from NetworkX
net.from_nx(G)
# Customize
net.show_buttons(filter_=['physics'])
# Save
net.show('graph.html')
```
### Graphviz (via pydot)
```python
# Requires graphviz and pydot
from networkx.drawing.nx_pydot import graphviz_layout
pos = graphviz_layout(G, prog='neato') # neato, dot, fdp, sfdp, circo, twopi
nx.draw(G, pos=pos, with_labels=True)
plt.show()
# Export to graphviz
nx.drawing.nx_pydot.write_dot(G, 'graph.dot')
```
## Bipartite Graph Visualization
### Two-Set Layout
```python
from networkx.algorithms import bipartite
# Create bipartite graph
B = nx.Graph()
B.add_nodes_from([1, 2, 3, 4], bipartite=0)
B.add_nodes_from(['a', 'b', 'c', 'd', 'e'], bipartite=1)
B.add_edges_from([(1, 'a'), (1, 'b'), (2, 'b'), (2, 'c'), (3, 'd'), (4, 'e')])
# Layout with two columns
pos = {}
top_nodes = [n for n, d in B.nodes(data=True) if d['bipartite'] == 0]
bottom_nodes = [n for n, d in B.nodes(data=True) if d['bipartite'] == 1]
pos.update({node: (0, i) for i, node in enumerate(top_nodes)})
pos.update({node: (1, i) for i, node in enumerate(bottom_nodes)})
nx.draw(B, pos=pos, with_labels=True,
node_color=['lightblue' if B.nodes[n]['bipartite'] == 0 else 'lightgreen'
for n in B.nodes()])
plt.show()
```
## 3D Visualization
### 3D Network Plot
```python
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 3D spring layout
pos = nx.spring_layout(G, dim=3, seed=42)
# Extract coordinates
node_xyz = np.array([pos[v] for v in G.nodes()])
edge_xyz = np.array([(pos[u], pos[v]) for u, v in G.edges()])
# Create figure
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
# Plot edges
for vizedge in edge_xyz:
ax.plot(*vizedge.T, color='gray', alpha=0.5)
# Plot nodes
ax.scatter(*node_xyz.T, s=100, c='lightblue', edgecolors='black')
# Labels
for i, (x, y, z) in enumerate(node_xyz):
ax.text(x, y, z, str(i))
ax.set_axis_off()
plt.show()
```
## Best Practices
### Performance
- For large graphs (>1000 nodes), use simpler layouts (circular, random)
- Use `alpha` parameter to make dense edges more visible
- Consider downsampling or showing subgraphs for very large networks
### Aesthetics
- Use consistent color schemes
- Scale node sizes meaningfully (e.g., by degree or importance)
- Keep labels readable (adjust font size and position)
- Use white space effectively (adjust figure size)
### Reproducibility
- Always set random seeds for layouts: `nx.spring_layout(G, seed=42)`
- Save layout positions for consistency across multiple plots
- Document color/size mappings in legends or captions
### File Formats
- PNG for raster images (web, presentations)
- PDF for vector graphics (publications, scalable)
- SVG for web and interactive applications
- HTML for interactive visualizations