correlation-network 1.0.0__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.
- correlation_network-1.0.0/.gitignore +214 -0
- correlation_network-1.0.0/CHANGELOG.md +79 -0
- correlation_network-1.0.0/LICENSE +21 -0
- correlation_network-1.0.0/PKG-INFO +385 -0
- correlation_network-1.0.0/README.md +349 -0
- correlation_network-1.0.0/pyproject.toml +116 -0
- correlation_network-1.0.0/src/correlation_network/__init__.py +56 -0
- correlation_network-1.0.0/src/correlation_network/_graph_utils.py +65 -0
- correlation_network-1.0.0/src/correlation_network/_types.py +34 -0
- correlation_network-1.0.0/src/correlation_network/_validation.py +118 -0
- correlation_network-1.0.0/src/correlation_network/association/__init__.py +29 -0
- correlation_network-1.0.0/src/correlation_network/association/_auto.py +104 -0
- correlation_network-1.0.0/src/correlation_network/association/_base.py +25 -0
- correlation_network-1.0.0/src/correlation_network/association/_categorical.py +174 -0
- correlation_network-1.0.0/src/correlation_network/association/_correlation.py +65 -0
- correlation_network-1.0.0/src/correlation_network/association/_dtypes.py +47 -0
- correlation_network-1.0.0/src/correlation_network/association/_universal.py +162 -0
- correlation_network-1.0.0/src/correlation_network/centrality.py +124 -0
- correlation_network-1.0.0/src/correlation_network/exceptions.py +61 -0
- correlation_network-1.0.0/src/correlation_network/network.py +407 -0
- correlation_network-1.0.0/src/correlation_network/py.typed +0 -0
- correlation_network-1.0.0/src/correlation_network/visualization.py +394 -0
- correlation_network-1.0.0/tests/__init__.py +0 -0
- correlation_network-1.0.0/tests/conftest.py +98 -0
- correlation_network-1.0.0/tests/integration/__init__.py +0 -0
- correlation_network-1.0.0/tests/integration/test_end_to_end.py +132 -0
- correlation_network-1.0.0/tests/unit/__init__.py +0 -0
- correlation_network-1.0.0/tests/unit/test_association_auto.py +86 -0
- correlation_network-1.0.0/tests/unit/test_association_categorical.py +212 -0
- correlation_network-1.0.0/tests/unit/test_association_correlation.py +130 -0
- correlation_network-1.0.0/tests/unit/test_association_universal.py +137 -0
- correlation_network-1.0.0/tests/unit/test_centrality.py +177 -0
- correlation_network-1.0.0/tests/unit/test_exceptions.py +37 -0
- correlation_network-1.0.0/tests/unit/test_network.py +396 -0
- correlation_network-1.0.0/tests/unit/test_package.py +7 -0
- correlation_network-1.0.0/tests/unit/test_visualization.py +198 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
#poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
#pdm.lock
|
|
116
|
+
#pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
#pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# SageMath parsed files
|
|
135
|
+
*.sage.py
|
|
136
|
+
|
|
137
|
+
# MCP configuration (generated from template)
|
|
138
|
+
.mcp.json
|
|
139
|
+
|
|
140
|
+
# Environments
|
|
141
|
+
.env
|
|
142
|
+
.envrc
|
|
143
|
+
.venv
|
|
144
|
+
.venv-*/
|
|
145
|
+
env/
|
|
146
|
+
venv/
|
|
147
|
+
ENV/
|
|
148
|
+
env.bak/
|
|
149
|
+
venv.bak/
|
|
150
|
+
|
|
151
|
+
# Spyder project settings
|
|
152
|
+
.spyderproject
|
|
153
|
+
.spyproject
|
|
154
|
+
|
|
155
|
+
# Rope project settings
|
|
156
|
+
.ropeproject
|
|
157
|
+
|
|
158
|
+
# mkdocs documentation
|
|
159
|
+
/site
|
|
160
|
+
|
|
161
|
+
# mypy
|
|
162
|
+
.mypy_cache/
|
|
163
|
+
.dmypy.json
|
|
164
|
+
dmypy.json
|
|
165
|
+
|
|
166
|
+
# Pyre type checker
|
|
167
|
+
.pyre/
|
|
168
|
+
|
|
169
|
+
# pytype static type analyzer
|
|
170
|
+
.pytype/
|
|
171
|
+
|
|
172
|
+
# Cython debug symbols
|
|
173
|
+
cython_debug/
|
|
174
|
+
|
|
175
|
+
# PyCharm
|
|
176
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
177
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
178
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
179
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
180
|
+
#.idea/
|
|
181
|
+
|
|
182
|
+
# Abstra
|
|
183
|
+
# Abstra is an AI-powered process automation framework.
|
|
184
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
185
|
+
# Learn more at https://abstra.io/docs
|
|
186
|
+
.abstra/
|
|
187
|
+
|
|
188
|
+
# Visual Studio Code
|
|
189
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
190
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
191
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
192
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
193
|
+
# .vscode/
|
|
194
|
+
|
|
195
|
+
# Ruff stuff:
|
|
196
|
+
.ruff_cache/
|
|
197
|
+
|
|
198
|
+
# PyPI configuration file
|
|
199
|
+
.pypirc
|
|
200
|
+
|
|
201
|
+
# Cursor
|
|
202
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
203
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
204
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
205
|
+
.cursorignore
|
|
206
|
+
.cursorindexingignore
|
|
207
|
+
|
|
208
|
+
# Marimo
|
|
209
|
+
marimo/_static/
|
|
210
|
+
marimo/_lsp/
|
|
211
|
+
__marimo__/
|
|
212
|
+
|
|
213
|
+
# Claude Code
|
|
214
|
+
.claude/
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2026-10-24
|
|
9
|
+
|
|
10
|
+
Initial release.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
#### Core API
|
|
15
|
+
|
|
16
|
+
- `CorrelationNetwork` with a scikit-learn-style `fit` / `transform` /
|
|
17
|
+
`fit_transform` pipeline
|
|
18
|
+
- `from_matrix()` class method for pre-computed association matrices, from
|
|
19
|
+
pandas or Polars
|
|
20
|
+
- Strategy pattern for association measures via the `AssociationStrategy`
|
|
21
|
+
protocol; any object implementing `compute(df) -> pd.DataFrame` can be passed
|
|
22
|
+
through the `strategy` parameter
|
|
23
|
+
- Polars DataFrame and LazyFrame input, converted internally (via the `polars`
|
|
24
|
+
extra)
|
|
25
|
+
- `correlation_network.__version__` and inline type information (PEP 561,
|
|
26
|
+
`py.typed`)
|
|
27
|
+
|
|
28
|
+
#### Association measures
|
|
29
|
+
|
|
30
|
+
- Correlation strategies: Pearson, Spearman, Kendall
|
|
31
|
+
- Categorical strategies: Cramer's V (bias-corrected, no continuity
|
|
32
|
+
correction on 2x2 tables), Correlation Ratio (eta)
|
|
33
|
+
- Universal strategies: Mutual Information (normalized, with equal-width or
|
|
34
|
+
`binning="quantile"` bins), PhiK (via the `phik` extra)
|
|
35
|
+
- `AutoAssociationStrategy`, dispatching by column type: Spearman for
|
|
36
|
+
numeric pairs, Cramer's V for categorical pairs, eta for mixed pairs; bool
|
|
37
|
+
and pandas `string` columns count as categorical
|
|
38
|
+
- Pairs are evaluated on their pairwise-complete rows, so missing values in one
|
|
39
|
+
column do not distort an unrelated pair
|
|
40
|
+
- Inclusive thresholding: an association equal to `threshold` creates an edge
|
|
41
|
+
|
|
42
|
+
#### Analysis and rendering
|
|
43
|
+
|
|
44
|
+
- `CentralityAnalyzer` with degree, strength, betweenness, eigenvector and
|
|
45
|
+
closeness, individually or as a combined `summary()` table
|
|
46
|
+
- Path-based metrics convert association strength to distance
|
|
47
|
+
(`distance = 1 - |weight|`), so shortest paths follow strongly associated
|
|
48
|
+
pairs; `strength()` and `eigenvector()` use absolute weights
|
|
49
|
+
- `NetworkVisualizer` with 5 layouts (kamada_kawai, spring, circular, shell,
|
|
50
|
+
spectral), a continuous edge colormap centred on 0 with colorbar, a
|
|
51
|
+
two-colour positive/negative fallback, community colouring via greedy
|
|
52
|
+
modularity, centrality-scaled node sizes, per-edge alpha, and a `save()`
|
|
53
|
+
helper accepting `str` or `os.PathLike` paths
|
|
54
|
+
|
|
55
|
+
#### Validation
|
|
56
|
+
|
|
57
|
+
- Datetime and high-cardinality column exclusion during preprocessing; columns
|
|
58
|
+
a strategy cannot use are excluded too, all recorded in `excluded_columns_`
|
|
59
|
+
and reported through warnings that point at the caller's code
|
|
60
|
+
- `CorrelationNetworkError`, the base class of every library error
|
|
61
|
+
- `NotFittedError` with a `check_is_fitted` guard
|
|
62
|
+
- `InsufficientColumnsError` when a method receives no columns of the type it
|
|
63
|
+
needs, carrying the `method`, the `required` types, and the columns `found`
|
|
64
|
+
- `NoCompleteObservationsError` when a column pair shares no rows where both
|
|
65
|
+
values are present, carrying the offending `columns`
|
|
66
|
+
- `ValueError` for an unknown `layout` or `centrality_metric`
|
|
67
|
+
|
|
68
|
+
#### Project
|
|
69
|
+
|
|
70
|
+
- Unit and integration test suites
|
|
71
|
+
- GitHub Actions CI: lint, format, strict mypy, a test matrix over Python
|
|
72
|
+
3.10–3.13 gated at 80% coverage, and a run against the lowest supported
|
|
73
|
+
dependency versions
|
|
74
|
+
- Tag-triggered release workflow publishing to TestPyPI and PyPI through
|
|
75
|
+
Trusted Publishing
|
|
76
|
+
- `CONTRIBUTING.md` with development setup, checks, conventions and the
|
|
77
|
+
release procedure
|
|
78
|
+
|
|
79
|
+
[1.0.0]: https://github.com/skateddu/correlation-network/releases/tag/v1.0.0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andrea Cadeddu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: correlation-network
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Build and analyze association networks from pandas DataFrames: correlations, Cramer's V, mutual information and PhiK, with centrality analysis and visualization
|
|
5
|
+
Project-URL: Homepage, https://github.com/skateddu/correlation-network
|
|
6
|
+
Project-URL: Repository, https://github.com/skateddu/correlation-network
|
|
7
|
+
Project-URL: Issues, https://github.com/skateddu/correlation-network/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/skateddu/correlation-network/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Andrea Cadeddu <cadeddu7@gmail.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: association,centrality,correlation,cramers-v,graph,mutual-information,network,networkx,pandas
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: matplotlib>=3.7
|
|
26
|
+
Requires-Dist: networkx>=3.0
|
|
27
|
+
Requires-Dist: numpy>=1.24
|
|
28
|
+
Requires-Dist: pandas>=2.0
|
|
29
|
+
Requires-Dist: scipy>=1.11
|
|
30
|
+
Provides-Extra: phik
|
|
31
|
+
Requires-Dist: phik>=0.12.2; extra == 'phik'
|
|
32
|
+
Provides-Extra: polars
|
|
33
|
+
Requires-Dist: polars>=0.20; extra == 'polars'
|
|
34
|
+
Requires-Dist: pyarrow>=14.0; extra == 'polars'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# Correlation Network
|
|
38
|
+
|
|
39
|
+
[](https://pypi.org/project/correlation-network/)
|
|
40
|
+
[](https://pypi.org/project/correlation-network/)
|
|
41
|
+
[](https://github.com/skateddu/correlation-network/actions/workflows/ci.yml)
|
|
42
|
+

|
|
43
|
+

|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
Python library that builds association networks from pandas DataFrames. Computes pairwise association measures (correlations, Cramer's V, mutual information, etc.), applies threshold filtering, and produces NetworkX graphs with centrality analysis and visualization.
|
|
47
|
+
|
|
48
|
+
## Features
|
|
49
|
+
|
|
50
|
+
- **Association methods**: Pearson, Spearman, Kendall, Cramer's V, Correlation Ratio, Mutual Information, PhiK
|
|
51
|
+
- **Auto-detection**: selects the right measure based on column types (numeric, categorical, mixed)
|
|
52
|
+
- **API**: scikit-learn-style `fit()` / `transform()` / `fit_transform()` pipeline
|
|
53
|
+
- **Centrality**: degree, strength, betweenness, eigenvector, closeness — individual or summary table
|
|
54
|
+
- **Visualization**: continuous colormap, community detection coloring, centrality-scaled nodes, per-edge alpha
|
|
55
|
+
- **Extensible**: any object implementing the `AssociationStrategy` protocol can be plugged in
|
|
56
|
+
|
|
57
|
+
## Dependencies
|
|
58
|
+
|
|
59
|
+
| Package | Description | Link |
|
|
60
|
+
|---------|-------------|------|
|
|
61
|
+
| [NetworkX](https://networkx.org/) | Graph data structures, algorithms, and layout | [Docs](https://networkx.org/documentation/stable/) |
|
|
62
|
+
| [pandas](https://pandas.pydata.org/) | DataFrame input and association matrix storage | [Docs](https://pandas.pydata.org/docs/) |
|
|
63
|
+
| [NumPy](https://numpy.org/) | Array operations for matrix computation | [Docs](https://numpy.org/doc/stable/) |
|
|
64
|
+
| [SciPy](https://scipy.org/) | Chi-squared test (Cramer's V), entropy (Mutual Information) | [Docs](https://docs.scipy.org/doc/scipy/) |
|
|
65
|
+
| [Matplotlib](https://matplotlib.org/) | Graph rendering, colormaps, and figure export | [Docs](https://matplotlib.org/stable/contents.html) |
|
|
66
|
+
|
|
67
|
+
Optional extras:
|
|
68
|
+
|
|
69
|
+
| Extra | Description | Link |
|
|
70
|
+
|-------|-------------|------|
|
|
71
|
+
| `phik` | PhiK correlation (works on any column type pair) | [Docs](https://phik.readthedocs.io/) |
|
|
72
|
+
| `polars` | Accept Polars DataFrames as input (auto-converted to pandas) | [Docs](https://docs.pola.rs/) |
|
|
73
|
+
|
|
74
|
+
## Installation
|
|
75
|
+
|
|
76
|
+
Requires Python >= 3.10.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install correlation-network
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
With optional extras:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install "correlation-network[phik]" # PhiK correlation support
|
|
86
|
+
pip install "correlation-network[polars]" # Polars DataFrame support
|
|
87
|
+
pip install "correlation-network[phik,polars]"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
With [uv](https://docs.astral.sh/uv/): `uv add correlation-network` (extras:
|
|
91
|
+
`uv add "correlation-network[phik]"`).
|
|
92
|
+
|
|
93
|
+
## Quick Start
|
|
94
|
+
|
|
95
|
+
### Python API
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
import pandas as pd
|
|
99
|
+
from correlation_network import CorrelationNetwork, CentralityAnalyzer, NetworkVisualizer
|
|
100
|
+
|
|
101
|
+
# Build the network (default: method="pearson", threshold=0.5)
|
|
102
|
+
net = CorrelationNetwork(method="spearman", threshold=0.3)
|
|
103
|
+
graph = net.fit_transform(df)
|
|
104
|
+
|
|
105
|
+
# Analyze centrality
|
|
106
|
+
analyzer = CentralityAnalyzer(graph)
|
|
107
|
+
rankings = analyzer.summary()
|
|
108
|
+
print(rankings)
|
|
109
|
+
|
|
110
|
+
# Visualize
|
|
111
|
+
viz = NetworkVisualizer(graph)
|
|
112
|
+
fig = viz.plot(
|
|
113
|
+
centrality_metric="degree",
|
|
114
|
+
community_colors=True,
|
|
115
|
+
title="Association Network",
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Save directly
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
viz.save("network.png", dpi=150, community_colors=True, title="My Network")
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### From a pre-computed matrix
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
# Use a correlation matrix you already have
|
|
129
|
+
net = CorrelationNetwork.from_matrix(my_matrix, threshold=0.5)
|
|
130
|
+
graph = net.transform()
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### With Polars DataFrames
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
import polars as pl
|
|
137
|
+
|
|
138
|
+
df_polars = pl.read_csv("data.csv")
|
|
139
|
+
graph = CorrelationNetwork(method="pearson", threshold=0.5).fit_transform(df_polars)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Constructor Parameters
|
|
143
|
+
|
|
144
|
+
| Parameter | Default | Description |
|
|
145
|
+
|-----------|---------|-------------|
|
|
146
|
+
| `method` | `"pearson"` | Association method (see table below) |
|
|
147
|
+
| `threshold` | `0.5` | Minimum `\|weight\|` to keep an edge (inclusive) |
|
|
148
|
+
| `strategy` | `None` | Custom `AssociationStrategy` instance (overrides `method`) |
|
|
149
|
+
| `cardinality_threshold` | `0.95` | Exclude index-like columns with `nunique/nrows >= threshold` (see below) |
|
|
150
|
+
|
|
151
|
+
## Preprocessing
|
|
152
|
+
|
|
153
|
+
Before computing associations, `fit()` automatically excludes:
|
|
154
|
+
|
|
155
|
+
- **Datetime columns** (not suitable for correlation)
|
|
156
|
+
- **High-cardinality columns** where `nunique / nrows >= cardinality_threshold` (e.g. IDs, UUIDs).
|
|
157
|
+
Integer columns are excluded only when their values also form a consecutive
|
|
158
|
+
sequence (row numbers, auto-increment keys, even if shuffled): on small frames
|
|
159
|
+
genuine integer measurements such as age or income are near-unique too
|
|
160
|
+
|
|
161
|
+
After computing the matrix, columns the strategy cannot use (e.g. strings with
|
|
162
|
+
`method="pearson"`) are reported with a warning instead of silently disappearing
|
|
163
|
+
from the graph.
|
|
164
|
+
|
|
165
|
+
All these columns are stored in `net.excluded_columns_` after fitting:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
net = CorrelationNetwork(method="auto", threshold=0.3)
|
|
169
|
+
net.fit(df)
|
|
170
|
+
print(net.excluded_columns_) # ['timestamp', 'row_id']
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Association Methods
|
|
174
|
+
|
|
175
|
+
| Method | Strategy Class | Column Types | Output Range |
|
|
176
|
+
|--------|---------------|-------------|--------------|
|
|
177
|
+
| `pearson` | `PearsonStrategy` | numeric — numeric | [-1, 1] |
|
|
178
|
+
| `spearman` | `SpearmanStrategy` | numeric — numeric | [-1, 1] |
|
|
179
|
+
| `kendall` | `KendallStrategy` | numeric — numeric | [-1, 1] |
|
|
180
|
+
| `cramers_v` | `CramersVStrategy` | categorical — categorical | [0, 1] |
|
|
181
|
+
| `correlation_ratio` | `CorrelationRatioStrategy` | categorical — numeric | [0, 1] |
|
|
182
|
+
| `mutual_information` | `MutualInformationStrategy` | any | [0, 1] |
|
|
183
|
+
| `phik` | `PhiKStrategy` | any | [0, 1] |
|
|
184
|
+
| `auto` | `AutoAssociationStrategy` | auto-detects types | [0, 1] |
|
|
185
|
+
|
|
186
|
+
Each method requires columns of the type it operates on. Passing a DataFrame
|
|
187
|
+
that cannot satisfy it raises `InsufficientColumnsError` (attributes `method`,
|
|
188
|
+
`required`, `found`) rather than silently returning an empty matrix.
|
|
189
|
+
|
|
190
|
+
### Missing values
|
|
191
|
+
|
|
192
|
+
Every method evaluates a column pair on the rows where **both** values are
|
|
193
|
+
present (pairwise-complete observations), the same convention `pandas.corr()`
|
|
194
|
+
uses. Rows missing elsewhere in the DataFrame do not affect an unrelated pair.
|
|
195
|
+
|
|
196
|
+
If a pair shares no complete rows at all, its association is undefined and
|
|
197
|
+
`NoCompleteObservationsError` (attribute `columns`) is raised rather than a
|
|
198
|
+
misleading zero.
|
|
199
|
+
|
|
200
|
+
> **Mutual information and bin count.** `MutualInformationStrategy` discretizes
|
|
201
|
+
> numeric columns into `n_bins` (default 10) before computing MI. With few
|
|
202
|
+
> samples per bin the estimate is biased upward: on 200 rows, two *independent*
|
|
203
|
+
> columns score ≈0.10 at `n_bins=10` but ≈0.50 at `n_bins=50`. Raise `n_bins`
|
|
204
|
+
> only when you have the rows to support it, and set `threshold` accordingly.
|
|
205
|
+
>
|
|
206
|
+
> Bins are equal-width by default, so a single extreme outlier can squeeze
|
|
207
|
+
> almost every row into one bin and hide a real relationship. Pass
|
|
208
|
+
> `binning="quantile"` for equal-frequency bins on skewed or outlier-prone data:
|
|
209
|
+
>
|
|
210
|
+
> ```python
|
|
211
|
+
> from correlation_network import MutualInformationStrategy
|
|
212
|
+
>
|
|
213
|
+
> net = CorrelationNetwork(strategy=MutualInformationStrategy(binning="quantile"))
|
|
214
|
+
> ```
|
|
215
|
+
|
|
216
|
+
### Error handling
|
|
217
|
+
|
|
218
|
+
All library errors derive from `CorrelationNetworkError`, so a single `except`
|
|
219
|
+
catches any of them. Each one also keeps its built-in base (`ValueError`, and
|
|
220
|
+
`AttributeError` for `NotFittedError`), so existing handlers keep working.
|
|
221
|
+
|
|
222
|
+
### Auto mode
|
|
223
|
+
|
|
224
|
+
When `method="auto"`, the library detects column dtypes and dispatches:
|
|
225
|
+
|
|
226
|
+
| Pair type | Default strategy |
|
|
227
|
+
|-----------|-----------------|
|
|
228
|
+
| numeric — numeric | Spearman |
|
|
229
|
+
| categorical — categorical | Cramer's V |
|
|
230
|
+
| mixed | Correlation Ratio |
|
|
231
|
+
|
|
232
|
+
Bool and string columns count as categorical.
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
net = CorrelationNetwork(method="auto", threshold=0.3)
|
|
236
|
+
graph = net.fit_transform(df)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
> **Auto mode discards the sign.** Numeric correlations are reduced to their
|
|
240
|
+
> absolute value so all three measures share a [0, 1] scale (Cramer's V and eta
|
|
241
|
+
> are non-negative by definition). A Spearman of `-0.98` becomes `0.98`, and the
|
|
242
|
+
> positive/negative edge colouring in `NetworkVisualizer` has no effect on such a
|
|
243
|
+
> graph. Use an explicit correlation method when the direction matters.
|
|
244
|
+
|
|
245
|
+
### Custom strategies
|
|
246
|
+
|
|
247
|
+
Any object implementing the `AssociationStrategy` protocol (`compute(df) -> pd.DataFrame`) can be used:
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
net = CorrelationNetwork(strategy=my_custom_strategy, threshold=0.3)
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Centrality Analysis
|
|
254
|
+
|
|
255
|
+
| Method | Description |
|
|
256
|
+
|--------|-------------|
|
|
257
|
+
| `degree()` | Normalized degree: connected neighbors as fraction of (n-1) |
|
|
258
|
+
| `strength()` | Sum of absolute edge weights |
|
|
259
|
+
| `betweenness()` | Frequency on shortest paths (weights converted to distances) |
|
|
260
|
+
| `eigenvector()` | Influence based on connections to high-scoring nodes (absolute weights) |
|
|
261
|
+
| `closeness()` | Inverse average distance to all other nodes (weights converted to distances) |
|
|
262
|
+
| `summary()` | DataFrame with all metrics combined |
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
analyzer = CentralityAnalyzer(graph)
|
|
266
|
+
|
|
267
|
+
analyzer.degree() # pd.Series, sorted descending
|
|
268
|
+
analyzer.summary() # pd.DataFrame with all 5 metrics
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Association strength vs. graph distance
|
|
272
|
+
|
|
273
|
+
`betweenness()` and `closeness()` are path-based, and NetworkX reads the edge
|
|
274
|
+
attribute it is given as a **cost**. Association values run the other way — a
|
|
275
|
+
weight near 1 means two nodes are *close*, not far apart. Both metrics therefore
|
|
276
|
+
convert weights with `distance = 1 - |weight|` before running Dijkstra, so
|
|
277
|
+
shortest paths follow strongly associated pairs.
|
|
278
|
+
|
|
279
|
+
`strength()` is a magnitude sum rather than a path metric, so it keeps using the
|
|
280
|
+
raw absolute weights. `eigenvector()` also runs on absolute weights: signed
|
|
281
|
+
weights can yield negative scores, which have no meaning as centrality.
|
|
282
|
+
|
|
283
|
+
The default `kamada_kawai` layout applies the same `1 - |weight|` conversion,
|
|
284
|
+
since it reads weights as target distances: strongly associated nodes are drawn
|
|
285
|
+
close together.
|
|
286
|
+
|
|
287
|
+
## Visualization
|
|
288
|
+
|
|
289
|
+
| Parameter | Default | Description |
|
|
290
|
+
|-----------|---------|-------------|
|
|
291
|
+
| `layout` | `kamada_kawai` | Layout algorithm (`spring`, `circular`, `shell`, `spectral`) |
|
|
292
|
+
| `centrality_metric` | `None` | Scale node size by centrality (`degree`, `strength`, etc.) |
|
|
293
|
+
| `figsize` | `(10, 8)` | Figure size in inches |
|
|
294
|
+
| `dpi` | `100` | Figure resolution |
|
|
295
|
+
| `node_color` | `tab:blue` | Node color (ignored when `community_colors=True`) |
|
|
296
|
+
| `community_colors` | `False` | Color nodes by greedy modularity community |
|
|
297
|
+
| `edge_cmap` | `RdYlGn` | Diverging colormap for edges, centred at 0 (`None` for two-color mode) |
|
|
298
|
+
| `positive_edge_color` | `darkgreen` | Positive edge color (two-color mode only) |
|
|
299
|
+
| `negative_edge_color` | `tab:red` | Negative edge color (two-color mode only) |
|
|
300
|
+
| `font_size` | `10` | Font size for node labels |
|
|
301
|
+
| `edge_label_font_size` | `8` | Font size for edge weight labels |
|
|
302
|
+
| `show_edge_labels` | `True` | Display edge weight labels |
|
|
303
|
+
| `title` | `None` | Plot title |
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
viz = NetworkVisualizer(graph)
|
|
307
|
+
|
|
308
|
+
fig = viz.plot(
|
|
309
|
+
layout="kamada_kawai",
|
|
310
|
+
centrality_metric="degree",
|
|
311
|
+
community_colors=True,
|
|
312
|
+
edge_cmap="RdYlGn",
|
|
313
|
+
title="My Network",
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
viz.save("network.png", dpi=150, community_colors=True)
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## Development
|
|
320
|
+
|
|
321
|
+
Setup, test commands and contribution guidelines are in [CONTRIBUTING.md](https://github.com/skateddu/correlation-network/blob/main/CONTRIBUTING.md).
|
|
322
|
+
|
|
323
|
+
### Architecture
|
|
324
|
+
|
|
325
|
+
The library follows a pipeline pattern: DataFrame in, graph out.
|
|
326
|
+
|
|
327
|
+
```mermaid
|
|
328
|
+
graph LR
|
|
329
|
+
A[DataFrame] -->|fit| B[Association Matrix]
|
|
330
|
+
B -->|transform| C[nx.Graph]
|
|
331
|
+
C --> D[CentralityAnalyzer]
|
|
332
|
+
C --> E[NetworkVisualizer]
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
| Concept | Implementation | Module |
|
|
336
|
+
|---------|---------------|--------|
|
|
337
|
+
| Pluggable metrics | `AssociationStrategy` Protocol | `association/_base.py` |
|
|
338
|
+
| Metric dispatch | `_STRATEGY_REGISTRY` dict | `network.py` |
|
|
339
|
+
| Fit/transform pipeline | scikit-learn-style API | `network.py` |
|
|
340
|
+
| Graph centrality | NetworkX algorithms wrapper | `centrality.py` |
|
|
341
|
+
| Negative weight handling | `abs_weight_graph()` helper | `_graph_utils.py` |
|
|
342
|
+
| Association → distance | `distance_graph()` helper | `_graph_utils.py` |
|
|
343
|
+
| Fitted state guard | `check_is_fitted()` function | `_validation.py` |
|
|
344
|
+
| Rendering | Matplotlib + NetworkX drawing | `visualization.py` |
|
|
345
|
+
|
|
346
|
+
### Project Structure
|
|
347
|
+
|
|
348
|
+
```
|
|
349
|
+
correlation-network/
|
|
350
|
+
├── src/correlation_network/
|
|
351
|
+
│ ├── __init__.py # Public API exports
|
|
352
|
+
│ ├── network.py # CorrelationNetwork (main entry point)
|
|
353
|
+
│ ├── centrality.py # CentralityAnalyzer (5 metrics + summary)
|
|
354
|
+
│ ├── visualization.py # NetworkVisualizer (5 layouts, colormap, community colors)
|
|
355
|
+
│ ├── exceptions.py # CorrelationNetworkError base + NotFittedError, InsufficientColumnsError, NoCompleteObservationsError
|
|
356
|
+
│ ├── py.typed # PEP 561 marker: ships inline type information
|
|
357
|
+
│ ├── _types.py # Literal type aliases (AssociationMethod, BinningMethod, CentralityMetric, LayoutAlgorithm, CorrelationMethod)
|
|
358
|
+
│ ├── _validation.py # Fitted guard, missing-data checks, option validation
|
|
359
|
+
│ ├── _graph_utils.py # Shared graph utilities (abs_weight_graph, distance_graph)
|
|
360
|
+
│ └── association/
|
|
361
|
+
│ ├── __init__.py # Re-exports all strategies
|
|
362
|
+
│ ├── _base.py # AssociationStrategy (Protocol)
|
|
363
|
+
│ ├── _dtypes.py # Numeric vs categorical column detection
|
|
364
|
+
│ ├── _correlation.py # Pearson, Spearman, Kendall
|
|
365
|
+
│ ├── _categorical.py # Cramer's V, Correlation Ratio (eta)
|
|
366
|
+
│ ├── _universal.py # PhiK, Mutual Information
|
|
367
|
+
│ └── _auto.py # Auto-detection + dispatch
|
|
368
|
+
├── tests/
|
|
369
|
+
│ ├── conftest.py # Shared fixtures (numeric, categorical, mixed DataFrames)
|
|
370
|
+
│ ├── unit/ # One test file per module
|
|
371
|
+
│ └── integration/ # End-to-end pipeline tests
|
|
372
|
+
├── .github/workflows/ci.yml # Lint, format, mypy, test matrix (3.10–3.13), lowest dependency versions
|
|
373
|
+
├── pyproject.toml # Metadata, dependencies, ruff, mypy and pytest config
|
|
374
|
+
├── uv.lock # Locked dependency versions
|
|
375
|
+
├── CHANGELOG.md # Keep a Changelog format
|
|
376
|
+
├── CONTRIBUTING.md # Development setup and workflow
|
|
377
|
+
└── LICENSE # MIT
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## References
|
|
381
|
+
|
|
382
|
+
- Newman, M. E. J. (2010). *Networks: An Introduction*. Oxford University Press.
|
|
383
|
+
- Cramér, H. (1946). *Mathematical Methods of Statistics*. Princeton University Press.
|
|
384
|
+
- Bergsma, W. (2018). [A new correlation coefficient](https://arxiv.org/abs/1712.05289). *arXiv:1712.05289* — PhiK foundation.
|
|
385
|
+
- Reshef, D. et al. (2011). [Detecting Novel Associations in Large Data Sets](https://doi.org/10.1126/science.1205438). *Science*.
|