Initial commit
This commit is contained in:
383
skills/networkx/references/algorithms.md
Normal file
383
skills/networkx/references/algorithms.md
Normal 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
|
||||
378
skills/networkx/references/generators.md
Normal file
378
skills/networkx/references/generators.md
Normal 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)
|
||||
```
|
||||
283
skills/networkx/references/graph-basics.md
Normal file
283
skills/networkx/references/graph-basics.md
Normal 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.
|
||||
441
skills/networkx/references/io.md
Normal file
441
skills/networkx/references/io.md
Normal 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
|
||||
```
|
||||
529
skills/networkx/references/visualization.md
Normal file
529
skills/networkx/references/visualization.md
Normal 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
|
||||
Reference in New Issue
Block a user