nettracer3d 0.7.6__py3-none-any.whl → 0.7.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nettracer3d might be problematic. Click here for more details.
- nettracer3d/community_extractor.py +13 -29
- nettracer3d/excelotron.py +1719 -0
- nettracer3d/modularity.py +6 -9
- nettracer3d/neighborhoods.py +354 -0
- nettracer3d/nettracer.py +400 -13
- nettracer3d/nettracer_gui.py +1120 -229
- nettracer3d/proximity.py +89 -9
- nettracer3d/smart_dilate.py +20 -15
- {nettracer3d-0.7.6.dist-info → nettracer3d-0.7.8.dist-info}/METADATA +11 -2
- nettracer3d-0.7.8.dist-info/RECORD +23 -0
- {nettracer3d-0.7.6.dist-info → nettracer3d-0.7.8.dist-info}/WHEEL +1 -1
- nettracer3d-0.7.6.dist-info/RECORD +0 -21
- {nettracer3d-0.7.6.dist-info → nettracer3d-0.7.8.dist-info}/entry_points.txt +0 -0
- {nettracer3d-0.7.6.dist-info → nettracer3d-0.7.8.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-0.7.6.dist-info → nettracer3d-0.7.8.dist-info}/top_level.txt +0 -0
nettracer3d/modularity.py
CHANGED
|
@@ -337,7 +337,7 @@ def community_partition(master_list, weighted = False, style = 0, dostats = True
|
|
|
337
337
|
|
|
338
338
|
try:
|
|
339
339
|
# Overall network modularity using Louvain
|
|
340
|
-
stats['Modularity Entire Network'] = community.modularity(
|
|
340
|
+
stats['Modularity Entire Network'] = community.modularity(G, partition)
|
|
341
341
|
except:
|
|
342
342
|
pass
|
|
343
343
|
|
|
@@ -348,7 +348,7 @@ def community_partition(master_list, weighted = False, style = 0, dostats = True
|
|
|
348
348
|
for i, component in enumerate(connected_components):
|
|
349
349
|
subgraph = G.subgraph(component)
|
|
350
350
|
subgraph_partition = nx.community.louvain_communities(G, weight='weight', seed = seed)
|
|
351
|
-
modularity = community.modularity(
|
|
351
|
+
modularity = community.modularity(subgraph, subgraph_partition)
|
|
352
352
|
num_nodes = len(component)
|
|
353
353
|
stats[f'Modularity of component with {num_nodes} nodes'] = modularity
|
|
354
354
|
except:
|
|
@@ -359,6 +359,7 @@ def community_partition(master_list, weighted = False, style = 0, dostats = True
|
|
|
359
359
|
stats['Number of Communities'] = len(communities)
|
|
360
360
|
community_sizes = [len(com) for com in communities]
|
|
361
361
|
stats['Community Sizes'] = community_sizes
|
|
362
|
+
import numpy as np
|
|
362
363
|
stats['Average Community Size'] = np.mean(community_sizes)
|
|
363
364
|
except:
|
|
364
365
|
pass
|
|
@@ -368,10 +369,6 @@ def community_partition(master_list, weighted = False, style = 0, dostats = True
|
|
|
368
369
|
#stats['Partition Resolution'] = 1.0 # Default resolution parameter
|
|
369
370
|
#except:
|
|
370
371
|
#pass
|
|
371
|
-
try:
|
|
372
|
-
stats['Number of Iterations'] = len(set(partition.values()))
|
|
373
|
-
except:
|
|
374
|
-
pass
|
|
375
372
|
|
|
376
373
|
# Global network metrics
|
|
377
374
|
try:
|
|
@@ -423,12 +420,14 @@ def community_partition(master_list, weighted = False, style = 0, dostats = True
|
|
|
423
420
|
|
|
424
421
|
# Degree centrality
|
|
425
422
|
degree_cent = nx.degree_centrality(subgraph)
|
|
423
|
+
import numpy as np
|
|
426
424
|
stats[f'Community {i+1} Avg Degree Centrality'] = np.mean(list(degree_cent.values()))
|
|
427
425
|
|
|
428
426
|
# Average path length (only for connected subgraphs)
|
|
429
427
|
if nx.is_connected(subgraph):
|
|
430
428
|
stats[f'Community {i+1} Avg Path Length'] = nx.average_shortest_path_length(subgraph)
|
|
431
429
|
except:
|
|
430
|
+
import traceback
|
|
432
431
|
pass
|
|
433
432
|
|
|
434
433
|
return stats
|
|
@@ -475,7 +474,7 @@ def community_partition(master_list, weighted = False, style = 0, dostats = True
|
|
|
475
474
|
G = nx.Graph()
|
|
476
475
|
G.add_edges_from(edges)
|
|
477
476
|
|
|
478
|
-
#
|
|
477
|
+
# Louvain with NetworkX's implementation
|
|
479
478
|
communities = list(nx.community.louvain_communities(G, seed = seed))
|
|
480
479
|
|
|
481
480
|
# Convert to the same format as community_louvain.best_partition
|
|
@@ -525,8 +524,6 @@ def community_partition(master_list, weighted = False, style = 0, dostats = True
|
|
|
525
524
|
|
|
526
525
|
stats = calculate_network_stats(G, communities)
|
|
527
526
|
|
|
528
|
-
|
|
529
|
-
|
|
530
527
|
return output, normalized_weights, stats
|
|
531
528
|
|
|
532
529
|
elif style == 0:
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from sklearn.cluster import KMeans
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
from typing import Dict, Set
|
|
5
|
+
import umap
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
os.environ['LOKY_MAX_CPU_COUNT'] = '4'
|
|
11
|
+
|
|
12
|
+
def cluster_arrays(data_input, n_clusters, seed = 42):
|
|
13
|
+
"""
|
|
14
|
+
Simple clustering of 1D arrays with key tracking.
|
|
15
|
+
|
|
16
|
+
Parameters:
|
|
17
|
+
-----------
|
|
18
|
+
data_input : dict or List[List[float]]
|
|
19
|
+
Dictionary {key: array} or list of arrays to cluster
|
|
20
|
+
n_clusters : int
|
|
21
|
+
How many groups you want
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
--------
|
|
25
|
+
dict: {cluster_id: {'keys': [keys], 'arrays': [arrays]}}
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# Handle both dict and list inputs
|
|
29
|
+
if isinstance(data_input, dict):
|
|
30
|
+
keys = list(data_input.keys())
|
|
31
|
+
array_values = list(data_input.values()) # Use .values() to get the arrays
|
|
32
|
+
else:
|
|
33
|
+
keys = list(range(len(data_input))) # Use indices as keys for lists
|
|
34
|
+
array_values = data_input
|
|
35
|
+
|
|
36
|
+
# Convert to numpy and cluster
|
|
37
|
+
data = np.array(array_values)
|
|
38
|
+
kmeans = KMeans(n_clusters=n_clusters, random_state=seed)
|
|
39
|
+
labels = kmeans.fit_predict(data)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
clusters = [[] for _ in range(n_clusters)]
|
|
43
|
+
|
|
44
|
+
for i, label in enumerate(labels):
|
|
45
|
+
clusters[label].append(keys[i])
|
|
46
|
+
|
|
47
|
+
return clusters
|
|
48
|
+
|
|
49
|
+
def plot_dict_heatmap(unsorted_data_dict, id_set, figsize=(12, 8), title="Neighborhood Heatmap"):
|
|
50
|
+
"""
|
|
51
|
+
Create a heatmap from a dictionary of numpy arrays.
|
|
52
|
+
|
|
53
|
+
Parameters:
|
|
54
|
+
-----------
|
|
55
|
+
data_dict : dict
|
|
56
|
+
Dictionary where keys are identifiers and values are 1D numpy arrays of floats (0-1)
|
|
57
|
+
id_set : list
|
|
58
|
+
List of strings describing what each index in the numpy arrays represents
|
|
59
|
+
figsize : tuple, optional
|
|
60
|
+
Figure size (width, height)
|
|
61
|
+
title : str, optional
|
|
62
|
+
Title for the heatmap
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
--------
|
|
66
|
+
fig, ax : matplotlib figure and axes objects
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
data_dict = {k: unsorted_data_dict[k] for k in sorted(unsorted_data_dict.keys())}
|
|
70
|
+
|
|
71
|
+
# Convert dict to 2D array for heatmap
|
|
72
|
+
# Each row represents one key from the dict
|
|
73
|
+
keys = list(data_dict.keys())
|
|
74
|
+
data_matrix = np.array([data_dict[key] for key in keys])
|
|
75
|
+
|
|
76
|
+
# Create the plot
|
|
77
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
78
|
+
|
|
79
|
+
# Create heatmap with white-to-red colormap
|
|
80
|
+
im = ax.imshow(data_matrix, cmap='Reds', aspect='auto', vmin=0, vmax=1)
|
|
81
|
+
|
|
82
|
+
# Set ticks and labels
|
|
83
|
+
ax.set_xticks(np.arange(len(id_set)))
|
|
84
|
+
ax.set_yticks(np.arange(len(keys)))
|
|
85
|
+
ax.set_xticklabels(id_set)
|
|
86
|
+
ax.set_yticklabels(keys)
|
|
87
|
+
|
|
88
|
+
# Rotate x-axis labels for better readability
|
|
89
|
+
plt.setp(ax.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor")
|
|
90
|
+
|
|
91
|
+
# Add text annotations showing the actual values
|
|
92
|
+
for i in range(len(keys)):
|
|
93
|
+
for j in range(len(id_set)):
|
|
94
|
+
text = ax.text(j, i, f'{data_matrix[i, j]:.3f}',
|
|
95
|
+
ha="center", va="center", color="black", fontsize=8)
|
|
96
|
+
|
|
97
|
+
# Add colorbar
|
|
98
|
+
cbar = ax.figure.colorbar(im, ax=ax)
|
|
99
|
+
cbar.ax.set_ylabel('Intensity', rotation=-90, va="bottom")
|
|
100
|
+
|
|
101
|
+
# Set labels and title
|
|
102
|
+
ax.set_xlabel('Proportion of Node Type')
|
|
103
|
+
ax.set_ylabel('Neighborhood')
|
|
104
|
+
ax.set_title(title)
|
|
105
|
+
|
|
106
|
+
# Adjust layout to prevent label cutoff
|
|
107
|
+
plt.tight_layout()
|
|
108
|
+
|
|
109
|
+
plt.show()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def visualize_cluster_composition_umap(cluster_data: Dict[int, np.ndarray],
|
|
113
|
+
class_names: Set[str],
|
|
114
|
+
label = False,
|
|
115
|
+
n_components: int = 2,
|
|
116
|
+
random_state: int = 42):
|
|
117
|
+
"""
|
|
118
|
+
Convert cluster composition data to UMAP visualization.
|
|
119
|
+
|
|
120
|
+
Parameters:
|
|
121
|
+
-----------
|
|
122
|
+
cluster_data : dict
|
|
123
|
+
Dictionary where keys are cluster IDs (int) and values are 1D numpy arrays
|
|
124
|
+
representing the composition of each cluster
|
|
125
|
+
class_names : set
|
|
126
|
+
Set of strings representing the class names (order corresponds to array indices)
|
|
127
|
+
n_components : int
|
|
128
|
+
Number of UMAP components (default: 2 for 2D visualization)
|
|
129
|
+
random_state : int
|
|
130
|
+
Random state for reproducibility
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
--------
|
|
134
|
+
embedding : numpy.ndarray
|
|
135
|
+
UMAP embedding of the cluster compositions
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
# Convert set to sorted list for consistent ordering
|
|
139
|
+
class_labels = sorted(list(class_names))
|
|
140
|
+
|
|
141
|
+
# Extract cluster IDs and compositions
|
|
142
|
+
cluster_ids = list(cluster_data.keys())
|
|
143
|
+
compositions = np.array([cluster_data[cluster_id] for cluster_id in cluster_ids])
|
|
144
|
+
|
|
145
|
+
# Create UMAP reducer
|
|
146
|
+
reducer = umap.UMAP(n_components=n_components, random_state=random_state)
|
|
147
|
+
|
|
148
|
+
# Fit and transform the composition data
|
|
149
|
+
embedding = reducer.fit_transform(compositions)
|
|
150
|
+
|
|
151
|
+
# Create visualization
|
|
152
|
+
plt.figure(figsize=(10, 8))
|
|
153
|
+
|
|
154
|
+
if n_components == 2:
|
|
155
|
+
scatter = plt.scatter(embedding[:, 0], embedding[:, 1],
|
|
156
|
+
c=cluster_ids, cmap='viridis', s=100, alpha=0.7)
|
|
157
|
+
|
|
158
|
+
if label:
|
|
159
|
+
# Add cluster ID labels
|
|
160
|
+
for i, cluster_id in enumerate(cluster_ids):
|
|
161
|
+
plt.annotate(f'{cluster_id}',
|
|
162
|
+
(embedding[i, 0], embedding[i, 1]),
|
|
163
|
+
xytext=(5, 5), textcoords='offset points',
|
|
164
|
+
fontsize=9, alpha=0.8)
|
|
165
|
+
|
|
166
|
+
plt.colorbar(scatter, label='Community ID')
|
|
167
|
+
plt.xlabel('UMAP Component 1')
|
|
168
|
+
plt.ylabel('UMAP Component 2')
|
|
169
|
+
plt.title('UMAP Visualization of Community Compositions')
|
|
170
|
+
|
|
171
|
+
elif n_components == 3:
|
|
172
|
+
fig = plt.figure(figsize=(12, 9))
|
|
173
|
+
ax = fig.add_subplot(111, projection='3d')
|
|
174
|
+
scatter = ax.scatter(embedding[:, 0], embedding[:, 1], embedding[:, 2],
|
|
175
|
+
c=cluster_ids, cmap='viridis', s=100, alpha=0.7)
|
|
176
|
+
|
|
177
|
+
# Add cluster ID labels
|
|
178
|
+
for i, cluster_id in enumerate(cluster_ids):
|
|
179
|
+
ax.text(embedding[i, 0], embedding[i, 1], embedding[i, 2],
|
|
180
|
+
f'C{cluster_id}', fontsize=8)
|
|
181
|
+
|
|
182
|
+
ax.set_xlabel('UMAP Component 1')
|
|
183
|
+
ax.set_ylabel('UMAP Component 2')
|
|
184
|
+
ax.set_zlabel('UMAP Component 3')
|
|
185
|
+
ax.set_title('3D UMAP Visualization of Cluster Compositions')
|
|
186
|
+
plt.colorbar(scatter, label='Cluster ID')
|
|
187
|
+
|
|
188
|
+
plt.tight_layout()
|
|
189
|
+
plt.show()
|
|
190
|
+
|
|
191
|
+
# Print composition details
|
|
192
|
+
print("Cluster Compositions:")
|
|
193
|
+
print(f"Classes: {class_labels}")
|
|
194
|
+
for i, cluster_id in enumerate(cluster_ids):
|
|
195
|
+
composition = compositions[i]
|
|
196
|
+
print(f"Cluster {cluster_id}: {composition}")
|
|
197
|
+
# Show which classes dominate this cluster
|
|
198
|
+
dominant_indices = np.argsort(composition)[::-1][:2] # Top 2
|
|
199
|
+
dominant_classes = [class_labels[idx] for idx in dominant_indices]
|
|
200
|
+
dominant_values = [composition[idx] for idx in dominant_indices]
|
|
201
|
+
print(f" Dominant: {dominant_classes[0]} ({dominant_values[0]:.3f}), {dominant_classes[1]} ({dominant_values[1]:.3f})")
|
|
202
|
+
|
|
203
|
+
return embedding
|
|
204
|
+
|
|
205
|
+
def create_community_heatmap(community_intensity, node_community, node_centroids, is_3d=True,
|
|
206
|
+
figsize=(12, 8), point_size=50, alpha=0.7, colorbar_label="Community Intensity"):
|
|
207
|
+
"""
|
|
208
|
+
Create a 2D or 3D heatmap showing nodes colored by their community intensities.
|
|
209
|
+
|
|
210
|
+
Parameters:
|
|
211
|
+
-----------
|
|
212
|
+
community_intensity : dict
|
|
213
|
+
Dictionary mapping community IDs to intensity values
|
|
214
|
+
Keys can be np.int64 or regular ints
|
|
215
|
+
|
|
216
|
+
node_community : dict
|
|
217
|
+
Dictionary mapping node IDs to community IDs
|
|
218
|
+
|
|
219
|
+
node_centroids : dict
|
|
220
|
+
Dictionary mapping node IDs to centroids
|
|
221
|
+
Centroids should be [Z, Y, X] for 3D or [1, Y, X] for pseudo-3D
|
|
222
|
+
|
|
223
|
+
is_3d : bool, default=True
|
|
224
|
+
If True, create 3D plot. If False, create 2D plot.
|
|
225
|
+
|
|
226
|
+
figsize : tuple, default=(12, 8)
|
|
227
|
+
Figure size (width, height)
|
|
228
|
+
|
|
229
|
+
point_size : int, default=50
|
|
230
|
+
Size of scatter plot points
|
|
231
|
+
|
|
232
|
+
alpha : float, default=0.7
|
|
233
|
+
Transparency of points (0-1)
|
|
234
|
+
|
|
235
|
+
colorbar_label : str, default="Community Intensity"
|
|
236
|
+
Label for the colorbar
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
--------
|
|
240
|
+
fig, ax : matplotlib figure and axis objects
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
# Convert numpy int64 keys to regular ints for consistency
|
|
244
|
+
community_intensity_clean = {}
|
|
245
|
+
for k, v in community_intensity.items():
|
|
246
|
+
if hasattr(k, 'item'): # numpy scalar
|
|
247
|
+
community_intensity_clean[k.item()] = v
|
|
248
|
+
else:
|
|
249
|
+
community_intensity_clean[k] = v
|
|
250
|
+
|
|
251
|
+
# Prepare data for plotting
|
|
252
|
+
node_positions = []
|
|
253
|
+
node_intensities = []
|
|
254
|
+
|
|
255
|
+
for node_id, centroid in node_centroids.items():
|
|
256
|
+
try:
|
|
257
|
+
# Get community for this node
|
|
258
|
+
community_id = node_community[node_id]
|
|
259
|
+
|
|
260
|
+
# Convert community_id to regular int if it's numpy
|
|
261
|
+
if hasattr(community_id, 'item'):
|
|
262
|
+
community_id = community_id.item()
|
|
263
|
+
|
|
264
|
+
# Get intensity for this community
|
|
265
|
+
intensity = community_intensity_clean[community_id]
|
|
266
|
+
|
|
267
|
+
node_positions.append(centroid)
|
|
268
|
+
node_intensities.append(intensity)
|
|
269
|
+
except:
|
|
270
|
+
pass
|
|
271
|
+
|
|
272
|
+
# Convert to numpy arrays
|
|
273
|
+
positions = np.array(node_positions)
|
|
274
|
+
intensities = np.array(node_intensities)
|
|
275
|
+
|
|
276
|
+
# Determine min and max intensities for color scaling
|
|
277
|
+
min_intensity = np.min(intensities)
|
|
278
|
+
max_intensity = np.max(intensities)
|
|
279
|
+
|
|
280
|
+
# Create figure
|
|
281
|
+
fig = plt.figure(figsize=figsize)
|
|
282
|
+
|
|
283
|
+
if is_3d:
|
|
284
|
+
# 3D plot
|
|
285
|
+
ax = fig.add_subplot(111, projection='3d')
|
|
286
|
+
|
|
287
|
+
# Extract coordinates (assuming [Z, Y, X] format)
|
|
288
|
+
z_coords = positions[:, 0]
|
|
289
|
+
y_coords = positions[:, 1]
|
|
290
|
+
x_coords = positions[:, 2]
|
|
291
|
+
|
|
292
|
+
# Create scatter plot
|
|
293
|
+
scatter = ax.scatter(x_coords, y_coords, z_coords,
|
|
294
|
+
c=intensities, s=point_size, alpha=alpha,
|
|
295
|
+
cmap='RdBu_r', vmin=min_intensity, vmax=max_intensity)
|
|
296
|
+
|
|
297
|
+
ax.set_xlabel('X')
|
|
298
|
+
ax.set_ylabel('Y')
|
|
299
|
+
ax.set_zlabel('Z')
|
|
300
|
+
ax.set_title('3D Community Intensity Heatmap')
|
|
301
|
+
|
|
302
|
+
else:
|
|
303
|
+
# 2D plot (using Y, X coordinates, ignoring Z/first dimension)
|
|
304
|
+
ax = fig.add_subplot(111)
|
|
305
|
+
|
|
306
|
+
# Extract Y, X coordinates
|
|
307
|
+
y_coords = positions[:, 1]
|
|
308
|
+
x_coords = positions[:, 2]
|
|
309
|
+
|
|
310
|
+
# Create scatter plot
|
|
311
|
+
scatter = ax.scatter(x_coords, y_coords,
|
|
312
|
+
c=intensities, s=point_size, alpha=alpha,
|
|
313
|
+
cmap='RdBu_r', vmin=min_intensity, vmax=max_intensity)
|
|
314
|
+
|
|
315
|
+
ax.set_xlabel('X')
|
|
316
|
+
ax.set_ylabel('Y')
|
|
317
|
+
ax.set_title('2D Community Intensity Heatmap')
|
|
318
|
+
ax.grid(True, alpha=0.3)
|
|
319
|
+
|
|
320
|
+
# Set origin to top-left (invert Y-axis)
|
|
321
|
+
ax.invert_yaxis()
|
|
322
|
+
|
|
323
|
+
# Add colorbar
|
|
324
|
+
cbar = plt.colorbar(scatter, ax=ax, shrink=0.8)
|
|
325
|
+
cbar.set_label(colorbar_label)
|
|
326
|
+
|
|
327
|
+
# Add text annotations for min/max values
|
|
328
|
+
cbar.ax.text(1.05, 0, f'Min: {min_intensity:.3f}\n(Blue)',
|
|
329
|
+
transform=cbar.ax.transAxes, va='bottom')
|
|
330
|
+
cbar.ax.text(1.05, 1, f'Max: {max_intensity:.3f}\n(Red)',
|
|
331
|
+
transform=cbar.ax.transAxes, va='top')
|
|
332
|
+
|
|
333
|
+
plt.tight_layout()
|
|
334
|
+
plt.show()
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
# Example usage:
|
|
339
|
+
if __name__ == "__main__":
|
|
340
|
+
# Sample data for demonstration
|
|
341
|
+
sample_dict = {
|
|
342
|
+
'category_A': np.array([0.1, 0.5, 0.8, 0.3, 0.9]),
|
|
343
|
+
'category_B': np.array([0.7, 0.2, 0.6, 0.4, 0.1]),
|
|
344
|
+
'category_C': np.array([0.9, 0.8, 0.2, 0.7, 0.5])
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
sample_id_set = ['feature_1', 'feature_2', 'feature_3', 'feature_4', 'feature_5']
|
|
348
|
+
|
|
349
|
+
# Create the heatmap
|
|
350
|
+
fig, ax = plot_dict_heatmap(sample_dict, sample_id_set,
|
|
351
|
+
title="Sample Heatmap Visualization")
|
|
352
|
+
|
|
353
|
+
plt.show()
|
|
354
|
+
|