betaMixPy 0.1.4__tar.gz → 0.2.1__tar.gz

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.
@@ -0,0 +1,178 @@
1
+ Metadata-Version: 2.4
2
+ Name: betaMixPy
3
+ Version: 0.2.1
4
+ Summary: A Python package to find strong correlations among P variables, each with N observations.
5
+ Home-page: https://pypi.org/project/betaMixPy/
6
+ Author: Haim Bar
7
+ Author-email: haim.bar@uconn.edu
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: matplotlib
14
+ Requires-Dist: pandas
15
+ Requires-Dist: numpy
16
+ Requires-Dist: scipy
17
+ Dynamic: author
18
+ Dynamic: author-email
19
+ Dynamic: classifier
20
+ Dynamic: description
21
+ Dynamic: description-content-type
22
+ Dynamic: home-page
23
+ Dynamic: license-file
24
+ Dynamic: requires-dist
25
+ Dynamic: summary
26
+
27
+ # betaMixPy
28
+
29
+ Python implementation of the **betaMix** method for detecting edges (significant pairwise associations) in gene co-expression and other multivariate networks.
30
+
31
+ The method is built on the result of Frankl & Maehara (1990): two random vectors in a high-dimensional space are nearly orthogonal with high probability. Pairwise correlations equal the cosine of the angle between column vectors, so the statistic $z_j = 1 - r_{ij}^2 \in (0,1]$ follows a $\mathrm{Beta}((N-1)/2,\,1/2)$ null distribution. An EM algorithm fits a two-component Beta mixture to all $P(P-1)/2$ pairs simultaneously, and a threshold is derived that simultaneously controls a frequentist (Type I error) and a Bayesian (posterior probability) false-positive rate.
32
+
33
+ ## Installation
34
+
35
+ ```
36
+ pip install betaMixPy
37
+ ```
38
+
39
+ ## Quick start
40
+
41
+ ```python
42
+ import pandas as pd
43
+ import betaMixPy as bx
44
+
45
+ # M is N×P: samples in rows, variables (genes/metabolites) in columns
46
+ M = pd.read_csv("my_data.csv", index_col=0)
47
+ res = bx.betaMix(M, ppr=0.01, ind=True) # fit the mixture model
48
+
49
+ bx.shortSummary(res) # parameter estimates + edge count
50
+ bx.assessFit(res) # 2×2 diagnostic panel
51
+
52
+ adjM = bx.getAdjMat(res) # P×P adjacency matrix
53
+ clust = bx.graphComponents(adjM, minCtr=2) # disjoint clusters
54
+ bx.plotBitmapCC(adjM, clust) # bitmap of adjacency
55
+ bx.plotCluster(adjM, 1, clust, labels=True) # network plot of cluster 1
56
+ ```
57
+
58
+ ## Input convention
59
+
60
+ `betaMix()` expects **M as N×P** — N samples in rows, P variables in columns — matching numpy's `corrcoef(M, rowvar=False)` and the R `betaMix` package.
61
+
62
+ > **Upgrading from ≤ 0.1.4?** Remove any `.T` transpose and update code that unpacked the previous tuple return value. See [NEWS.md](NEWS.md) for the full list of breaking changes.
63
+
64
+ ## Key functions
65
+
66
+ ### Fitting and diagnostics
67
+
68
+ | Function | Description |
69
+ |---|---|
70
+ | `betaMix(M, ...)` | Fit the two-component Beta mixture; returns a `dict` |
71
+ | `shortSummary(res)` | Print parameter estimates and edge count |
72
+ | `assessFit(res)` | 2×2 panel: mixture histogram, null Q-Q, non-null Q-Q, ROC |
73
+ | `plotFittedBetaMix(res)` | Fitted mixture density on log scale |
74
+ | `plotROC(res)` | Model-based ROC; returns AUC and Youden-index threshold |
75
+ | `plotNonNullDensity(res)` | Kernel density of non-null z_j values |
76
+
77
+ ### Network analysis
78
+
79
+ | Function | Description |
80
+ |---|---|
81
+ | `getAdjMat(res, ...)` | Build P×P adjacency matrix; `signed=True` for −1/0/1 |
82
+ | `graphComponents(A, ...)` | Disjoint clusters via centrality-based assignment |
83
+ | `sphericalCaps(A, ...)` | Hub-and-spoke clustering (greedy, degree-ordered) |
84
+ | `greedyBasis(A, strategy, ...)` | **Overlapping communities** via maximal independent set |
85
+ | `summarizeClusters(ci)` | Five-number summary per cluster |
86
+ | `summarizeBasis(bi)` | Summary for a `greedyBasis()` result |
87
+ | `collapsedGraph(res, ci)` | Adjacency matrix reduced to cluster centres |
88
+ | `shortestPathDistance(A, numSteps)` | Multi-hop distances in the network |
89
+ | `clusteringCoef(A)` | Vectorised clustering coefficients |
90
+
91
+ ### Visualisation
92
+
93
+ | Function | Description |
94
+ |---|---|
95
+ | `plotBitmapCC(A, ...)` | Bitmap heatmap of adjacency, coloured by cluster |
96
+ | `plotCluster(A, n, ci, ...)` | Force-directed network plot of cluster `n` |
97
+ | `plotDegCC(res, ci)` | Degree vs clustering coefficient scatter |
98
+
99
+ ## `betaMix()` parameters
100
+
101
+ | Parameter | Default | Meaning |
102
+ |---|---|---|
103
+ | `M` | required | N×P DataFrame or array |
104
+ | `ind` | `True` | Independent samples? `False` estimates an effective sample size ν |
105
+ | `maxalpha` | `1e-4` | Per-pair frequentist Type I error bound (use `1e-6` for P > 1000) |
106
+ | `ppr` | `0.05` | Posterior null probability threshold for calling an edge |
107
+ | `method` | `"pearson"` | `"pearson"` or `"spearman"` |
108
+ | `subsamplesize` | `50000` | Pairs subsampled for EM; ≥ 20 000 required (ignored if smaller) |
109
+ | `bmax` | `0.999` | Upper bound of non-null support |
110
+ | `seed` | `912469` | Random seed for subsampling reproducibility |
111
+ | `msg` | `False` | Print EM iteration progress |
112
+
113
+ **Returns** a dict with: `angleMat`, `z_j`, `m0`, `p0`, `ahat`, `bhat`, `etahat`, `ppthr`, `nodes`, `edges`, `bmax`, `N`, `cnt`, `method`.
114
+
115
+ ## Overlapping communities with `greedyBasis()`
116
+
117
+ Unlike `graphComponents` (disjoint) and `sphericalCaps` (hub-centric), `greedyBasis` finds a **maximal independent set (MIS)**: a set of mutually non-adjacent representative nodes. Each remaining node is then assigned to every community whose centre it is adjacent to, giving overlapping membership.
118
+
119
+ ```python
120
+ adjM = bx.getAdjMat(res)
121
+ basis = bx.greedyBasis(adjM, strategy="min_degree", labels=M.columns)
122
+ # columns: node, isBasis, basisOrder, degree, cc, nCovered, nCommunities, communities
123
+
124
+ bx.summarizeBasis(basis)
125
+
126
+ # Nodes in multiple communities simultaneously:
127
+ bridges = basis[basis["nCommunities"] > 1]
128
+
129
+ # Members of the community centred on node 5:
130
+ community5 = basis[basis["communities"].apply(lambda c: 5 in c)]
131
+ ```
132
+
133
+ Two strategies:
134
+
135
+ | Strategy | Selects first | Basis size | Community size | Overlap |
136
+ |---|---|---|---|---|
137
+ | `"min_degree"` (default) | Least-connected node | Larger | Smaller | Higher |
138
+ | `"max_degree"` | Most-connected hub | Smaller | Larger | Lower |
139
+
140
+ **Guaranteed properties**: no two basis nodes share an edge (independence); every non-basis node has at least one basis neighbour (maximality).
141
+
142
+ ## Choosing the right clustering function
143
+
144
+ | | `graphComponents` | `sphericalCaps` | `greedyBasis` |
145
+ |---|---|---|---|
146
+ | Membership | Disjoint | Overlapping | Overlapping |
147
+ | Basis nodes adjacent? | Yes | Yes | **No** (independent set) |
148
+ | Selection criterion | Max degree × cc | Max degree | Min or max degree |
149
+ | Best for | General clusters | Hub-spoke networks | Non-redundant representatives |
150
+
151
+ ## What's new in 0.2.x
152
+
153
+ See [NEWS.md](NEWS.md) for the full changelog. Highlights:
154
+
155
+ - **0.2.1**: `getAdjMat` edge count now matches `betaMix()`; fixed a
156
+ `plotFittedBetaMix` crash; unused dependencies removed; full test suite
157
+
158
+ - **`greedyBasis()`** — overlapping community detection via MIS
159
+ - **Input convention**: M is now N×P (remove any `.T` in existing code)
160
+ - **`betaMix()` returns a `dict`** instead of a tuple
161
+ - **`method="spearman"`** for rank-based correlation
162
+ - **`assessFit()`**, **`plotROC()`**, **`plotNonNullDensity()`** — new diagnostics
163
+ - **Vectorised `clusteringCoef()`** — approximately 10–80× faster
164
+ - Analytic Jacobian for MLE; `brentq` for eta estimation — matching R's solvers
165
+ - Python is approximately 3× faster than R on large benchmark datasets
166
+
167
+ ## See also
168
+
169
+ - R package: [github.com/haimbar/betaMix](https://github.com/haimbar/betaMix)
170
+ - LLM-friendly function reference: [llms.txt](llms.txt)
171
+ - Version history: [NEWS.md](NEWS.md)
172
+ - Additional examples and data: [code/](code/), [data/](data/), [doc/](doc/)
173
+
174
+ ## References
175
+
176
+ Bar, H. & Wells, M. T. (2023). On graphical models and convex geometry. *Computational Statistics & Data Analysis*, 187, 107800. https://doi.org/10.1016/j.csda.2023.107800
177
+
178
+ Frankl, P. & Maehara, H. (1990). Some geometric applications of the beta distribution. *Annals of the Institute of Statistical Mathematics*, 42(3), 463–474.
@@ -0,0 +1,152 @@
1
+ # betaMixPy
2
+
3
+ Python implementation of the **betaMix** method for detecting edges (significant pairwise associations) in gene co-expression and other multivariate networks.
4
+
5
+ The method is built on the result of Frankl & Maehara (1990): two random vectors in a high-dimensional space are nearly orthogonal with high probability. Pairwise correlations equal the cosine of the angle between column vectors, so the statistic $z_j = 1 - r_{ij}^2 \in (0,1]$ follows a $\mathrm{Beta}((N-1)/2,\,1/2)$ null distribution. An EM algorithm fits a two-component Beta mixture to all $P(P-1)/2$ pairs simultaneously, and a threshold is derived that simultaneously controls a frequentist (Type I error) and a Bayesian (posterior probability) false-positive rate.
6
+
7
+ ## Installation
8
+
9
+ ```
10
+ pip install betaMixPy
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```python
16
+ import pandas as pd
17
+ import betaMixPy as bx
18
+
19
+ # M is N×P: samples in rows, variables (genes/metabolites) in columns
20
+ M = pd.read_csv("my_data.csv", index_col=0)
21
+ res = bx.betaMix(M, ppr=0.01, ind=True) # fit the mixture model
22
+
23
+ bx.shortSummary(res) # parameter estimates + edge count
24
+ bx.assessFit(res) # 2×2 diagnostic panel
25
+
26
+ adjM = bx.getAdjMat(res) # P×P adjacency matrix
27
+ clust = bx.graphComponents(adjM, minCtr=2) # disjoint clusters
28
+ bx.plotBitmapCC(adjM, clust) # bitmap of adjacency
29
+ bx.plotCluster(adjM, 1, clust, labels=True) # network plot of cluster 1
30
+ ```
31
+
32
+ ## Input convention
33
+
34
+ `betaMix()` expects **M as N×P** — N samples in rows, P variables in columns — matching numpy's `corrcoef(M, rowvar=False)` and the R `betaMix` package.
35
+
36
+ > **Upgrading from ≤ 0.1.4?** Remove any `.T` transpose and update code that unpacked the previous tuple return value. See [NEWS.md](NEWS.md) for the full list of breaking changes.
37
+
38
+ ## Key functions
39
+
40
+ ### Fitting and diagnostics
41
+
42
+ | Function | Description |
43
+ |---|---|
44
+ | `betaMix(M, ...)` | Fit the two-component Beta mixture; returns a `dict` |
45
+ | `shortSummary(res)` | Print parameter estimates and edge count |
46
+ | `assessFit(res)` | 2×2 panel: mixture histogram, null Q-Q, non-null Q-Q, ROC |
47
+ | `plotFittedBetaMix(res)` | Fitted mixture density on log scale |
48
+ | `plotROC(res)` | Model-based ROC; returns AUC and Youden-index threshold |
49
+ | `plotNonNullDensity(res)` | Kernel density of non-null z_j values |
50
+
51
+ ### Network analysis
52
+
53
+ | Function | Description |
54
+ |---|---|
55
+ | `getAdjMat(res, ...)` | Build P×P adjacency matrix; `signed=True` for −1/0/1 |
56
+ | `graphComponents(A, ...)` | Disjoint clusters via centrality-based assignment |
57
+ | `sphericalCaps(A, ...)` | Hub-and-spoke clustering (greedy, degree-ordered) |
58
+ | `greedyBasis(A, strategy, ...)` | **Overlapping communities** via maximal independent set |
59
+ | `summarizeClusters(ci)` | Five-number summary per cluster |
60
+ | `summarizeBasis(bi)` | Summary for a `greedyBasis()` result |
61
+ | `collapsedGraph(res, ci)` | Adjacency matrix reduced to cluster centres |
62
+ | `shortestPathDistance(A, numSteps)` | Multi-hop distances in the network |
63
+ | `clusteringCoef(A)` | Vectorised clustering coefficients |
64
+
65
+ ### Visualisation
66
+
67
+ | Function | Description |
68
+ |---|---|
69
+ | `plotBitmapCC(A, ...)` | Bitmap heatmap of adjacency, coloured by cluster |
70
+ | `plotCluster(A, n, ci, ...)` | Force-directed network plot of cluster `n` |
71
+ | `plotDegCC(res, ci)` | Degree vs clustering coefficient scatter |
72
+
73
+ ## `betaMix()` parameters
74
+
75
+ | Parameter | Default | Meaning |
76
+ |---|---|---|
77
+ | `M` | required | N×P DataFrame or array |
78
+ | `ind` | `True` | Independent samples? `False` estimates an effective sample size ν |
79
+ | `maxalpha` | `1e-4` | Per-pair frequentist Type I error bound (use `1e-6` for P > 1000) |
80
+ | `ppr` | `0.05` | Posterior null probability threshold for calling an edge |
81
+ | `method` | `"pearson"` | `"pearson"` or `"spearman"` |
82
+ | `subsamplesize` | `50000` | Pairs subsampled for EM; ≥ 20 000 required (ignored if smaller) |
83
+ | `bmax` | `0.999` | Upper bound of non-null support |
84
+ | `seed` | `912469` | Random seed for subsampling reproducibility |
85
+ | `msg` | `False` | Print EM iteration progress |
86
+
87
+ **Returns** a dict with: `angleMat`, `z_j`, `m0`, `p0`, `ahat`, `bhat`, `etahat`, `ppthr`, `nodes`, `edges`, `bmax`, `N`, `cnt`, `method`.
88
+
89
+ ## Overlapping communities with `greedyBasis()`
90
+
91
+ Unlike `graphComponents` (disjoint) and `sphericalCaps` (hub-centric), `greedyBasis` finds a **maximal independent set (MIS)**: a set of mutually non-adjacent representative nodes. Each remaining node is then assigned to every community whose centre it is adjacent to, giving overlapping membership.
92
+
93
+ ```python
94
+ adjM = bx.getAdjMat(res)
95
+ basis = bx.greedyBasis(adjM, strategy="min_degree", labels=M.columns)
96
+ # columns: node, isBasis, basisOrder, degree, cc, nCovered, nCommunities, communities
97
+
98
+ bx.summarizeBasis(basis)
99
+
100
+ # Nodes in multiple communities simultaneously:
101
+ bridges = basis[basis["nCommunities"] > 1]
102
+
103
+ # Members of the community centred on node 5:
104
+ community5 = basis[basis["communities"].apply(lambda c: 5 in c)]
105
+ ```
106
+
107
+ Two strategies:
108
+
109
+ | Strategy | Selects first | Basis size | Community size | Overlap |
110
+ |---|---|---|---|---|
111
+ | `"min_degree"` (default) | Least-connected node | Larger | Smaller | Higher |
112
+ | `"max_degree"` | Most-connected hub | Smaller | Larger | Lower |
113
+
114
+ **Guaranteed properties**: no two basis nodes share an edge (independence); every non-basis node has at least one basis neighbour (maximality).
115
+
116
+ ## Choosing the right clustering function
117
+
118
+ | | `graphComponents` | `sphericalCaps` | `greedyBasis` |
119
+ |---|---|---|---|
120
+ | Membership | Disjoint | Overlapping | Overlapping |
121
+ | Basis nodes adjacent? | Yes | Yes | **No** (independent set) |
122
+ | Selection criterion | Max degree × cc | Max degree | Min or max degree |
123
+ | Best for | General clusters | Hub-spoke networks | Non-redundant representatives |
124
+
125
+ ## What's new in 0.2.x
126
+
127
+ See [NEWS.md](NEWS.md) for the full changelog. Highlights:
128
+
129
+ - **0.2.1**: `getAdjMat` edge count now matches `betaMix()`; fixed a
130
+ `plotFittedBetaMix` crash; unused dependencies removed; full test suite
131
+
132
+ - **`greedyBasis()`** — overlapping community detection via MIS
133
+ - **Input convention**: M is now N×P (remove any `.T` in existing code)
134
+ - **`betaMix()` returns a `dict`** instead of a tuple
135
+ - **`method="spearman"`** for rank-based correlation
136
+ - **`assessFit()`**, **`plotROC()`**, **`plotNonNullDensity()`** — new diagnostics
137
+ - **Vectorised `clusteringCoef()`** — approximately 10–80× faster
138
+ - Analytic Jacobian for MLE; `brentq` for eta estimation — matching R's solvers
139
+ - Python is approximately 3× faster than R on large benchmark datasets
140
+
141
+ ## See also
142
+
143
+ - R package: [github.com/haimbar/betaMix](https://github.com/haimbar/betaMix)
144
+ - LLM-friendly function reference: [llms.txt](llms.txt)
145
+ - Version history: [NEWS.md](NEWS.md)
146
+ - Additional examples and data: [code/](code/), [data/](data/), [doc/](doc/)
147
+
148
+ ## References
149
+
150
+ Bar, H. & Wells, M. T. (2023). On graphical models and convex geometry. *Computational Statistics & Data Analysis*, 187, 107800. https://doi.org/10.1016/j.csda.2023.107800
151
+
152
+ Frankl, P. & Maehara, H. (1990). Some geometric applications of the beta distribution. *Annals of the Institute of Statistical Mathematics*, 42(3), 463–474.
@@ -0,0 +1,3 @@
1
+ __version__ = "0.2.1" # single source of truth; setup.py reads this line
2
+
3
+ from .betaMixPy import *