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.
Files changed (26) hide show
  1. entropy_invariant-1.1.0/PKG-INFO +107 -0
  2. entropy_invariant-1.1.0/README.md +82 -0
  3. entropy_invariant-1.1.0/pyproject.toml +45 -0
  4. entropy_invariant-1.1.0/setup.cfg +4 -0
  5. entropy_invariant-1.1.0/src/entropy_invariant/__init__.py +81 -0
  6. entropy_invariant-1.1.0/src/entropy_invariant/_constants.py +16 -0
  7. entropy_invariant-1.1.0/src/entropy_invariant/_types.py +36 -0
  8. entropy_invariant-1.1.0/src/entropy_invariant/advanced.py +331 -0
  9. entropy_invariant-1.1.0/src/entropy_invariant/entropy.py +207 -0
  10. entropy_invariant-1.1.0/src/entropy_invariant/helpers/__init__.py +45 -0
  11. entropy_invariant-1.1.0/src/entropy_invariant/helpers/computation.py +167 -0
  12. entropy_invariant-1.1.0/src/entropy_invariant/helpers/data.py +108 -0
  13. entropy_invariant-1.1.0/src/entropy_invariant/helpers/utility.py +181 -0
  14. entropy_invariant-1.1.0/src/entropy_invariant/mutual_information.py +148 -0
  15. entropy_invariant-1.1.0/src/entropy_invariant/optimized.py +249 -0
  16. entropy_invariant-1.1.0/src/entropy_invariant/pid.py +241 -0
  17. entropy_invariant-1.1.0/src/entropy_invariant.egg-info/PKG-INFO +107 -0
  18. entropy_invariant-1.1.0/src/entropy_invariant.egg-info/SOURCES.txt +24 -0
  19. entropy_invariant-1.1.0/src/entropy_invariant.egg-info/dependency_links.txt +1 -0
  20. entropy_invariant-1.1.0/src/entropy_invariant.egg-info/requires.txt +6 -0
  21. entropy_invariant-1.1.0/src/entropy_invariant.egg-info/top_level.txt +1 -0
  22. entropy_invariant-1.1.0/tests/test_advanced.py +79 -0
  23. entropy_invariant-1.1.0/tests/test_entropy.py +127 -0
  24. entropy_invariant-1.1.0/tests/test_mutual_information.py +114 -0
  25. entropy_invariant-1.1.0/tests/test_optimized.py +94 -0
  26. 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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