@@ -121,3 +121,70 @@ def calculate_leiden_partition(
121121 clusters = np .array (partition .membership , dtype = int )
122122 logger .finish_progress (progress_name = "Community clustering with %s" % ("leiden" ))
123123 return clusters
124+
125+
126+ def calculate_louvain_partition (
127+ adj : Optional [Union [scipy .sparse .spmatrix , np .ndarray ]] = None ,
128+ input_mat : Optional [np .ndarray ] = None ,
129+ num_neighbors : int = 10 ,
130+ graph_type : Literal ["distance" , "embedding" ] = "distance" ,
131+ resolution : float = 1.0 ,
132+ n_iterations : int = - 1 ,
133+ ) -> np .ndarray :
134+ """Performs Louvain clustering on a given dataset.
135+
136+ Args:
137+ adj: Optional precomputed adjacency matrix
138+ input_mat: Optional, will be used only if 'adj' is not given. The input data, will be interepreted as either a
139+ distance matrix (if :param `graph_type` is "distance" or an embedding matrix (if :param `graph_type` is
140+ "embedding")
141+ num_neighbors: Only used if 'adj' is not given- the number of nearest neighbors for constructing the graph
142+ graph_type: Only used if 'adj' is not given- specifies the input type, either 'distance' or 'embedding'
143+ resolution: The resolution parameter for the Louvain algorithm
144+ n_iterations: The number of iterations for the Louvain algorithm (-1 for unlimited iterations)
145+
146+ Returns:
147+ clusters: Array containing cluster assignments
148+ """
149+ import louvain
150+
151+ from ...logging import logger_manager as lm
152+
153+ logger = lm .get_main_logger ()
154+ if adj is None and input_mat is None :
155+ raise ValueError ("Either `adj` or `input_mat` must be specified" )
156+
157+ logger .info ("using adj_matrix from arg for clustering..." )
158+
159+ if adj is not None :
160+ if scipy .sparse .issparse (adj ):
161+ pass
162+ else :
163+ adj = scipy .sparse .csr_matrix (adj )
164+ sources , targets = adj .nonzero ()
165+ weights = adj [sources , targets ]
166+ if isinstance (weights , np .matrix ):
167+ weights = weights .A1
168+ G = igraph .Graph (directed = None )
169+ G .add_vertices (adj .shape [0 ]) # this adds adjacency.shape[0] vertices
170+ G .add_edges (list (zip (sources , targets )))
171+ try :
172+ G .es ["weight" ] = weights
173+ except KeyError :
174+ pass
175+ if G .vcount () != adj .shape [0 ]:
176+ print (
177+ f"The constructed graph has only { G .vcount ()} nodes. "
178+ "Your adjacency matrix contained redundant nodes."
179+ )
180+ else :
181+ if graph_type == "distance" :
182+ G = distance_knn_graph (input_mat , num_neighbors )
183+ elif graph_type == "embedding" :
184+ G = embedding_knn_graph (input_mat , num_neighbors )
185+ logger .info ("Converting graph_sparse_matrix to igraph object" , indent_level = 2 )
186+ partition_kwargs = {"resolution_parameter" : resolution , "seed" : 42 , "weights" : G .es ["weight" ]}
187+ partition = louvain .find_partition (G , louvain .RBConfigurationVertexPartition , ** partition_kwargs )
188+ clusters = np .array (partition .membership , dtype = int )
189+ logger .finish_progress (progress_name = "Community clustering with %s" % ("louvain" ))
190+ return clusters
0 commit comments