Skip to content

Commit f763788

Browse files
committed
add: lower case and moved sierpinski methods to functions
1 parent ef2fb40 commit f763788

File tree

8 files changed

+387
-6307
lines changed

8 files changed

+387
-6307
lines changed

doc/source/community_detection_guide/notebooks/consensus_clustering.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"id": "4aa38bb9-30e4-4859-b547-ba92036ad57a",
1616
"metadata": {},
1717
"source": [
18-
"# Consensus Clustering\n",
18+
"# Consensus clustering\n",
1919
"\n",
2020
"Consensus clustering is a method that combines the results of multiple clustering runs to produce a more stable and reliable clustering solution. It is particularly useful when the results of a single clustering algorithm are not robust or consistent across different runs. By aggregating the outputs of various clustering runs, consensus clustering aims to identify clusters that are consistently found across different runs and are therefore more likely to be true representations of the underlying data structur"
2121
]
@@ -152,7 +152,7 @@
152152
"id": "cd4985bf-0667-4dca-9c3a-2e40f6e63c79",
153153
"metadata": {},
154154
"source": [
155-
"## Karate Cub"
155+
"## Karate club"
156156
]
157157
},
158158
{

doc/source/community_detection_guide/notebooks/functions.ipynb

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,278 @@
503503
" # To display the DataFrame with interactive features, including sticky headers AND frozen first column\n",
504504
" show(df, scrollY=\"300px\", scrollCollapse=True, fixedColumns=True, pageLength=-1)"
505505
]
506+
},
507+
{
508+
"cell_type": "markdown",
509+
"id": "8575f2b2-a73e-400f-af14-f6c833657a43",
510+
"metadata": {},
511+
"source": [
512+
"## Resolution parameter on Sierpinski"
513+
]
514+
},
515+
{
516+
"cell_type": "code",
517+
"execution_count": null,
518+
"id": "fbc4947f-38e1-4086-9c2f-522bacf63e75",
519+
"metadata": {},
520+
"outputs": [],
521+
"source": [
522+
"#import igraph as ig\n",
523+
"#import matplotlib.pyplot as plt\n",
524+
"#import numpy as np\n",
525+
"#import ipywidgets as widgets\n",
526+
"#from IPython.display import display, clear_output\n",
527+
"#import matplotlib.cm as cm \n",
528+
"#import matplotlib.colors as mcolors\n",
529+
"#import colorcet as cc\n",
530+
"#ig.config[\"plotting.backend\"] = \"matplotlib\""
531+
]
532+
},
533+
{
534+
"cell_type": "code",
535+
"execution_count": 1,
536+
"id": "a9181bf9-eaf5-488b-a600-c5e3db066240",
537+
"metadata": {},
538+
"outputs": [],
539+
"source": [
540+
"# Global state for node mapping (needed because igraph uses integer IDs)\n",
541+
"_coord_to_id = {}\n",
542+
"_coords_list = [] # Stores (x, y) tuples in order of their assigned ID\n",
543+
"_next_id = 0\n",
544+
"\n",
545+
"def _get_or_create_vertex(G, coord):\n",
546+
" \"\"\"\n",
547+
" Helper function to get the igraph vertex ID for a given coordinate.\n",
548+
" If the coordinate doesn't exist, it creates a new vertex in G and assigns an ID.\n",
549+
" \"\"\"\n",
550+
" global _coord_to_id, _coords_list, _next_id\n",
551+
" coord_tuple = tuple(coord) # Ensure the coordinate is a hashable tuple\n",
552+
"\n",
553+
" if coord_tuple not in _coord_to_id:\n",
554+
" _coord_to_id[coord_tuple] = _next_id\n",
555+
" _coords_list.append(coord_tuple)\n",
556+
" G.add_vertex() # Add a new vertex to the igraph graph\n",
557+
" _next_id += 1\n",
558+
" return _coord_to_id[coord_tuple]\n",
559+
"\n",
560+
"def _sierpinski_igraph(G, p1, p2, p3, depth):\n",
561+
" \"\"\"\n",
562+
" Recursive function to build the Sierpiński triangle structure in an igraph Graph.\n",
563+
" This function uses integer IDs for vertices after mapping from coordinates.\n",
564+
" \"\"\"\n",
565+
" if depth == 0:\n",
566+
" id1 = _get_or_create_vertex(G, p1)\n",
567+
" id2 = _get_or_create_vertex(G, p2)\n",
568+
" id3 = _get_or_create_vertex(G, p3)\n",
569+
" if not G.are_adjacent(id1, id2): G.add_edge(id1, id2)\n",
570+
" if not G.are_adjacent(id2, id3): G.add_edge(id2, id3)\n",
571+
" if not G.are_adjacent(id3, id1): G.add_edge(id3, id1)\n",
572+
" else:\n",
573+
" a = ((p1[0]+p2[0])/2, (p1[1]+p2[1])/2)\n",
574+
" b = ((p2[0]+p3[0])/2, (p2[1]+p3[1])/2)\n",
575+
" c = ((p3[0]+p1[0])/2, (p3[1]+p1[1])/2)\n",
576+
" _sierpinski_igraph(G, p1, a, c, depth-1)\n",
577+
" _sierpinski_igraph(G, a, p2, b, depth-1)\n",
578+
" _sierpinski_igraph(G, c, b, p3, depth-1)\n",
579+
"\n",
580+
"def draw_sierpinski_igraph_on_axes(depth, ax):\n",
581+
" \"\"\"\n",
582+
" Generates and draws a Sierpiński triangle using igraph onto a given Matplotlib Axes.\n",
583+
" \"\"\"\n",
584+
" global _coord_to_id, _coords_list, _next_id\n",
585+
" \n",
586+
" _coord_to_id = {}\n",
587+
" _coords_list = []\n",
588+
" _next_id = 0\n",
589+
"\n",
590+
" G = ig.Graph()\n",
591+
" p1, p2, p3 = (0, 0), (1, 0), (0.5, np.sqrt(3)/2) \n",
592+
" _sierpinski_igraph(G, p1, p2, p3, depth)\n",
593+
"\n",
594+
" ax.clear()\n",
595+
"\n",
596+
" if not G.vcount():\n",
597+
" ax.set_title(f\"Sierpiński Triangle - Depth {depth} (No vertices)\")\n",
598+
" ax.set_xticks([])\n",
599+
" ax.set_yticks([])\n",
600+
" ax.axis('off')\n",
601+
" return\n",
602+
"\n",
603+
" layout = _coords_list\n",
604+
" ig.plot(G, target=ax, layout=layout, vertex_size=5, vertex_color=\"black\", \n",
605+
" vertex_label=None, edge_color=\"blue\", edge_width=1,\n",
606+
" bbox=(0, 0, 600, 600), margin=20)\n",
607+
" \n",
608+
" ax.set_title(f\"Sierpiński Triangle - Depth {depth}\")\n",
609+
" ax.set_aspect('equal', adjustable='box')\n",
610+
" ax.set_xticks([])\n",
611+
" ax.set_yticks([])\n",
612+
" ax.axis('off')\n",
613+
"\n",
614+
"# Global state for node mapping (needed because igraph uses integer IDs)\n",
615+
"_coord_to_id = {}\n",
616+
"_coords_list = [] # Stores (x, y) tuples in order of their assigned ID\n",
617+
"_next_id = 0\n",
618+
"\n",
619+
"def _get_or_create_vertex(G, coord):\n",
620+
" \"\"\"\n",
621+
" Helper function to get the igraph vertex ID for a given coordinate.\n",
622+
" If the coordinate doesn't exist, it creates a new vertex in G and assigns an ID.\n",
623+
" \"\"\"\n",
624+
" global _coord_to_id, _coords_list, _next_id\n",
625+
" coord_tuple = tuple(coord) # Ensure the coordinate is a hashable tuple\n",
626+
"\n",
627+
" if coord_tuple not in _coord_to_id:\n",
628+
" _coord_to_id[coord_tuple] = _next_id\n",
629+
" _coords_list.append(coord_tuple)\n",
630+
" G.add_vertex() # Add a new vertex to the igraph graph\n",
631+
" _next_id += 1\n",
632+
" return _coord_to_id[coord_tuple]\n",
633+
"\n",
634+
"def _sierpinski_igraph_builder(G, p1, p2, p3, depth):\n",
635+
" \"\"\"\n",
636+
" Recursive function to build the Sierpiński triangle structure in an igraph Graph.\n",
637+
" This function uses integer IDs for vertices after mapping from coordinates.\n",
638+
" \"\"\"\n",
639+
" if depth == 0:\n",
640+
" id1 = _get_or_create_vertex(G, p1)\n",
641+
" id2 = _get_or_create_vertex(G, p2)\n",
642+
" id3 = _get_or_create_vertex(G, p3)\n",
643+
" if not G.are_adjacent(id1, id2): G.add_edge(id1, id2)\n",
644+
" if not G.are_adjacent(id2, id3): G.add_edge(id2, id3)\n",
645+
" if not G.are_adjacent(id3, id1): G.add_edge(id3, id1)\n",
646+
" else:\n",
647+
" a = ((p1[0]+p2[0])/2, (p1[1]+p2[1])/2)\n",
648+
" b = ((p2[0]+p3[0])/2, (p2[1]+p3[1])/2)\n",
649+
" c = ((p3[0]+p1[0])/2, (p3[1]+p1[1])/2)\n",
650+
" _sierpinski_igraph_builder(G, p1, a, c, depth-1)\n",
651+
" _sierpinski_igraph_builder(G, a, p2, b, depth-1)\n",
652+
" _sierpinski_igraph_builder(G, c, b, p3, depth-1)\n",
653+
"\n",
654+
"def get_sierpinski_graph_and_layout(depth):\n",
655+
" \"\"\"\n",
656+
" Generates a Sierpiński triangle graph and its coordinate layout.\n",
657+
" Returns the igraph.Graph object and the list of (x,y) coordinates for its layout.\n",
658+
" \"\"\"\n",
659+
" import numpy as np\n",
660+
" import igraph as ig\n",
661+
" \n",
662+
" global _coord_to_id, _coords_list, _next_id\n",
663+
" \n",
664+
" _coord_to_id = {}\n",
665+
" _coords_list = []\n",
666+
" _next_id = 0\n",
667+
"\n",
668+
" G = ig.Graph()\n",
669+
" p1, p2, p3 = (0, 0), (1, 0), (0.5, np.sqrt(3)/2) \n",
670+
" _sierpinski_igraph_builder(G, p1, p2, p3, depth)\n",
671+
"\n",
672+
" if not G.vcount():\n",
673+
" print(f\"Warning: Sierpiński graph at depth {depth} has no vertices.\")\n",
674+
" return G, [] # Return empty layout if no vertices\n",
675+
"\n",
676+
" return G, _coords_list\n",
677+
"\n",
678+
"# --- Leiden Clustering and Plotting Function ---\n",
679+
"\n",
680+
"def plot_leiden_communities_on_axes(graph, layout, resolution, ax, title_suffix=\"\"):\n",
681+
" \"\"\"\n",
682+
" Clusters a given graph using Leiden with a specified resolution and plots it\n",
683+
" onto the provided Matplotlib Axes, coloring vertices by community.\n",
684+
" \"\"\"\n",
685+
" import colorcet as cc\n",
686+
" \n",
687+
" ax.clear() # Clear the axes for the new plot\n",
688+
"\n",
689+
" communities = graph.community_leiden(objective_function=\"modularity\", resolution=resolution)\n",
690+
" \n",
691+
" num_communities = len(communities)\n",
692+
"\n",
693+
" palette = cc.glasbey_dark\n",
694+
" vertex_colors = [palette[membership_id % len(palette)] for membership_id in communities.membership]\n",
695+
"\n",
696+
" # Handle case where there are no communities or no vertices (though Leiden usually finds at least 1)\n",
697+
" if not vertex_colors and graph.vcount() > 0:\n",
698+
" vertex_colors = [\"lightgray\"] * graph.vcount()\n",
699+
" elif graph.vcount() == 0:\n",
700+
" ax.set_title(f\"Sierpiński Triangle - No vertices (Depth {SIERPINSKI_DEPTH})\")\n",
701+
" ax.axis('off')\n",
702+
" return\n",
703+
"\n",
704+
" ig.plot(\n",
705+
" graph,\n",
706+
" target=ax,\n",
707+
" layout=layout,\n",
708+
" vertex_size=32, # Adjust node size (smaller for higher depth for clarity)\n",
709+
" vertex_color=vertex_colors, # Use community-assigned colors\n",
710+
" vertex_label=None, # No labels for vertices\n",
711+
" edge_color=\"black\", # Edge color (can be made less prominent)\n",
712+
" edge_width=1.5,\n",
713+
" bbox=(0, 0, 600, 600), # Bounding box for the internal renderer\n",
714+
" margin=20, # Margin around the plot area\n",
715+
" )\n",
716+
" \n",
717+
" # Set plot title to indicate resolution and number of communities\n",
718+
" ax.set_title(f\"Resolution: {resolution:.3f} ({num_communities} comms) {title_suffix}\")\n",
719+
" ax.set_aspect('equal', adjustable='box') # Ensure the aspect ratio is 1:1\n",
720+
" ax.set_xticks([]) # Remove x-axis ticks\n",
721+
" ax.set_yticks([]) # Remove y-axis ticks\n",
722+
" ax.axis('off') # Turn off axis lines and labels completely\n",
723+
"\n",
724+
"def create_interactive_resolution_param_tabs():\n",
725+
" import numpy as np\n",
726+
" import ipywidgets as widgets\n",
727+
" import matplotlib.pyplot as plt\n",
728+
" # --- Main Tabbed Interface for Leiden Resolutions ---\n",
729+
" \n",
730+
" # 1. Generate the Sierpiński graph (depth 3) ONCE\n",
731+
" # This graph will be reused for all resolution settings.\n",
732+
" SIERPINSKI_DEPTH = 4\n",
733+
" sierpinski_graph, sierpinski_layout = get_sierpinski_graph_and_layout(SIERPINSKI_DEPTH)\n",
734+
" \n",
735+
" print(f\"Generated Sierpiński Graph (Depth {SIERPINSKI_DEPTH}):\")\n",
736+
" print(f\" Vertices: {sierpinski_graph.vcount()}\")\n",
737+
" print(f\" Edges: {sierpinski_graph.ecount()}\")\n",
738+
" \n",
739+
" # Define a set of resolution parameters to display in different tabs\n",
740+
" # 10**-1.5, 10**-0.75, 10**0, 10**0.75, 10**1.5\n",
741+
" resolution_values = [10 ** i for i in np.arange(-1.5, 1.51, 0.75)]\n",
742+
" \n",
743+
" # Create an Output widget for each resolution value. Each Output widget will hold one plot.\n",
744+
" output_widgets = [widgets.Output() for _ in resolution_values]\n",
745+
" \n",
746+
" # Populate the content for each tab\n",
747+
" tab_titles = []\n",
748+
" for i, res in enumerate(resolution_values):\n",
749+
" with output_widgets[i]: # Direct output to the current Output widget\n",
750+
" fig, ax = plt.subplots(figsize=(7, 7)) # Create a new figure and axes for this tab's plot\n",
751+
" \n",
752+
" # Call the plotting function with the pre-generated graph and layout,\n",
753+
" # and the current resolution parameter.\n",
754+
" plot_leiden_communities_on_axes(\n",
755+
" sierpinski_graph,\n",
756+
" sierpinski_layout,\n",
757+
" resolution=res,\n",
758+
" ax=ax,\n",
759+
" title_suffix=f\"(Depth {SIERPINSKI_DEPTH})\" # Optional suffix for the title\n",
760+
" )\n",
761+
" plt.tight_layout() # Adjust layout to prevent labels/titles from overlapping\n",
762+
" plt.show() # Display the Matplotlib figure within this Output widget\n",
763+
" \n",
764+
" tab_titles.append(f\"Res: {res:.3f}\") # Store the title for this tab\n",
765+
" \n",
766+
" # 4. Create the Tab widget\n",
767+
" leiden_tabs = widgets.Tab()\n",
768+
" leiden_tabs.children = output_widgets # Assign the list of Output widgets as children\n",
769+
" \n",
770+
" # 5. Set the titles for each tab\n",
771+
" for i, title in enumerate(tab_titles):\n",
772+
" leiden_tabs.set_title(i, title)\n",
773+
" \n",
774+
" # 6. Display the Tab widget in your Jupyter Notebook\n",
775+
" print(\"\\nSierpiński Graph with Leiden Communities (varying resolution):\")\n",
776+
" display(leiden_tabs)"
777+
]
506778
}
507779
],
508780
"metadata": {

doc/source/community_detection_guide/notebooks/hierarchical_clustering.ipynb

Lines changed: 11 additions & 11 deletions
Large diffs are not rendered by default.

doc/source/community_detection_guide/notebooks/initial_workflow.ipynb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"id": "352b1210-1303-43f4-881d-4c16effc7332",
3939
"metadata": {},
4040
"source": [
41-
"### 1. Load the graph\n",
41+
"### Load the graph\n",
4242
"First we start by loading the graph:"
4343
]
4444
},
@@ -58,7 +58,7 @@
5858
"id": "07166ec9-696e-46d0-87ee-40cc41f98b10",
5959
"metadata": {},
6060
"source": [
61-
"### 2. Plot the loaded graph (without any community detection yet)\n",
61+
"### Plot the network\n",
6262
"To ensure consistency across our visualizations, we'll first define and compute a single layout for our graph. We will then define a `style` dictionary to store the plot's settings, making them reusable for all our plots:"
6363
]
6464
},
@@ -108,7 +108,7 @@
108108
"id": "cc18a096-7533-44f3-ba1b-43840ebce47d",
109109
"metadata": {},
110110
"source": [
111-
"### 3. Run community detection\n",
111+
"### Run community detection\n",
112112
"Let's run community detection on the network, starting with the Leiden algorithm. This method detects communities by maximizing modularity. \n",
113113
"<div style=\"background-color: #e6ffe6; padding: 20px; border-radius: 5px;\">\n",
114114
" \n",
@@ -307,7 +307,7 @@
307307
"id": "f5ed38a2-849e-49ac-89a4-783b1b686a45",
308308
"metadata": {},
309309
"source": [
310-
"### 4. Visualize detected communities\n",
310+
"### Visualize detected communities\n",
311311
"\n",
312312
"Now we can visualize the obtained clusters using a simple `igraph` visualization technique. First, we will define a color map, and then we will add the `vertex_color` property to the `style` dictionary. This will allow us to color the nodes based on their communities:"
313313
]
@@ -348,7 +348,7 @@
348348
"id": "502357bf-ed1c-407d-aadc-4f4ce88250e5",
349349
"metadata": {},
350350
"source": [
351-
"### 5. Exploring other community detection algorithms (optional)"
351+
"### Exploring other community detection algorithms (optional)"
352352
]
353353
},
354354
{
@@ -395,7 +395,7 @@
395395
"id": "fe6c594c-0d59-4d99-95e9-98dde62720b4",
396396
"metadata": {},
397397
"source": [
398-
"### 6. Test the stability of the result\n",
398+
"### Test the stability of the result\n",
399399
"Next, we can test the stability of the Leiden algorithm's result by:\n",
400400
"\n",
401401
"* Generating multiple partitions:"
@@ -479,7 +479,7 @@
479479
" mean_nmi = np.mean(pairwise_nmi_values)\n",
480480
" plt.axvline(mean_nmi, color='blue', linestyle='dashed', linewidth=2, label=f'Mean NMI: {mean_nmi:.4f}')\n",
481481
" \n",
482-
" plt.title('Stability of Community Detection')\n",
482+
" plt.title('Stability of community detection')\n",
483483
" plt.legend()\n",
484484
" plt.tight_layout()\n",
485485
" plt.show()\n",

0 commit comments

Comments
 (0)