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.
Files changed (36) hide show
  1. correlation_network-1.0.0/.gitignore +214 -0
  2. correlation_network-1.0.0/CHANGELOG.md +79 -0
  3. correlation_network-1.0.0/LICENSE +21 -0
  4. correlation_network-1.0.0/PKG-INFO +385 -0
  5. correlation_network-1.0.0/README.md +349 -0
  6. correlation_network-1.0.0/pyproject.toml +116 -0
  7. correlation_network-1.0.0/src/correlation_network/__init__.py +56 -0
  8. correlation_network-1.0.0/src/correlation_network/_graph_utils.py +65 -0
  9. correlation_network-1.0.0/src/correlation_network/_types.py +34 -0
  10. correlation_network-1.0.0/src/correlation_network/_validation.py +118 -0
  11. correlation_network-1.0.0/src/correlation_network/association/__init__.py +29 -0
  12. correlation_network-1.0.0/src/correlation_network/association/_auto.py +104 -0
  13. correlation_network-1.0.0/src/correlation_network/association/_base.py +25 -0
  14. correlation_network-1.0.0/src/correlation_network/association/_categorical.py +174 -0
  15. correlation_network-1.0.0/src/correlation_network/association/_correlation.py +65 -0
  16. correlation_network-1.0.0/src/correlation_network/association/_dtypes.py +47 -0
  17. correlation_network-1.0.0/src/correlation_network/association/_universal.py +162 -0
  18. correlation_network-1.0.0/src/correlation_network/centrality.py +124 -0
  19. correlation_network-1.0.0/src/correlation_network/exceptions.py +61 -0
  20. correlation_network-1.0.0/src/correlation_network/network.py +407 -0
  21. correlation_network-1.0.0/src/correlation_network/py.typed +0 -0
  22. correlation_network-1.0.0/src/correlation_network/visualization.py +394 -0
  23. correlation_network-1.0.0/tests/__init__.py +0 -0
  24. correlation_network-1.0.0/tests/conftest.py +98 -0
  25. correlation_network-1.0.0/tests/integration/__init__.py +0 -0
  26. correlation_network-1.0.0/tests/integration/test_end_to_end.py +132 -0
  27. correlation_network-1.0.0/tests/unit/__init__.py +0 -0
  28. correlation_network-1.0.0/tests/unit/test_association_auto.py +86 -0
  29. correlation_network-1.0.0/tests/unit/test_association_categorical.py +212 -0
  30. correlation_network-1.0.0/tests/unit/test_association_correlation.py +130 -0
  31. correlation_network-1.0.0/tests/unit/test_association_universal.py +137 -0
  32. correlation_network-1.0.0/tests/unit/test_centrality.py +177 -0
  33. correlation_network-1.0.0/tests/unit/test_exceptions.py +37 -0
  34. correlation_network-1.0.0/tests/unit/test_network.py +396 -0
  35. correlation_network-1.0.0/tests/unit/test_package.py +7 -0
  36. 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
+ [![PyPI](https://img.shields.io/pypi/v/correlation-network)](https://pypi.org/project/correlation-network/)
40
+ [![Python](https://img.shields.io/pypi/pyversions/correlation-network?logo=python&logoColor=white)](https://pypi.org/project/correlation-network/)
41
+ [![CI](https://github.com/skateddu/correlation-network/actions/workflows/ci.yml/badge.svg)](https://github.com/skateddu/correlation-network/actions/workflows/ci.yml)
42
+ ![License](https://img.shields.io/badge/license-MIT-green)
43
+ ![Code style: Ruff](https://img.shields.io/badge/code%20style-ruff-d4aa00)
44
+ ![Type: Typed](https://img.shields.io/badge/typing-typed-blue)
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*.