vrfcd 0.1.1__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.
vrfcd/__init__.py ADDED
@@ -0,0 +1,14 @@
1
+ from .distance import compute_van_rossum_distance
2
+ from .kernels import distance_to_functional_matrix
3
+ from .graph import build_functional_graph
4
+ from .communities import detect_communities
5
+ from .pipeline import VRFCDPipeline, VRFCDResult
6
+
7
+ __all__ = [
8
+ "compute_van_rossum_distance",
9
+ "distance_to_functional_matrix",
10
+ "build_functional_graph",
11
+ "detect_communities",
12
+ "VRFCDPipeline",
13
+ "VRFCDResult",
14
+ ]
vrfcd/communities.py ADDED
@@ -0,0 +1,130 @@
1
+ from collections import Counter
2
+
3
+ import networkx as nx
4
+ from networkx.algorithms.community import louvain_communities
5
+
6
+
7
+ def detect_communities(
8
+ G,
9
+ method="louvain",
10
+ seed=42,
11
+ resolution=1.0,
12
+ threshold=1e-10,
13
+ ):
14
+ """
15
+ Detect communities in a weighted functional graph.
16
+
17
+ Parameters
18
+ ----------
19
+ G : networkx.Graph
20
+ Weighted graph.
21
+ method : {"louvain", "leiden"}
22
+ Community detection method.
23
+ seed : int
24
+ Random seed.
25
+ resolution : float
26
+ Louvain resolution parameter.
27
+ threshold : float
28
+ Louvain threshold parameter.
29
+
30
+ Returns
31
+ -------
32
+ partition : dict
33
+ Mapping node -> community label.
34
+ """
35
+
36
+ if method == "louvain":
37
+ communities = louvain_communities(
38
+ G,
39
+ weight="weight",
40
+ seed=seed,
41
+ resolution=resolution,
42
+ threshold=threshold,
43
+ )
44
+
45
+ partition = {node: cid for cid, comm in enumerate(communities) for node in comm}
46
+
47
+ elif method == "leiden":
48
+ try:
49
+ import igraph as ig
50
+ import leidenalg
51
+ except ImportError as exc:
52
+ raise ImportError(
53
+ "Leiden requires optional dependencies. "
54
+ "Install using: pip install -e '.[leiden]'"
55
+ ) from exc
56
+
57
+ nodes = list(G.nodes())
58
+ node_to_idx = {node: idx for idx, node in enumerate(nodes)}
59
+
60
+ edges = [(node_to_idx[u], node_to_idx[v]) for u, v in G.edges()]
61
+
62
+ weights = [G[u][v].get("weight", 1.0) for u, v in G.edges()]
63
+
64
+ g_ig = ig.Graph()
65
+ g_ig.add_vertices(len(nodes))
66
+ g_ig.add_edges(edges)
67
+ g_ig.es["weight"] = weights
68
+ g_ig.vs["name"] = nodes
69
+
70
+ part = leidenalg.find_partition(
71
+ g_ig,
72
+ leidenalg.ModularityVertexPartition,
73
+ weights="weight",
74
+ seed=seed,
75
+ )
76
+
77
+ partition = {}
78
+
79
+ for cid, comm in enumerate(part):
80
+ for idx in comm:
81
+ partition[g_ig.vs[idx]["name"]] = cid
82
+
83
+ else:
84
+ raise ValueError("method must be 'louvain' or 'leiden'.")
85
+
86
+ return partition
87
+
88
+
89
+ def add_community_attributes(G, partition, attribute="community"):
90
+ """
91
+ Add community labels as node attributes.
92
+ """
93
+
94
+ G_out = G.copy()
95
+ nx.set_node_attributes(G_out, partition, attribute)
96
+ return G_out
97
+
98
+
99
+ def community_counts(partition):
100
+ """
101
+ Count nodes in each community.
102
+ """
103
+
104
+ return Counter(partition.values())
105
+
106
+
107
+ def make_partition_weighted_graph(
108
+ G,
109
+ partition,
110
+ within_scale=2.0,
111
+ between_scale=0.5,
112
+ ):
113
+ """
114
+ Modify edge weights for plotting.
115
+
116
+ Within-community edges are strengthened and between-community edges
117
+ are weakened.
118
+ """
119
+
120
+ G_comm = G.copy()
121
+
122
+ for u, v, data in G_comm.edges(data=True):
123
+ w = data.get("weight", 1.0)
124
+
125
+ if partition[u] == partition[v]:
126
+ data["weight"] = w * within_scale
127
+ else:
128
+ data["weight"] = w * between_scale
129
+
130
+ return G_comm
vrfcd/distance.py ADDED
@@ -0,0 +1,109 @@
1
+ import numpy as np
2
+ from scipy import signal
3
+ from joblib import Parallel, delayed
4
+
5
+
6
+ def compute_van_rossum_distance(
7
+ spike_matrix,
8
+ t,
9
+ t_R,
10
+ traces=False,
11
+ n_jobs=6,
12
+ normalise=True,
13
+ verbose=10,
14
+ ):
15
+ """
16
+ Compute the normalised van Rossum distance matrix.
17
+
18
+ Parameters
19
+ ----------
20
+ spike_matrix : array-like, shape (n_neurons, n_time_bins)
21
+ Binary or count spike matrix.
22
+ t : array-like, shape (n_time_bins,)
23
+ Time axis.
24
+ t_R : float
25
+ van Rossum kernel time constant.
26
+ traces : bool, default=False
27
+ If True, also return convolved spike traces.
28
+ n_jobs : int, default=6
29
+ Number of parallel workers.
30
+ normalise : bool, default=True
31
+ If True, divide distances by sqrt average spike count.
32
+ verbose : int, default=10
33
+ Joblib verbosity.
34
+
35
+ Returns
36
+ -------
37
+ D : ndarray
38
+ van Rossum distance matrix.
39
+ waveforms : ndarray, optional
40
+ Convolved spike traces.
41
+ """
42
+
43
+ t = np.asarray(t, dtype=float)
44
+
45
+ if not isinstance(spike_matrix, np.ndarray):
46
+ spike_matrix = np.asarray(spike_matrix.todense())
47
+ else:
48
+ spike_matrix = np.asarray(spike_matrix)
49
+
50
+ if spike_matrix.ndim != 2:
51
+ raise ValueError("spike_matrix must have shape (n_neurons, n_time_bins).")
52
+
53
+ if spike_matrix.shape[1] != len(t):
54
+ raise ValueError("Length of t must match the number of time bins.")
55
+
56
+ if t_R <= 0:
57
+ raise ValueError("t_R must be positive.")
58
+
59
+ dt = np.mean(np.diff(t))
60
+ n_neurons, n_time = spike_matrix.shape
61
+
62
+ kernel = np.exp(-t / t_R)
63
+
64
+ waveforms = np.zeros((n_neurons, n_time))
65
+
66
+ for j in range(n_neurons):
67
+ waveforms[j, :] = signal.convolve(
68
+ spike_matrix[j, :],
69
+ kernel,
70
+ mode="full",
71
+ )[:n_time]
72
+
73
+ spike_counts = (spike_matrix > 0).sum(axis=1)
74
+
75
+ def compute_row(j):
76
+ waveform_difference = waveforms - waveforms[j]
77
+
78
+ raw = np.sqrt(dt * np.sum(waveform_difference**2, axis=1) / t_R)
79
+
80
+ if normalise:
81
+ avg_spikes = np.sqrt((spike_counts + spike_counts[j]) / 2)
82
+
83
+ row = np.divide(
84
+ raw,
85
+ avg_spikes,
86
+ out=np.zeros_like(raw),
87
+ where=avg_spikes > 0,
88
+ )
89
+ else:
90
+ row = raw
91
+
92
+ return j, row
93
+
94
+ results = Parallel(n_jobs=n_jobs, verbose=verbose)(
95
+ delayed(compute_row)(j) for j in range(n_neurons)
96
+ )
97
+
98
+ D = np.zeros((n_neurons, n_neurons))
99
+
100
+ for j, row in results:
101
+ D[j, :] = row
102
+
103
+ D = 0.5 * (D + D.T)
104
+ np.fill_diagonal(D, 0.0)
105
+
106
+ if traces:
107
+ return D, waveforms
108
+
109
+ return D
vrfcd/graph.py ADDED
@@ -0,0 +1,55 @@
1
+ import numpy as np
2
+ import networkx as nx
3
+
4
+
5
+ def build_functional_graph(
6
+ A,
7
+ node_names=None,
8
+ threshold=0.0,
9
+ ):
10
+ """
11
+ Build a weighted NetworkX graph from a functional adjacency matrix.
12
+
13
+ Parameters
14
+ ----------
15
+ A : ndarray
16
+ Functional adjacency matrix.
17
+ node_names : list, optional
18
+ Node labels.
19
+ threshold : float
20
+ Only edges with weight > threshold are added.
21
+
22
+ Returns
23
+ -------
24
+ G : networkx.Graph
25
+ Weighted graph.
26
+ """
27
+
28
+ A = np.asarray(A, dtype=float)
29
+
30
+ if A.ndim != 2 or A.shape[0] != A.shape[1]:
31
+ raise ValueError("A must be a square matrix.")
32
+
33
+ n = A.shape[0]
34
+
35
+ if node_names is None:
36
+ node_names = list(range(n))
37
+
38
+ if len(node_names) != n:
39
+ raise ValueError("node_names must have length equal to A.shape[0].")
40
+
41
+ G = nx.Graph()
42
+ G.add_nodes_from(node_names)
43
+
44
+ for i in range(n):
45
+ for j in range(i + 1, n):
46
+ weight = A[i, j]
47
+
48
+ if weight > threshold:
49
+ G.add_edge(
50
+ node_names[i],
51
+ node_names[j],
52
+ weight=float(weight),
53
+ )
54
+
55
+ return G
vrfcd/kernels.py ADDED
@@ -0,0 +1,71 @@
1
+ import numpy as np
2
+
3
+
4
+ def distance_to_functional_matrix(
5
+ D,
6
+ kernel="minmax",
7
+ beta=0.1,
8
+ q_low=0.0,
9
+ q_high=1.0,
10
+ zero_diagonal=True,
11
+ ):
12
+ """
13
+ Convert a van Rossum distance matrix into a functional matrix.
14
+
15
+ Parameters
16
+ ----------
17
+ D : ndarray
18
+ Distance matrix.
19
+ kernel : {"clipping", "exponential", "minmax"}
20
+ Similarity transformation.
21
+ beta : float
22
+ Scale parameter for the exponential kernel.
23
+ q_low : float
24
+ Lower quantile for minmax scaling.
25
+ q_high : float
26
+ Upper quantile for minmax scaling.
27
+ zero_diagonal : bool
28
+ Whether to set the diagonal of A to zero.
29
+
30
+ Returns
31
+ -------
32
+ A : ndarray
33
+ Functional adjacency matrix.
34
+ """
35
+
36
+ D = np.asarray(D, dtype=float)
37
+
38
+ if D.ndim != 2 or D.shape[0] != D.shape[1]:
39
+ raise ValueError("D must be a square matrix.")
40
+
41
+ if kernel not in {"clipping", "exponential", "minmax"}:
42
+ raise ValueError("kernel must be one of: 'clipping', 'exponential', 'minmax'.")
43
+
44
+ if kernel == "clipping":
45
+ D_cap = np.clip(D, 0.0, 1.0)
46
+ A = 1.0 - D_cap
47
+
48
+ elif kernel == "exponential":
49
+ if beta <= 0:
50
+ raise ValueError("beta must be positive.")
51
+ A = np.exp(-D / beta)
52
+
53
+ elif kernel == "minmax":
54
+ dvals = D[np.triu_indices_from(D, k=1)]
55
+
56
+ lo = np.quantile(dvals, q_low)
57
+ hi = np.quantile(dvals, q_high)
58
+
59
+ if np.isclose(hi, lo):
60
+ A = np.zeros_like(D)
61
+ else:
62
+ Dnorm = (D - lo) / (hi - lo)
63
+ Dnorm = np.clip(Dnorm, 0.0, 1.0)
64
+ A = 1.0 - Dnorm
65
+
66
+ A = 0.5 * (A + A.T)
67
+
68
+ if zero_diagonal:
69
+ np.fill_diagonal(A, 0.0)
70
+
71
+ return A
vrfcd/pipeline.py ADDED
@@ -0,0 +1,131 @@
1
+ from dataclasses import dataclass
2
+
3
+ from .distance import compute_van_rossum_distance
4
+ from .kernels import distance_to_functional_matrix
5
+ from .graph import build_functional_graph
6
+ from .communities import (
7
+ detect_communities,
8
+ add_community_attributes,
9
+ make_partition_weighted_graph,
10
+ community_counts,
11
+ )
12
+
13
+
14
+ @dataclass
15
+ class VRFCDResult:
16
+ D: object
17
+ A: object
18
+ G: object
19
+ partition: dict | None = None
20
+ G_partitioned: object | None = None
21
+ counts: object | None = None
22
+ waveforms: object | None = None
23
+
24
+
25
+ class VRFCDPipeline:
26
+ """
27
+ End-to-end pipeline:
28
+
29
+ spike matrix -> van Rossum distance -> functional matrix
30
+ -> weighted graph -> community labels
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ t_R,
36
+ kernel="minmax",
37
+ beta=0.1,
38
+ q_low=0.0,
39
+ q_high=1.0,
40
+ graph_threshold=0.0,
41
+ community_method="louvain",
42
+ seed=42,
43
+ n_jobs=6,
44
+ traces=False,
45
+ normalise=True,
46
+ verbose=10,
47
+ ):
48
+ self.t_R = t_R
49
+ self.kernel = kernel
50
+ self.beta = beta
51
+ self.q_low = q_low
52
+ self.q_high = q_high
53
+ self.graph_threshold = graph_threshold
54
+ self.community_method = community_method
55
+ self.seed = seed
56
+ self.n_jobs = n_jobs
57
+ self.traces = traces
58
+ self.normalise = normalise
59
+ self.verbose = verbose
60
+
61
+ def fit(self, spike_matrix, t, node_names=None, detect=True):
62
+ """
63
+ Run the full VRFCD pipeline.
64
+ """
65
+
66
+ if self.traces:
67
+ D, waveforms = compute_van_rossum_distance(
68
+ spike_matrix,
69
+ t,
70
+ self.t_R,
71
+ traces=True,
72
+ n_jobs=self.n_jobs,
73
+ normalise=self.normalise,
74
+ verbose=self.verbose,
75
+ )
76
+ else:
77
+ D = compute_van_rossum_distance(
78
+ spike_matrix,
79
+ t,
80
+ self.t_R,
81
+ traces=False,
82
+ n_jobs=self.n_jobs,
83
+ normalise=self.normalise,
84
+ verbose=self.verbose,
85
+ )
86
+ waveforms = None
87
+
88
+ A = distance_to_functional_matrix(
89
+ D,
90
+ kernel=self.kernel,
91
+ beta=self.beta,
92
+ q_low=self.q_low,
93
+ q_high=self.q_high,
94
+ )
95
+
96
+ G = build_functional_graph(
97
+ A,
98
+ node_names=node_names,
99
+ threshold=self.graph_threshold,
100
+ )
101
+
102
+ if detect:
103
+ partition = detect_communities(
104
+ G,
105
+ method=self.community_method,
106
+ seed=self.seed,
107
+ )
108
+
109
+ G_with_partition = add_community_attributes(G, partition)
110
+
111
+ G_partitioned = make_partition_weighted_graph(
112
+ G_with_partition,
113
+ partition,
114
+ )
115
+
116
+ counts = community_counts(partition)
117
+
118
+ else:
119
+ partition = None
120
+ G_partitioned = None
121
+ counts = None
122
+
123
+ return VRFCDResult(
124
+ D=D,
125
+ A=A,
126
+ G=G,
127
+ partition=partition,
128
+ G_partitioned=G_partitioned,
129
+ counts=counts,
130
+ waveforms=waveforms,
131
+ )
vrfcd/plotting.py ADDED
@@ -0,0 +1,119 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+ import networkx as nx
4
+
5
+
6
+ def plot_matrix(
7
+ M,
8
+ title=None,
9
+ colorbar_title=None,
10
+ cmap="inferno",
11
+ figsize=(7, 6),
12
+ xlabel="Neuron index $j$",
13
+ ylabel="Neuron index $i$",
14
+ savepath=None,
15
+ fontsize=18,
16
+ ):
17
+ """
18
+ Plot a distance or functional matrix.
19
+ """
20
+
21
+ M = np.asarray(M)
22
+
23
+ fig, ax = plt.subplots(figsize=figsize)
24
+
25
+ im = ax.imshow(
26
+ M,
27
+ cmap=cmap,
28
+ vmin=np.min(M),
29
+ vmax=np.max(M),
30
+ origin="lower",
31
+ interpolation="nearest",
32
+ aspect="equal",
33
+ )
34
+
35
+ cbar = fig.colorbar(im, ax=ax, pad=0.03)
36
+
37
+ if colorbar_title is not None:
38
+ cbar.ax.set_title(colorbar_title, fontsize=fontsize, pad=10)
39
+
40
+ cbar.ax.tick_params(labelsize=fontsize)
41
+
42
+ ax.set_xlabel(xlabel, fontsize=fontsize)
43
+ ax.set_ylabel(ylabel, fontsize=fontsize)
44
+
45
+ ax.tick_params(axis="both", labelsize=fontsize)
46
+
47
+ ax.spines["top"].set_visible(False)
48
+ ax.spines["right"].set_visible(False)
49
+
50
+ if title is not None:
51
+ ax.set_title(title, fontsize=fontsize)
52
+
53
+ plt.tight_layout()
54
+
55
+ if savepath is not None:
56
+ fig.savefig(savepath, dpi=300, bbox_inches="tight")
57
+
58
+ return fig, ax
59
+
60
+
61
+ def plot_functional_network(
62
+ G,
63
+ partition=None,
64
+ pos=None,
65
+ weight_attr="weight",
66
+ layout_seed=42,
67
+ figsize=(5, 4),
68
+ node_size=500,
69
+ title=r"Network representation of $A(t_R)$",
70
+ savepath=None,
71
+ fontsize=18,
72
+ ):
73
+ """
74
+ Plot weighted functional network.
75
+
76
+ If partition is supplied, nodes are coloured by community.
77
+ """
78
+
79
+ if pos is None:
80
+ pos = nx.spring_layout(G, weight=weight_attr, seed=layout_seed)
81
+
82
+ fig, ax = plt.subplots(figsize=figsize)
83
+
84
+ if partition is None:
85
+ node_colors = "lightgray"
86
+ else:
87
+ node_colors = [partition[n] for n in G.nodes()]
88
+
89
+ nx.draw_networkx_nodes(
90
+ G,
91
+ pos,
92
+ node_size=node_size,
93
+ node_color=node_colors,
94
+ edgecolors="black",
95
+ linewidths=1.2,
96
+ cmap=plt.cm.tab10,
97
+ ax=ax,
98
+ )
99
+
100
+ widths = [max(0.2, G[u][v].get(weight_attr, 1.0)) for u, v in G.edges()]
101
+
102
+ nx.draw_networkx_edges(
103
+ G,
104
+ pos,
105
+ width=widths,
106
+ alpha=0.8,
107
+ edge_color="black",
108
+ ax=ax,
109
+ )
110
+
111
+ ax.set_title(title, fontsize=fontsize)
112
+ ax.axis("off")
113
+
114
+ plt.tight_layout()
115
+
116
+ if savepath is not None:
117
+ fig.savefig(savepath, dpi=300, bbox_inches="tight")
118
+
119
+ return fig, ax, pos
@@ -0,0 +1,278 @@
1
+ Metadata-Version: 2.4
2
+ Name: vrfcd
3
+ Version: 0.1.1
4
+ Summary: van Rossum distance based functional community detection for spike trains
5
+ Author-email: Indranil Ghosh <indranilg49@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/indrag49/vrfcd
8
+ Project-URL: Repository, https://github.com/indrag49/vrfcd
9
+ Project-URL: Issues, https://github.com/indrag49/vrfcd/issues
10
+ Keywords: computational-neuroscience,spike-trains,van-rossum-distance,functional-connectivity,community-detection,network-science
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: numpy
15
+ Requires-Dist: scipy
16
+ Requires-Dist: pandas
17
+ Requires-Dist: matplotlib
18
+ Requires-Dist: networkx
19
+ Requires-Dist: joblib
20
+ Requires-Dist: scikit-learn
21
+ Provides-Extra: leiden
22
+ Requires-Dist: igraph; extra == "leiden"
23
+ Requires-Dist: leidenalg; extra == "leiden"
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest; extra == "dev"
26
+ Requires-Dist: black; extra == "dev"
27
+ Requires-Dist: ruff; extra == "dev"
28
+ Requires-Dist: jupyter; extra == "dev"
29
+ Dynamic: license-file
30
+
31
+ # vrfcd
32
+
33
+ ![](assets/logo.png)
34
+
35
+ `vrfcd` stands for **van Rossum Functional Community Detection**.
36
+
37
+ This is a `Python` package for constructing functional connectivity networks from neural spike-train data
38
+ using the van Rossum distance, and then detecting functional assemblies in the resulting weighted network.
39
+ The package is designed for computational neuroscience workflows where the input data are spike rasters.
40
+ The goal is to identify groups of neurons with similar temporal spiking structure.
41
+
42
+ ## Workflow
43
+ ![](assets/SampleNew2.png)
44
+
45
+ Given a ```spike train``` as input the workflow is a four step process:
46
+ ```
47
+ - Compute the van Rossum distance matrix
48
+ - Transform it into functional adjacency matrix
49
+ - Prepare the weighted `networkX` graph
50
+ - Perform community detection on the graph
51
+ ```
52
+
53
+ ## Features
54
+ `vrfcd` provides tools to:
55
+
56
+ - compute a normalised van Rossum distance matrix from spike-train raster datasets,
57
+ - convert the distance matrix into a functional adjacency matrix using different similarity transformations,
58
+ - construct a weighted functional graph from the adjacency matrix,
59
+ - detect communitues using either the Louvain or Leiden community detection algorithm,
60
+ - visualise the distance matrix, functional matrix, and network representation, and
61
+ - run the full analysis using a single pipeline interface.
62
+
63
+
64
+ ## Installation
65
+
66
+ ```bash
67
+ pip install vrfcd
68
+ ```
69
+
70
+ For development:
71
+
72
+ ```bash
73
+ git clone https://github.com/indranilg49/vrfcd.git
74
+ cd vrfcd
75
+ pip install -e ".[dev]"
76
+ ```
77
+
78
+ For optional Leiden community detection:
79
+
80
+ ```bash
81
+ pip install -e ".[dev,leiden]"
82
+ ```
83
+
84
+ ## Simple usage
85
+
86
+ ```bash
87
+ import numpy as np
88
+ from vrfcd import VRFCDPipeline
89
+
90
+ pipeline = VRFCDPipeline( t_R=0.01, kernel="minmax", community_method="louvain", n_jobs=4, )
91
+
92
+ result = pipeline.fit(spike_matrix, t_axis)
93
+
94
+ D = result.D
95
+ A = result.A
96
+ G = result.G
97
+
98
+ partition = result.partition
99
+ ```
100
+
101
+ Here `spike_matrix` should have the shape of `n_neurons x n_time_bins`, and `t_axis` should be the corresponding
102
+ time axis. Note that if the time axis comes from experimental data with absolute time stamps, it is a good idea
103
+ to rescale it so that it starts at zero:
104
+
105
+ ```bash
106
+ t_axis = t_axis - t_axis[0]
107
+ ```
108
+
109
+ ## Main functions
110
+
111
+ - ```compute_van_rossum_distance```
112
+
113
+ ```bash
114
+ from vrfcd import compute_van_rossum_distance
115
+ ```
116
+
117
+ This computes the pairwise van Rossum distance matrix between spike trains.
118
+
119
+ ```bash
120
+ D = compute_van_rossum_distance(
121
+ spike_matrix,
122
+ t_axis,
123
+ t_R=0.01,
124
+ traces=False,
125
+ n_jobs=4,
126
+ )
127
+ ```
128
+
129
+ The parameter `t_R` controls the time scale of the exponential filter used in the van Rossum distance.
130
+ Smaller values of `t_R` make the distance more sensitive to precise spike timing, while larger values smooth
131
+ spike trains over a longer temporal window.
132
+
133
+ If `traces=True`, the function also returns the convolved spike-train waveforms:
134
+
135
+ ```bash
136
+ D, waveforms = compute_van_rossum_distance( spike_matrix, t_axis, t_R=0.01, traces=True, )
137
+ ```
138
+
139
+ - ```distance_to_functional_matrix```
140
+
141
+ ```bash
142
+ from vrfcd import distance_to_functional_matrix
143
+ ```
144
+
145
+ This converts the van Rossum distance matrix into a functional adjacency matrix.
146
+
147
+ ```bash
148
+ A = distance_to_functional_matrix( D, kernel="minmax", )
149
+ ```
150
+
151
+ This package currently supports three similarity transformations.
152
+
153
+ ```kernel = "clipping"```
154
+
155
+ When using this, the distances are capped at 1 and converted to similarities using: A = 1-D.
156
+
157
+ ```kernel = "exponential"```
158
+
159
+ When using this, the distances are rescaled using an exponential kernel: A = exp(-D/beta). The parameter
160
+ `beta` controls how quickly similarity decays with distance.
161
+
162
+ ```bash
163
+ A = distance_to_functional_matrix( D, kernel="exponential", beta=0.1, )
164
+ ```
165
+
166
+ ```kernel = "minmax"```
167
+
168
+ When using this, the distances are rescaled using a min-max transformation:
169
+
170
+ ```bash
171
+ A = distance_to_functional_matrix(
172
+ D,
173
+ kernel="minmax",
174
+ q_low=0.0,
175
+ q_high=1.0,
176
+ )
177
+ ```
178
+
179
+ This maps small distances to high functional similarity and large distances to low functional similarity.
180
+ The diagonal of the resulting matrix is set to 0 by default.
181
+
182
+ - ```build_functional_graph```
183
+
184
+ ```bash
185
+ from vrfcd import build_functional_graph
186
+ ```
187
+
188
+ This builds a weighted `networkX` graph from the functional adjacency matrix.
189
+
190
+ ```bash
191
+ G = build_functional_graph( A, threshold=0.0, )
192
+ ```
193
+
194
+ In the graph representation, each neuron is a node, and edges are weighted by the functional similarity values
195
+ in `A`. The `threshold` parameter can be used to remove weak functional connections.
196
+
197
+ - ```detect_communities```
198
+
199
+ ```bash
200
+ from vrfcd import detect_communities
201
+ ```
202
+
203
+ This function detects communities in the weighted functional graph.
204
+
205
+ ```bash
206
+ partition = detect_communities( G, method="louvain", seed=42, )
207
+ ```
208
+
209
+ The output is a disctionary mapping each node to a community label. The package currently supports two
210
+ methods for community detection:
211
+
212
+ ```bash
213
+ method = "louvain"
214
+ method = "leiden"
215
+ ```
216
+
217
+ The Louvain method works with the core dependencies. In order to use Leiden, you will need to install the optional dependencies
218
+ `igraph` and `leidenalg` additionally.
219
+
220
+ ## Pipeline interface
221
+
222
+ Here we outline the easiest way to use the package through `VRFCDPipeline`.
223
+
224
+ ```bash
225
+ from vrfcd import VRFCDPipeline
226
+
227
+ pipeline = VRFCDPipeline( t_R=0.01, kernel="minmax", q_low=0.0, q_high=1.0, community_method="louvain", n_jobs=4, traces=True, )
228
+
229
+ result = pipeline.fit(spike_matrix, t_axis)
230
+ ```
231
+
232
+ The pipeline returns a `VRFCDResult` object containing:
233
+
234
+ - `result.D`: van Rossum distance matrix
235
+ - `result.A`: functional adjacency matrix
236
+ - `result.G`: community labels
237
+ - `result.partition`: graph with community attributes
238
+ - `result.G_partitioned`: van Rossum distance matrix
239
+ - `result.counts`: number of nodes in each community
240
+ - `result.waveforms`: convolved spike traces, if `traces=True`
241
+
242
+ This allows the full analysis to be run in one step while still giving access to every intermediate object.
243
+
244
+ ## Plotting
245
+
246
+ `vrfcd` also includes helper functions for viusalisation.
247
+
248
+ ```bash
249
+ from vrfcd.plotting import plot_matrix, plot_functional_network
250
+ ```
251
+
252
+ To plot the van Rossum distance matrix:
253
+
254
+ ```bash
255
+ plot_matrix( result.D, colorbar_title=r"$\tilde{D}(t_R)$", savepath="distance_matrix.pdf", )
256
+ ```
257
+
258
+ To plot the adjacency matrix:
259
+
260
+ ```bash
261
+ plot_matrix( result.A, colorbar_title=r"$A(t_R)$", savepath="functional_matrix.pdf", )
262
+ ```
263
+
264
+ To plot the functional network:
265
+
266
+ ```bash
267
+ plot_functional_network( result.G_partitioned, partition=result.partition, savepath="functional_network.pdf", )
268
+ ```
269
+
270
+ ## Overview
271
+
272
+ This package is intended for exploratory and research-focused analysis of spike-train data. It is particularly useful when
273
+ one wants to compare neurons based on spike timing, construct a functional network from these similarities,
274
+ and identify groups of neurons with similar temporal spiking patterns. This package can be used with synthetic spike raster,
275
+ simulated spiking network models, and experimental recordings such as Neuropixels spike-train datasets.
276
+
277
+
278
+
@@ -0,0 +1,12 @@
1
+ vrfcd/__init__.py,sha256=4rxSSBS-S3oA7YrS6qsDJDtaIEAj-KS2qhYcdBkLDmE,419
2
+ vrfcd/communities.py,sha256=8-Ado-FMFe9vH-xNJYYUQfTueiz_764JK5fh03XL0XQ,3017
3
+ vrfcd/distance.py,sha256=1ouv4NvqyCMgFtbLMpmPsqXzhlXQO9i8uR3MWN2Yh3A,2665
4
+ vrfcd/graph.py,sha256=lRjtgDdZzvy6d4gYzifN2HCVpUJy6rkePSIpzH7R9Ag,1173
5
+ vrfcd/kernels.py,sha256=AhOI1fr2xxnlGGp19OZDBUdlD23I5su54EurUngN7ww,1710
6
+ vrfcd/pipeline.py,sha256=_etbYix_lki82nHNBkkWhfzzpAVUHnJ0WR1z53ryZz4,3246
7
+ vrfcd/plotting.py,sha256=os53Fzy8_vKqAMZRpTx-6Me7FBdGzHOfZ107RP2sLcM,2489
8
+ vrfcd-0.1.1.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ vrfcd-0.1.1.dist-info/METADATA,sha256=3LGr1-dwsAbWxJvOk4wMUkMaft3_ET1J6LRh6U3DFK0,7980
10
+ vrfcd-0.1.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
11
+ vrfcd-0.1.1.dist-info/top_level.txt,sha256=j3yae39lvtgTi3UTrID7haeqDTn4iRbD4m4iVlLeT7o,6
12
+ vrfcd-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
File without changes
@@ -0,0 +1 @@
1
+ vrfcd