entropy-invariant 1.1.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.
- entropy_invariant-1.1.0/PKG-INFO +107 -0
- entropy_invariant-1.1.0/README.md +82 -0
- entropy_invariant-1.1.0/pyproject.toml +45 -0
- entropy_invariant-1.1.0/setup.cfg +4 -0
- entropy_invariant-1.1.0/src/entropy_invariant/__init__.py +81 -0
- entropy_invariant-1.1.0/src/entropy_invariant/_constants.py +16 -0
- entropy_invariant-1.1.0/src/entropy_invariant/_types.py +36 -0
- entropy_invariant-1.1.0/src/entropy_invariant/advanced.py +331 -0
- entropy_invariant-1.1.0/src/entropy_invariant/entropy.py +207 -0
- entropy_invariant-1.1.0/src/entropy_invariant/helpers/__init__.py +45 -0
- entropy_invariant-1.1.0/src/entropy_invariant/helpers/computation.py +167 -0
- entropy_invariant-1.1.0/src/entropy_invariant/helpers/data.py +108 -0
- entropy_invariant-1.1.0/src/entropy_invariant/helpers/utility.py +181 -0
- entropy_invariant-1.1.0/src/entropy_invariant/mutual_information.py +148 -0
- entropy_invariant-1.1.0/src/entropy_invariant/optimized.py +249 -0
- entropy_invariant-1.1.0/src/entropy_invariant/pid.py +241 -0
- entropy_invariant-1.1.0/src/entropy_invariant.egg-info/PKG-INFO +107 -0
- entropy_invariant-1.1.0/src/entropy_invariant.egg-info/SOURCES.txt +24 -0
- entropy_invariant-1.1.0/src/entropy_invariant.egg-info/dependency_links.txt +1 -0
- entropy_invariant-1.1.0/src/entropy_invariant.egg-info/requires.txt +6 -0
- entropy_invariant-1.1.0/src/entropy_invariant.egg-info/top_level.txt +1 -0
- entropy_invariant-1.1.0/tests/test_advanced.py +79 -0
- entropy_invariant-1.1.0/tests/test_entropy.py +127 -0
- entropy_invariant-1.1.0/tests/test_mutual_information.py +114 -0
- entropy_invariant-1.1.0/tests/test_optimized.py +94 -0
- entropy_invariant-1.1.0/tests/test_pid.py +47 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: entropy-invariant
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Invariant entropy estimation using nearest neighbor methods
|
|
5
|
+
Author: Felix Truong, Alexandre Giuliani
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: entropy,mutual information,information theory,k-nn,nearest neighbor
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: numpy>=1.20.0
|
|
21
|
+
Requires-Dist: scipy>=1.7.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
25
|
+
|
|
26
|
+
# entropy-invariant
|
|
27
|
+
|
|
28
|
+
A Python package implementing an improved nearest neighbor method for estimating differential entropy for continuous variables. This is a port of the Julia [EntropyInvariant](https://github.com/truongfelix/EntropyInvariant-private) package.
|
|
29
|
+
|
|
30
|
+
## Key Features
|
|
31
|
+
|
|
32
|
+
- **Invariant under change of variables**: Scale and translation invariant entropy estimation
|
|
33
|
+
- **Always positive**: Solves Edwin Thompson Jaynes' limiting density of discrete points problem
|
|
34
|
+
- **Multiple methods**: Supports invariant (default), k-NN, and histogram methods
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install entropy-invariant
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or install from source:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install -e .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import numpy as np
|
|
52
|
+
from entropy_invariant import entropy, mutual_information
|
|
53
|
+
|
|
54
|
+
# Generate random data
|
|
55
|
+
n = 1000
|
|
56
|
+
x = np.random.rand(n)
|
|
57
|
+
y = 2 * x + np.random.rand(n)
|
|
58
|
+
|
|
59
|
+
# Compute entropy (invariant method, default)
|
|
60
|
+
h = entropy(x)
|
|
61
|
+
print(f"Entropy: {h}")
|
|
62
|
+
|
|
63
|
+
# Entropy is invariant under scaling and translation
|
|
64
|
+
h_scaled = entropy(1e5 * x - 123.456)
|
|
65
|
+
print(f"Entropy (scaled): {h_scaled}") # Same value!
|
|
66
|
+
|
|
67
|
+
# Mutual information
|
|
68
|
+
mi = mutual_information(x, y)
|
|
69
|
+
print(f"Mutual Information: {mi}")
|
|
70
|
+
|
|
71
|
+
# Different methods
|
|
72
|
+
h_knn = entropy(x, method="knn")
|
|
73
|
+
h_hist = entropy(x, method="histogram", nbins=20)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Available Functions
|
|
77
|
+
|
|
78
|
+
### Core Entropy
|
|
79
|
+
- `entropy(X, method="inv", k=3, base=e, ...)` - Unified entropy interface
|
|
80
|
+
- `entropy_inv(X, ...)` - Invariant method (default)
|
|
81
|
+
- `entropy_knn(X, ...)` - k-NN method
|
|
82
|
+
- `entropy_hist(X, ...)` - Histogram method
|
|
83
|
+
|
|
84
|
+
### Information Theory
|
|
85
|
+
- `conditional_entropy(X, Y, ...)` - H(Y|X)
|
|
86
|
+
- `mutual_information(X, Y, ...)` - I(X;Y)
|
|
87
|
+
- `conditional_mutual_information(X, Y, Z, ...)` - I(X;Y|Z)
|
|
88
|
+
- `normalized_mutual_information(X, Y, ...)` - NMI
|
|
89
|
+
- `interaction_information(X, Y, Z, ...)` - Three-way interaction
|
|
90
|
+
|
|
91
|
+
### Partial Information Decomposition
|
|
92
|
+
- `redundancy(X, Y, Z, ...)` - Shared information
|
|
93
|
+
- `unique(X, Y, Z, ...)` - Unique information
|
|
94
|
+
- `synergy(X, Y, Z, ...)` - Synergistic information
|
|
95
|
+
|
|
96
|
+
### Optimized Matrix Functions
|
|
97
|
+
- `MI(X, ...)` - Pairwise mutual information matrix
|
|
98
|
+
- `CMI(X, Z, ...)` - Pairwise conditional MI matrix
|
|
99
|
+
|
|
100
|
+
## Authors
|
|
101
|
+
|
|
102
|
+
- Felix Truong
|
|
103
|
+
- Alexandre Giuliani
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
MIT
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# entropy-invariant
|
|
2
|
+
|
|
3
|
+
A Python package implementing an improved nearest neighbor method for estimating differential entropy for continuous variables. This is a port of the Julia [EntropyInvariant](https://github.com/truongfelix/EntropyInvariant-private) package.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
|
|
7
|
+
- **Invariant under change of variables**: Scale and translation invariant entropy estimation
|
|
8
|
+
- **Always positive**: Solves Edwin Thompson Jaynes' limiting density of discrete points problem
|
|
9
|
+
- **Multiple methods**: Supports invariant (default), k-NN, and histogram methods
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install entropy-invariant
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or install from source:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install -e .
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import numpy as np
|
|
27
|
+
from entropy_invariant import entropy, mutual_information
|
|
28
|
+
|
|
29
|
+
# Generate random data
|
|
30
|
+
n = 1000
|
|
31
|
+
x = np.random.rand(n)
|
|
32
|
+
y = 2 * x + np.random.rand(n)
|
|
33
|
+
|
|
34
|
+
# Compute entropy (invariant method, default)
|
|
35
|
+
h = entropy(x)
|
|
36
|
+
print(f"Entropy: {h}")
|
|
37
|
+
|
|
38
|
+
# Entropy is invariant under scaling and translation
|
|
39
|
+
h_scaled = entropy(1e5 * x - 123.456)
|
|
40
|
+
print(f"Entropy (scaled): {h_scaled}") # Same value!
|
|
41
|
+
|
|
42
|
+
# Mutual information
|
|
43
|
+
mi = mutual_information(x, y)
|
|
44
|
+
print(f"Mutual Information: {mi}")
|
|
45
|
+
|
|
46
|
+
# Different methods
|
|
47
|
+
h_knn = entropy(x, method="knn")
|
|
48
|
+
h_hist = entropy(x, method="histogram", nbins=20)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Available Functions
|
|
52
|
+
|
|
53
|
+
### Core Entropy
|
|
54
|
+
- `entropy(X, method="inv", k=3, base=e, ...)` - Unified entropy interface
|
|
55
|
+
- `entropy_inv(X, ...)` - Invariant method (default)
|
|
56
|
+
- `entropy_knn(X, ...)` - k-NN method
|
|
57
|
+
- `entropy_hist(X, ...)` - Histogram method
|
|
58
|
+
|
|
59
|
+
### Information Theory
|
|
60
|
+
- `conditional_entropy(X, Y, ...)` - H(Y|X)
|
|
61
|
+
- `mutual_information(X, Y, ...)` - I(X;Y)
|
|
62
|
+
- `conditional_mutual_information(X, Y, Z, ...)` - I(X;Y|Z)
|
|
63
|
+
- `normalized_mutual_information(X, Y, ...)` - NMI
|
|
64
|
+
- `interaction_information(X, Y, Z, ...)` - Three-way interaction
|
|
65
|
+
|
|
66
|
+
### Partial Information Decomposition
|
|
67
|
+
- `redundancy(X, Y, Z, ...)` - Shared information
|
|
68
|
+
- `unique(X, Y, Z, ...)` - Unique information
|
|
69
|
+
- `synergy(X, Y, Z, ...)` - Synergistic information
|
|
70
|
+
|
|
71
|
+
### Optimized Matrix Functions
|
|
72
|
+
- `MI(X, ...)` - Pairwise mutual information matrix
|
|
73
|
+
- `CMI(X, Z, ...)` - Pairwise conditional MI matrix
|
|
74
|
+
|
|
75
|
+
## Authors
|
|
76
|
+
|
|
77
|
+
- Felix Truong
|
|
78
|
+
- Alexandre Giuliani
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "entropy-invariant"
|
|
7
|
+
version = "1.1.0"
|
|
8
|
+
description = "Invariant entropy estimation using nearest neighbor methods"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
authors = [
|
|
12
|
+
{name = "Felix Truong"},
|
|
13
|
+
{name = "Alexandre Giuliani"}
|
|
14
|
+
]
|
|
15
|
+
requires-python = ">=3.8"
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.8",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
27
|
+
]
|
|
28
|
+
keywords = ["entropy", "mutual information", "information theory", "k-nn", "nearest neighbor"]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"numpy>=1.20.0",
|
|
31
|
+
"scipy>=1.7.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
dev = [
|
|
36
|
+
"pytest>=7.0",
|
|
37
|
+
"pytest-cov",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
testpaths = ["tests"]
|
|
45
|
+
python_files = ["test_*.py"]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EntropyInvariant - Invariant entropy estimation using nearest neighbor methods.
|
|
3
|
+
|
|
4
|
+
This package implements an improved nearest neighbor method for estimating
|
|
5
|
+
differential entropy for continuous variables, solving Edwin Thompson Jaynes'
|
|
6
|
+
limiting density of discrete points problem.
|
|
7
|
+
|
|
8
|
+
The main innovation is the invariant measure m(x) based on the median value
|
|
9
|
+
of nearest-neighbor distances, which ensures:
|
|
10
|
+
- Invariance under change of variables (scaling and translation)
|
|
11
|
+
- Always positive entropy values
|
|
12
|
+
|
|
13
|
+
Example usage:
|
|
14
|
+
>>> import numpy as np
|
|
15
|
+
>>> from entropy_invariant import entropy, mutual_information
|
|
16
|
+
>>>
|
|
17
|
+
>>> # Generate random data
|
|
18
|
+
>>> x = np.random.rand(1000)
|
|
19
|
+
>>> y = 2 * x + np.random.rand(1000)
|
|
20
|
+
>>>
|
|
21
|
+
>>> # Compute entropy (invariant method)
|
|
22
|
+
>>> h = entropy(x)
|
|
23
|
+
>>>
|
|
24
|
+
>>> # Entropy is scale-invariant
|
|
25
|
+
>>> h_scaled = entropy(1e5 * x - 123.456) # Same value!
|
|
26
|
+
>>>
|
|
27
|
+
>>> # Mutual information
|
|
28
|
+
>>> mi = mutual_information(x, y)
|
|
29
|
+
|
|
30
|
+
Authors: Felix Truong, Alexandre Giuliani
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from entropy_invariant.entropy import (
|
|
34
|
+
entropy,
|
|
35
|
+
entropy_hist,
|
|
36
|
+
entropy_inv,
|
|
37
|
+
entropy_knn,
|
|
38
|
+
)
|
|
39
|
+
from entropy_invariant.mutual_information import (
|
|
40
|
+
conditional_entropy,
|
|
41
|
+
mutual_information,
|
|
42
|
+
)
|
|
43
|
+
from entropy_invariant.advanced import (
|
|
44
|
+
conditional_mutual_information,
|
|
45
|
+
information_quality_ratio,
|
|
46
|
+
interaction_information,
|
|
47
|
+
normalized_mutual_information,
|
|
48
|
+
)
|
|
49
|
+
from entropy_invariant.pid import (
|
|
50
|
+
redundancy,
|
|
51
|
+
synergy,
|
|
52
|
+
unique,
|
|
53
|
+
)
|
|
54
|
+
from entropy_invariant.optimized import (
|
|
55
|
+
MI,
|
|
56
|
+
CMI,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
__version__ = "1.1.0"
|
|
60
|
+
__all__ = [
|
|
61
|
+
# Core entropy
|
|
62
|
+
"entropy",
|
|
63
|
+
"entropy_hist",
|
|
64
|
+
"entropy_knn",
|
|
65
|
+
"entropy_inv",
|
|
66
|
+
# Basic information theory
|
|
67
|
+
"conditional_entropy",
|
|
68
|
+
"mutual_information",
|
|
69
|
+
# Advanced information theory
|
|
70
|
+
"conditional_mutual_information",
|
|
71
|
+
"normalized_mutual_information",
|
|
72
|
+
"interaction_information",
|
|
73
|
+
"information_quality_ratio",
|
|
74
|
+
# Partial Information Decomposition
|
|
75
|
+
"redundancy",
|
|
76
|
+
"unique",
|
|
77
|
+
"synergy",
|
|
78
|
+
# Optimized matrix functions
|
|
79
|
+
"MI",
|
|
80
|
+
"CMI",
|
|
81
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Mathematical constants used throughout the package."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
# Euler's number (for base conversion and default base)
|
|
7
|
+
E = math.e
|
|
8
|
+
|
|
9
|
+
# Unit ball volumes for dimensions 1-3
|
|
10
|
+
# V_1 = 2.0 (line segment: [-1, 1])
|
|
11
|
+
# V_2 = pi (circle with radius 1)
|
|
12
|
+
# V_3 = 4*pi/3 (sphere with radius 1)
|
|
13
|
+
UNIT_BALL_VOLUMES = np.array([2.0, math.pi, 4.0 * math.pi / 3.0])
|
|
14
|
+
|
|
15
|
+
# Precomputed log of unit ball volumes for efficiency
|
|
16
|
+
LOG_UNIT_BALL_VOLUMES = np.log(UNIT_BALL_VOLUMES)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Type definitions for the entropy_invariant package."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import List
|
|
5
|
+
import numpy as np
|
|
6
|
+
from numpy.typing import NDArray
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class DataShape:
|
|
11
|
+
"""
|
|
12
|
+
Shape information for datasets.
|
|
13
|
+
|
|
14
|
+
In canonical format (columns as points):
|
|
15
|
+
- num_points: Number of data points (columns)
|
|
16
|
+
- num_dimensions: Number of dimensions (rows)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
num_points: int
|
|
20
|
+
num_dimensions: int
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class KNNResult:
|
|
25
|
+
"""
|
|
26
|
+
Results from k-nearest neighbor computations.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
indices: Indices of k-nearest neighbors for each point, shape (n_points, k+1)
|
|
30
|
+
all_distances: All k-nearest neighbor distances for each point, shape (n_points, k+1)
|
|
31
|
+
kth_distances: Distance to the k-th nearest neighbor for each point, shape (n_points,)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
indices: NDArray[np.intp]
|
|
35
|
+
all_distances: NDArray[np.float64]
|
|
36
|
+
kth_distances: NDArray[np.float64]
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""Advanced information theory functions."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
import numpy as np
|
|
5
|
+
from numpy.typing import NDArray
|
|
6
|
+
|
|
7
|
+
from entropy_invariant._constants import E
|
|
8
|
+
from entropy_invariant._types import DataShape
|
|
9
|
+
from entropy_invariant.helpers.data import (
|
|
10
|
+
ensure_columns_are_points,
|
|
11
|
+
ensure_2d,
|
|
12
|
+
get_shape,
|
|
13
|
+
validate_same_num_points,
|
|
14
|
+
validate_dimensions_equal_one,
|
|
15
|
+
)
|
|
16
|
+
from entropy_invariant.helpers.utility import log_computation_info
|
|
17
|
+
from entropy_invariant.entropy import entropy
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def conditional_mutual_information(
|
|
21
|
+
X: NDArray,
|
|
22
|
+
Y: NDArray,
|
|
23
|
+
Z: NDArray,
|
|
24
|
+
*,
|
|
25
|
+
method: str = "inv",
|
|
26
|
+
nbins: int = 10,
|
|
27
|
+
k: int = 3,
|
|
28
|
+
base: float = E,
|
|
29
|
+
verbose: bool = False,
|
|
30
|
+
degenerate: bool = False,
|
|
31
|
+
dim: int = 1,
|
|
32
|
+
) -> float:
|
|
33
|
+
"""
|
|
34
|
+
Compute conditional mutual information I(X;Y|Z) = H(X,Z) + H(Y,Z) - H(X,Y,Z) - H(Z).
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
X: First variable
|
|
38
|
+
Y: Second variable
|
|
39
|
+
Z: Conditioning variable
|
|
40
|
+
method: Entropy estimation method
|
|
41
|
+
nbins: Bins for histogram method
|
|
42
|
+
k: Neighbors for k-NN methods
|
|
43
|
+
base: Logarithmic base
|
|
44
|
+
verbose: Print info
|
|
45
|
+
degenerate: Handle degenerate cases
|
|
46
|
+
dim: Data layout
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Conditional mutual information I(X;Y|Z)
|
|
50
|
+
"""
|
|
51
|
+
mat_x = ensure_2d(X)
|
|
52
|
+
mat_y = ensure_2d(Y)
|
|
53
|
+
mat_z = ensure_2d(Z)
|
|
54
|
+
mat_x = ensure_columns_are_points(mat_x, dim)
|
|
55
|
+
mat_y = ensure_columns_are_points(mat_y, dim)
|
|
56
|
+
mat_z = ensure_columns_are_points(mat_z, dim)
|
|
57
|
+
|
|
58
|
+
shape_x = get_shape(mat_x)
|
|
59
|
+
shape_y = get_shape(mat_y)
|
|
60
|
+
shape_z = get_shape(mat_z)
|
|
61
|
+
|
|
62
|
+
validate_same_num_points([shape_x, shape_y, shape_z])
|
|
63
|
+
validate_dimensions_equal_one([shape_x, shape_y, shape_z])
|
|
64
|
+
|
|
65
|
+
if verbose:
|
|
66
|
+
total_dims = (
|
|
67
|
+
shape_x.num_dimensions + shape_y.num_dimensions + shape_z.num_dimensions
|
|
68
|
+
)
|
|
69
|
+
print(f"Number of points: {shape_x.num_points}")
|
|
70
|
+
print(f"Dimensions: {total_dims}")
|
|
71
|
+
print(f"Base: {base}")
|
|
72
|
+
|
|
73
|
+
# I(X;Y|Z) = H(X,Z) + H(Y,Z) - H(X,Y,Z) - H(Z)
|
|
74
|
+
xz_mat = np.vstack([mat_x, mat_z])
|
|
75
|
+
yz_mat = np.vstack([mat_y, mat_z])
|
|
76
|
+
xyz_mat = np.vstack([mat_x, mat_y, mat_z])
|
|
77
|
+
|
|
78
|
+
ent_z = entropy(
|
|
79
|
+
mat_z, method=method, nbins=nbins, k=k, base=base, degenerate=degenerate, dim=2
|
|
80
|
+
)
|
|
81
|
+
ent_xz = entropy(
|
|
82
|
+
xz_mat, method=method, nbins=nbins, k=k, base=base, degenerate=degenerate, dim=2
|
|
83
|
+
)
|
|
84
|
+
ent_yz = entropy(
|
|
85
|
+
yz_mat, method=method, nbins=nbins, k=k, base=base, degenerate=degenerate, dim=2
|
|
86
|
+
)
|
|
87
|
+
ent_xyz = entropy(
|
|
88
|
+
xyz_mat,
|
|
89
|
+
method=method,
|
|
90
|
+
nbins=nbins,
|
|
91
|
+
k=k,
|
|
92
|
+
base=base,
|
|
93
|
+
degenerate=degenerate,
|
|
94
|
+
dim=2,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return ent_xz + ent_yz - ent_xyz - ent_z
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def normalized_mutual_information(
|
|
101
|
+
X: NDArray,
|
|
102
|
+
Y: NDArray,
|
|
103
|
+
*,
|
|
104
|
+
method: str = "inv",
|
|
105
|
+
nbins: int = 10,
|
|
106
|
+
k: int = 3,
|
|
107
|
+
base: float = E,
|
|
108
|
+
verbose: bool = False,
|
|
109
|
+
degenerate: bool = False,
|
|
110
|
+
dim: int = 1,
|
|
111
|
+
) -> float:
|
|
112
|
+
"""
|
|
113
|
+
Compute normalized mutual information NMI = I(X;Y) / ((H(X) + H(Y)) / 2).
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
X: First variable
|
|
117
|
+
Y: Second variable
|
|
118
|
+
method: Entropy estimation method
|
|
119
|
+
nbins: Bins for histogram method
|
|
120
|
+
k: Neighbors for k-NN methods
|
|
121
|
+
base: Logarithmic base
|
|
122
|
+
verbose: Print info
|
|
123
|
+
degenerate: Handle degenerate cases
|
|
124
|
+
dim: Data layout
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Normalized mutual information in [0, 1]
|
|
128
|
+
"""
|
|
129
|
+
mat_x = ensure_2d(X)
|
|
130
|
+
mat_y = ensure_2d(Y)
|
|
131
|
+
mat_x = ensure_columns_are_points(mat_x, dim)
|
|
132
|
+
mat_y = ensure_columns_are_points(mat_y, dim)
|
|
133
|
+
|
|
134
|
+
shape_x = get_shape(mat_x)
|
|
135
|
+
shape_y = get_shape(mat_y)
|
|
136
|
+
|
|
137
|
+
validate_same_num_points([shape_x, shape_y])
|
|
138
|
+
validate_dimensions_equal_one([shape_x, shape_y])
|
|
139
|
+
|
|
140
|
+
if verbose:
|
|
141
|
+
total_dims = shape_x.num_dimensions + shape_y.num_dimensions
|
|
142
|
+
print(f"Number of points: {shape_x.num_points}")
|
|
143
|
+
print(f"Dimensions: {total_dims}")
|
|
144
|
+
print(f"Base: {base}")
|
|
145
|
+
|
|
146
|
+
# Compute entropies
|
|
147
|
+
joint_mat = np.vstack([mat_x, mat_y])
|
|
148
|
+
ent_x = entropy(
|
|
149
|
+
mat_x, method=method, nbins=nbins, k=k, base=base, degenerate=degenerate, dim=2
|
|
150
|
+
)
|
|
151
|
+
ent_y = entropy(
|
|
152
|
+
mat_y, method=method, nbins=nbins, k=k, base=base, degenerate=degenerate, dim=2
|
|
153
|
+
)
|
|
154
|
+
ent_joint = entropy(
|
|
155
|
+
joint_mat,
|
|
156
|
+
method=method,
|
|
157
|
+
nbins=nbins,
|
|
158
|
+
k=k,
|
|
159
|
+
base=base,
|
|
160
|
+
degenerate=degenerate,
|
|
161
|
+
dim=2,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# NMI = I(X;Y) / avg(H(X), H(Y))
|
|
165
|
+
# I(X;Y) = H(X) + H(Y) - H(X,Y), ensure non-negative
|
|
166
|
+
mi = max(0.0, ent_x + ent_y - ent_joint)
|
|
167
|
+
avg_entropy = (ent_x + ent_y) / 2.0
|
|
168
|
+
|
|
169
|
+
return mi / avg_entropy
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def interaction_information(
|
|
173
|
+
X: NDArray,
|
|
174
|
+
Y: NDArray,
|
|
175
|
+
Z: NDArray,
|
|
176
|
+
*,
|
|
177
|
+
method: str = "inv",
|
|
178
|
+
nbins: int = 10,
|
|
179
|
+
k: int = 3,
|
|
180
|
+
base: float = E,
|
|
181
|
+
verbose: bool = False,
|
|
182
|
+
degenerate: bool = False,
|
|
183
|
+
dim: int = 1,
|
|
184
|
+
) -> float:
|
|
185
|
+
"""
|
|
186
|
+
Compute interaction information (three-way interaction).
|
|
187
|
+
|
|
188
|
+
II(X;Y;Z) = H(X) + H(Y) + H(Z) - H(X,Y) - H(X,Z) - H(Y,Z) + H(X,Y,Z)
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
X: First variable
|
|
192
|
+
Y: Second variable
|
|
193
|
+
Z: Third variable
|
|
194
|
+
method: Entropy estimation method
|
|
195
|
+
nbins: Bins for histogram method
|
|
196
|
+
k: Neighbors for k-NN methods
|
|
197
|
+
base: Logarithmic base
|
|
198
|
+
verbose: Print info
|
|
199
|
+
degenerate: Handle degenerate cases
|
|
200
|
+
dim: Data layout
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Interaction information II(X;Y;Z)
|
|
204
|
+
"""
|
|
205
|
+
mat_x = ensure_2d(X)
|
|
206
|
+
mat_y = ensure_2d(Y)
|
|
207
|
+
mat_z = ensure_2d(Z)
|
|
208
|
+
mat_x = ensure_columns_are_points(mat_x, dim)
|
|
209
|
+
mat_y = ensure_columns_are_points(mat_y, dim)
|
|
210
|
+
mat_z = ensure_columns_are_points(mat_z, dim)
|
|
211
|
+
|
|
212
|
+
shape_x = get_shape(mat_x)
|
|
213
|
+
shape_y = get_shape(mat_y)
|
|
214
|
+
shape_z = get_shape(mat_z)
|
|
215
|
+
|
|
216
|
+
validate_same_num_points([shape_x, shape_y, shape_z])
|
|
217
|
+
validate_dimensions_equal_one([shape_x, shape_y, shape_z])
|
|
218
|
+
|
|
219
|
+
if verbose:
|
|
220
|
+
total_dims = (
|
|
221
|
+
shape_x.num_dimensions + shape_y.num_dimensions + shape_z.num_dimensions
|
|
222
|
+
)
|
|
223
|
+
print(f"Number of points: {shape_x.num_points}")
|
|
224
|
+
print(f"Dimensions: {total_dims}")
|
|
225
|
+
print(f"Base: {base}")
|
|
226
|
+
|
|
227
|
+
# Compute all required entropies
|
|
228
|
+
xy_mat = np.vstack([mat_x, mat_y])
|
|
229
|
+
xz_mat = np.vstack([mat_x, mat_z])
|
|
230
|
+
yz_mat = np.vstack([mat_y, mat_z])
|
|
231
|
+
xyz_mat = np.vstack([mat_x, mat_y, mat_z])
|
|
232
|
+
|
|
233
|
+
ent_x = entropy(
|
|
234
|
+
mat_x, method=method, nbins=nbins, k=k, base=base, degenerate=degenerate, dim=2
|
|
235
|
+
)
|
|
236
|
+
ent_y = entropy(
|
|
237
|
+
mat_y, method=method, nbins=nbins, k=k, base=base, degenerate=degenerate, dim=2
|
|
238
|
+
)
|
|
239
|
+
ent_z = entropy(
|
|
240
|
+
mat_z, method=method, nbins=nbins, k=k, base=base, degenerate=degenerate, dim=2
|
|
241
|
+
)
|
|
242
|
+
ent_xy = entropy(
|
|
243
|
+
xy_mat, method=method, nbins=nbins, k=k, base=base, degenerate=degenerate, dim=2
|
|
244
|
+
)
|
|
245
|
+
ent_xz = entropy(
|
|
246
|
+
xz_mat, method=method, nbins=nbins, k=k, base=base, degenerate=degenerate, dim=2
|
|
247
|
+
)
|
|
248
|
+
ent_yz = entropy(
|
|
249
|
+
yz_mat, method=method, nbins=nbins, k=k, base=base, degenerate=degenerate, dim=2
|
|
250
|
+
)
|
|
251
|
+
ent_xyz = entropy(
|
|
252
|
+
xyz_mat,
|
|
253
|
+
method=method,
|
|
254
|
+
nbins=nbins,
|
|
255
|
+
k=k,
|
|
256
|
+
base=base,
|
|
257
|
+
degenerate=degenerate,
|
|
258
|
+
dim=2,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# II = H(X) + H(Y) + H(Z) - H(X,Y) - H(X,Z) - H(Y,Z) + H(X,Y,Z)
|
|
262
|
+
return ent_x + ent_y + ent_z - ent_xy - ent_xz - ent_yz + ent_xyz
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def information_quality_ratio(
|
|
266
|
+
X: NDArray,
|
|
267
|
+
Y: NDArray,
|
|
268
|
+
*,
|
|
269
|
+
method: str = "inv",
|
|
270
|
+
nbins: int = 10,
|
|
271
|
+
k: int = 3,
|
|
272
|
+
base: float = E,
|
|
273
|
+
verbose: bool = False,
|
|
274
|
+
degenerate: bool = False,
|
|
275
|
+
dim: int = 1,
|
|
276
|
+
) -> float:
|
|
277
|
+
"""
|
|
278
|
+
Compute information quality ratio IQR = I(X;Y) / H(X).
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
X: First variable
|
|
282
|
+
Y: Second variable
|
|
283
|
+
method: Entropy estimation method
|
|
284
|
+
nbins: Bins for histogram method
|
|
285
|
+
k: Neighbors for k-NN methods
|
|
286
|
+
base: Logarithmic base
|
|
287
|
+
verbose: Print info
|
|
288
|
+
degenerate: Handle degenerate cases
|
|
289
|
+
dim: Data layout
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
Information quality ratio
|
|
293
|
+
"""
|
|
294
|
+
mat_x = ensure_2d(X)
|
|
295
|
+
mat_y = ensure_2d(Y)
|
|
296
|
+
mat_x = ensure_columns_are_points(mat_x, dim)
|
|
297
|
+
mat_y = ensure_columns_are_points(mat_y, dim)
|
|
298
|
+
|
|
299
|
+
shape_x = get_shape(mat_x)
|
|
300
|
+
shape_y = get_shape(mat_y)
|
|
301
|
+
|
|
302
|
+
validate_same_num_points([shape_x, shape_y])
|
|
303
|
+
validate_dimensions_equal_one([shape_x, shape_y])
|
|
304
|
+
|
|
305
|
+
if verbose:
|
|
306
|
+
total_dims = shape_x.num_dimensions + shape_y.num_dimensions
|
|
307
|
+
print(f"Number of points: {shape_x.num_points}")
|
|
308
|
+
print(f"Dimensions: {total_dims}")
|
|
309
|
+
print(f"Base: {base}")
|
|
310
|
+
|
|
311
|
+
# Compute entropies
|
|
312
|
+
joint_mat = np.vstack([mat_x, mat_y])
|
|
313
|
+
ent_x = entropy(
|
|
314
|
+
mat_x, method=method, nbins=nbins, k=k, base=base, degenerate=degenerate, dim=2
|
|
315
|
+
)
|
|
316
|
+
ent_y = entropy(
|
|
317
|
+
mat_y, method=method, nbins=nbins, k=k, base=base, degenerate=degenerate, dim=2
|
|
318
|
+
)
|
|
319
|
+
ent_joint = entropy(
|
|
320
|
+
joint_mat,
|
|
321
|
+
method=method,
|
|
322
|
+
nbins=nbins,
|
|
323
|
+
k=k,
|
|
324
|
+
base=base,
|
|
325
|
+
degenerate=degenerate,
|
|
326
|
+
dim=2,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# IQR = I(X;Y) / H(X)
|
|
330
|
+
mi = ent_x + ent_y - ent_joint
|
|
331
|
+
return mi / ent_x
|