518 lines
13 KiB
Markdown
518 lines
13 KiB
Markdown
|
|
# Information Theory API
|
|||
|
|
|
|||
|
|
## Overview
|
|||
|
|
|
|||
|
|
The Information Theory module provides implementations of fundamental information measures: Shannon Entropy and Mutual Information. These metrics are essential for feature selection, dependency detection, causality testing, and understanding information content in data.
|
|||
|
|
|
|||
|
|
## Functions
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
from optimizr import shannon_entropy, mutual_information
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Function: `shannon_entropy`
|
|||
|
|
|
|||
|
|
Computes the Shannon entropy of a random variable using histogram-based probability estimation.
|
|||
|
|
|
|||
|
|
### Signature
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
shannon_entropy(
|
|||
|
|
x: np.ndarray,
|
|||
|
|
n_bins: int = 10,
|
|||
|
|
) -> float
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Parameters
|
|||
|
|
|
|||
|
|
- **`x`** (np.ndarray): Sample values from the random variable (1D array).
|
|||
|
|
|
|||
|
|
- **`n_bins`** (int, optional): Number of bins for histogram-based probability estimation. Default is 10.
|
|||
|
|
|
|||
|
|
### Returns
|
|||
|
|
|
|||
|
|
- **`entropy`** (float): Shannon entropy in nats (natural logarithm). Multiply by 1/ln(2) ≈ 1.4427 to convert to bits.
|
|||
|
|
|
|||
|
|
### Formula
|
|||
|
|
|
|||
|
|
$$H(X) = -\sum_{i} p(x_i) \log p(x_i)$$
|
|||
|
|
|
|||
|
|
where $p(x_i)$ is estimated from the histogram.
|
|||
|
|
|
|||
|
|
### Example
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import numpy as np
|
|||
|
|
from optimizr import shannon_entropy
|
|||
|
|
|
|||
|
|
# Uniform distribution has high entropy
|
|||
|
|
x_uniform = np.random.uniform(0, 1, 10000)
|
|||
|
|
h_uniform = shannon_entropy(x_uniform, n_bins=20)
|
|||
|
|
print(f"Uniform entropy: {h_uniform:.4f} nats")
|
|||
|
|
print(f"Uniform entropy: {h_uniform/np.log(2):.4f} bits")
|
|||
|
|
|
|||
|
|
# Peaked distribution has low entropy
|
|||
|
|
x_peaked = np.random.normal(0, 0.1, 10000)
|
|||
|
|
h_peaked = shannon_entropy(x_peaked, n_bins=20)
|
|||
|
|
print(f"Peaked entropy: {h_peaked:.4f} nats")
|
|||
|
|
|
|||
|
|
# Constant has zero entropy
|
|||
|
|
x_constant = np.ones(1000)
|
|||
|
|
h_constant = shannon_entropy(x_constant, n_bins=20)
|
|||
|
|
print(f"Constant entropy: {h_constant:.4f} nats")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Function: `mutual_information`
|
|||
|
|
|
|||
|
|
Computes the mutual information between two random variables.
|
|||
|
|
|
|||
|
|
### Signature
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
mutual_information(
|
|||
|
|
x: np.ndarray,
|
|||
|
|
y: np.ndarray,
|
|||
|
|
n_bins: int = 10,
|
|||
|
|
) -> float
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Parameters
|
|||
|
|
|
|||
|
|
- **`x`** (np.ndarray): Sample values from the first random variable (1D array).
|
|||
|
|
|
|||
|
|
- **`y`** (np.ndarray): Sample values from the second random variable (1D array). Must be the same length as `x`.
|
|||
|
|
|
|||
|
|
- **`n_bins`** (int, optional): Number of bins for histogram estimation. Default is 10.
|
|||
|
|
|
|||
|
|
### Returns
|
|||
|
|
|
|||
|
|
- **`mi`** (float): Mutual information in nats (natural logarithm). Multiply by 1/ln(2) to convert to bits.
|
|||
|
|
|
|||
|
|
### Formula
|
|||
|
|
|
|||
|
|
$$I(X;Y) = H(X) + H(Y) - H(X,Y)$$
|
|||
|
|
|
|||
|
|
or equivalently:
|
|||
|
|
|
|||
|
|
$$I(X;Y) = \sum_{x,y} p(x,y) \log \frac{p(x,y)}{p(x)p(y)}$$
|
|||
|
|
|
|||
|
|
### Example
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import numpy as np
|
|||
|
|
from optimizr import mutual_information
|
|||
|
|
|
|||
|
|
# Generate correlated variables
|
|||
|
|
np.random.seed(42)
|
|||
|
|
x = np.random.randn(10000)
|
|||
|
|
y = 2 * x + np.random.randn(10000) * 0.5 # Strongly correlated
|
|||
|
|
|
|||
|
|
mi = mutual_information(x, y, n_bins=20)
|
|||
|
|
print(f"Mutual Information: {mi:.4f} nats")
|
|||
|
|
|
|||
|
|
# Independent variables
|
|||
|
|
x_ind = np.random.randn(10000)
|
|||
|
|
y_ind = np.random.randn(10000)
|
|||
|
|
|
|||
|
|
mi_ind = mutual_information(x_ind, y_ind, n_bins=20)
|
|||
|
|
print(f"MI (independent): {mi_ind:.4f} nats (should be near 0)")
|
|||
|
|
|
|||
|
|
# Perfectly correlated
|
|||
|
|
y_perfect = x.copy()
|
|||
|
|
mi_perfect = mutual_information(x, y_perfect, n_bins=20)
|
|||
|
|
print(f"MI (perfect): {mi_perfect:.4f} nats")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Advanced Examples
|
|||
|
|
|
|||
|
|
### 1. Feature Selection
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import numpy as np
|
|||
|
|
import pandas as pd
|
|||
|
|
from optimizr import mutual_information
|
|||
|
|
|
|||
|
|
# Generate dataset
|
|||
|
|
np.random.seed(42)
|
|||
|
|
n_samples = 1000
|
|||
|
|
|
|||
|
|
# Features
|
|||
|
|
x1 = np.random.randn(n_samples)
|
|||
|
|
x2 = np.random.randn(n_samples)
|
|||
|
|
x3 = np.random.randn(n_samples)
|
|||
|
|
x4 = np.random.randn(n_samples)
|
|||
|
|
x5 = np.random.randn(n_samples)
|
|||
|
|
|
|||
|
|
# Target: depends on x1 and x3, not others
|
|||
|
|
y = 2*x1 + 3*x3 + np.random.randn(n_samples)*0.5
|
|||
|
|
|
|||
|
|
# Calculate MI with target
|
|||
|
|
features = {'x1': x1, 'x2': x2, 'x3': x3, 'x4': x4, 'x5': x5}
|
|||
|
|
mi_scores = {}
|
|||
|
|
|
|||
|
|
for name, feature in features.items():
|
|||
|
|
mi = mutual_information(feature, y, n_bins=15)
|
|||
|
|
mi_scores[name] = mi
|
|||
|
|
|
|||
|
|
# Rank features
|
|||
|
|
ranked = sorted(mi_scores.items(), key=lambda x: x[1], reverse=True)
|
|||
|
|
|
|||
|
|
print("Feature Importance (by MI):")
|
|||
|
|
for name, score in ranked:
|
|||
|
|
print(f" {name}: {score:.4f}")
|
|||
|
|
|
|||
|
|
# Select top features
|
|||
|
|
threshold = 0.5
|
|||
|
|
selected = [name for name, score in ranked if score > threshold]
|
|||
|
|
print(f"\nSelected features: {selected}")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. Time Series Dependency
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import numpy as np
|
|||
|
|
from optimizr import mutual_information
|
|||
|
|
import matplotlib.pyplot as plt
|
|||
|
|
|
|||
|
|
# Generate time series
|
|||
|
|
np.random.seed(42)
|
|||
|
|
n = 1000
|
|||
|
|
x = np.random.randn(n)
|
|||
|
|
|
|||
|
|
# Calculate MI at different lags
|
|||
|
|
max_lag = 50
|
|||
|
|
mi_lags = []
|
|||
|
|
|
|||
|
|
for lag in range(1, max_lag + 1):
|
|||
|
|
x_lagged = x[:-lag]
|
|||
|
|
x_current = x[lag:]
|
|||
|
|
mi = mutual_information(x_lagged, x_current, n_bins=15)
|
|||
|
|
mi_lags.append(mi)
|
|||
|
|
|
|||
|
|
# Plot
|
|||
|
|
plt.figure(figsize=(10, 6))
|
|||
|
|
plt.plot(range(1, max_lag + 1), mi_lags, 'b-', linewidth=2)
|
|||
|
|
plt.xlabel('Lag')
|
|||
|
|
plt.ylabel('Mutual Information (nats)')
|
|||
|
|
plt.title('Time Series Autocorrelation via MI')
|
|||
|
|
plt.grid(True, alpha=0.3)
|
|||
|
|
plt.show()
|
|||
|
|
|
|||
|
|
# For an AR(1) process
|
|||
|
|
ar_coef = 0.8
|
|||
|
|
y = np.zeros(n)
|
|||
|
|
for t in range(1, n):
|
|||
|
|
y[t] = ar_coef * y[t-1] + np.random.randn()
|
|||
|
|
|
|||
|
|
mi_ar = mutual_information(y[:-1], y[1:], n_bins=20)
|
|||
|
|
print(f"AR(1) process MI(lag=1): {mi_ar:.4f}")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. Nonlinear Dependency Detection
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import numpy as np
|
|||
|
|
from optimizr import mutual_information
|
|||
|
|
|
|||
|
|
np.random.seed(42)
|
|||
|
|
n = 5000
|
|||
|
|
|
|||
|
|
# Linear relationship
|
|||
|
|
x_lin = np.random.randn(n)
|
|||
|
|
y_lin = 2*x_lin + np.random.randn(n)*0.3
|
|||
|
|
|
|||
|
|
# Nonlinear relationship
|
|||
|
|
x_nonlin = np.random.uniform(-3, 3, n)
|
|||
|
|
y_nonlin = x_nonlin**2 + np.random.randn(n)*0.5
|
|||
|
|
|
|||
|
|
# No relationship
|
|||
|
|
x_indep = np.random.randn(n)
|
|||
|
|
y_indep = np.random.randn(n)
|
|||
|
|
|
|||
|
|
# Calculate MI
|
|||
|
|
mi_lin = mutual_information(x_lin, y_lin, n_bins=20)
|
|||
|
|
mi_nonlin = mutual_information(x_nonlin, y_nonlin, n_bins=20)
|
|||
|
|
mi_indep = mutual_information(x_indep, y_indep, n_bins=20)
|
|||
|
|
|
|||
|
|
# Compare with Pearson correlation
|
|||
|
|
corr_lin = np.corrcoef(x_lin, y_lin)[0, 1]
|
|||
|
|
corr_nonlin = np.corrcoef(x_nonlin, y_nonlin)[0, 1]
|
|||
|
|
corr_indep = np.corrcoef(x_indep, y_indep)[0, 1]
|
|||
|
|
|
|||
|
|
print("Linear Relationship:")
|
|||
|
|
print(f" MI: {mi_lin:.4f}, Correlation: {corr_lin:.4f}")
|
|||
|
|
|
|||
|
|
print("\nNonlinear Relationship:")
|
|||
|
|
print(f" MI: {mi_nonlin:.4f}, Correlation: {corr_nonlin:.4f}")
|
|||
|
|
print(" (MI detects dependency, correlation doesn't)")
|
|||
|
|
|
|||
|
|
print("\nIndependent:")
|
|||
|
|
print(f" MI: {mi_indep:.4f}, Correlation: {corr_indep:.4f}")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. Image Processing
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import numpy as np
|
|||
|
|
from optimizr import shannon_entropy, mutual_information
|
|||
|
|
from skimage import data, filters
|
|||
|
|
import matplotlib.pyplot as plt
|
|||
|
|
|
|||
|
|
# Load image
|
|||
|
|
image = data.camera() # Grayscale image
|
|||
|
|
h, w = image.shape
|
|||
|
|
|
|||
|
|
# Calculate entropy
|
|||
|
|
entropy_original = shannon_entropy(image.flatten(), n_bins=50)
|
|||
|
|
print(f"Original image entropy: {entropy_original:.4f} nats")
|
|||
|
|
|
|||
|
|
# Apply Gaussian blur
|
|||
|
|
blurred = filters.gaussian(image, sigma=3)
|
|||
|
|
entropy_blurred = shannon_entropy((blurred * 255).astype(int).flatten(),
|
|||
|
|
n_bins=50)
|
|||
|
|
print(f"Blurred image entropy: {entropy_blurred:.4f} nats")
|
|||
|
|
|
|||
|
|
# Add noise
|
|||
|
|
noisy = image + np.random.randn(h, w) * 20
|
|||
|
|
entropy_noisy = shannon_entropy(noisy.flatten(), n_bins=50)
|
|||
|
|
print(f"Noisy image entropy: {entropy_noisy:.4f} nats")
|
|||
|
|
|
|||
|
|
# Mutual information between patches
|
|||
|
|
patch1 = image[100:200, 100:200].flatten()
|
|||
|
|
patch2 = image[200:300, 200:300].flatten()[:len(patch1)]
|
|||
|
|
|
|||
|
|
mi_patches = mutual_information(patch1, patch2, n_bins=30)
|
|||
|
|
print(f"MI between patches: {mi_patches:.4f}")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5. Model Comparison
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import numpy as np
|
|||
|
|
from optimizr import mutual_information
|
|||
|
|
from sklearn.linear_model import LinearRegression
|
|||
|
|
from sklearn.ensemble import RandomForestRegressor
|
|||
|
|
from sklearn.neural_network import MLPRegressor
|
|||
|
|
|
|||
|
|
# Generate data
|
|||
|
|
np.random.seed(42)
|
|||
|
|
n = 1000
|
|||
|
|
X = np.random.randn(n, 5)
|
|||
|
|
y_true = 2*X[:, 0] + 3*X[:, 1]**2 - X[:, 2]*X[:, 3]
|
|||
|
|
y = y_true + np.random.randn(n)*0.5
|
|||
|
|
|
|||
|
|
# Train models
|
|||
|
|
models = {
|
|||
|
|
'Linear': LinearRegression(),
|
|||
|
|
'Random Forest': RandomForestRegressor(n_estimators=100, random_state=42),
|
|||
|
|
'Neural Net': MLPRegressor(hidden_layer_sizes=(50, 50), random_state=42)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for name, model in models.items():
|
|||
|
|
model.fit(X, y)
|
|||
|
|
y_pred = model.predict(X)
|
|||
|
|
|
|||
|
|
# Mutual information between predictions and true values
|
|||
|
|
mi = mutual_information(y_pred, y, n_bins=20)
|
|||
|
|
|
|||
|
|
# Also calculate R²
|
|||
|
|
from sklearn.metrics import r2_score
|
|||
|
|
r2 = r2_score(y, y_pred)
|
|||
|
|
|
|||
|
|
print(f"{name}:")
|
|||
|
|
print(f" MI(pred, true): {mi:.4f}")
|
|||
|
|
print(f" R²: {r2:.4f}")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6. Causality Testing
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import numpy as np
|
|||
|
|
from optimizr import mutual_information
|
|||
|
|
|
|||
|
|
# Test if X causes Y
|
|||
|
|
np.random.seed(42)
|
|||
|
|
n = 1000
|
|||
|
|
|
|||
|
|
# X causes Y
|
|||
|
|
x = np.random.randn(n)
|
|||
|
|
y = np.zeros(n)
|
|||
|
|
for t in range(1, n):
|
|||
|
|
y[t] = 0.5*x[t-1] + 0.3*y[t-1] + np.random.randn()*0.1
|
|||
|
|
|
|||
|
|
# MI(X_t-1, Y_t) should be high
|
|||
|
|
mi_xy = mutual_information(x[:-1], y[1:], n_bins=20)
|
|||
|
|
print(f"MI(X_t-1, Y_t): {mi_xy:.4f}")
|
|||
|
|
|
|||
|
|
# MI(Y_t-1, X_t) should be low (Y doesn't cause X)
|
|||
|
|
mi_yx = mutual_information(y[:-1], x[1:], n_bins=20)
|
|||
|
|
print(f"MI(Y_t-1, X_t): {mi_yx:.4f}")
|
|||
|
|
|
|||
|
|
if mi_xy > 2 * mi_yx:
|
|||
|
|
print("Evidence suggests X → Y causality")
|
|||
|
|
else:
|
|||
|
|
print("No clear causal direction")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Choosing the Number of Bins
|
|||
|
|
|
|||
|
|
The choice of `n_bins` affects the bias-variance tradeoff:
|
|||
|
|
|
|||
|
|
### Too Few Bins
|
|||
|
|
- High bias, low variance
|
|||
|
|
- Underestimates entropy/MI
|
|||
|
|
- Use when: limited data, smooth distributions
|
|||
|
|
|
|||
|
|
### Too Many Bins
|
|||
|
|
- Low bias, high variance
|
|||
|
|
- Overestimates due to noise
|
|||
|
|
- Use when: large datasets, complex distributions
|
|||
|
|
|
|||
|
|
### Rule of Thumb
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import numpy as np
|
|||
|
|
|
|||
|
|
def optimal_bins(n_samples):
|
|||
|
|
"""Sturges' rule and alternatives"""
|
|||
|
|
|
|||
|
|
# Sturges' rule (for normal distributions)
|
|||
|
|
sturges = int(np.ceil(np.log2(n_samples) + 1))
|
|||
|
|
|
|||
|
|
# Square root rule
|
|||
|
|
sqrt_rule = int(np.ceil(np.sqrt(n_samples)))
|
|||
|
|
|
|||
|
|
# Rice rule
|
|||
|
|
rice = int(np.ceil(2 * n_samples**(1/3)))
|
|||
|
|
|
|||
|
|
# Scott's rule (data-dependent)
|
|||
|
|
# Would need the actual data
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
'sturges': sturges,
|
|||
|
|
'sqrt': sqrt_rule,
|
|||
|
|
'rice': rice
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Example
|
|||
|
|
n = 10000
|
|||
|
|
bins = optimal_bins(n)
|
|||
|
|
print(f"For {n} samples:")
|
|||
|
|
print(f" Sturges: {bins['sturges']} bins")
|
|||
|
|
print(f" Sqrt: {bins['sqrt']} bins")
|
|||
|
|
print(f" Rice: {bins['rice']} bins")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Sensitivity Analysis
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import numpy as np
|
|||
|
|
import matplotlib.pyplot as plt
|
|||
|
|
from optimizr import mutual_information
|
|||
|
|
|
|||
|
|
# Generate correlated data
|
|||
|
|
np.random.seed(42)
|
|||
|
|
n = 5000
|
|||
|
|
x = np.random.randn(n)
|
|||
|
|
y = 2*x + np.random.randn(n)
|
|||
|
|
|
|||
|
|
# Test different bin counts
|
|||
|
|
bin_range = range(5, 51, 5)
|
|||
|
|
mi_values = []
|
|||
|
|
|
|||
|
|
for n_bins in bin_range:
|
|||
|
|
mi = mutual_information(x, y, n_bins=n_bins)
|
|||
|
|
mi_values.append(mi)
|
|||
|
|
|
|||
|
|
# Plot
|
|||
|
|
plt.figure(figsize=(10, 6))
|
|||
|
|
plt.plot(bin_range, mi_values, 'bo-', linewidth=2, markersize=8)
|
|||
|
|
plt.xlabel('Number of Bins')
|
|||
|
|
plt.ylabel('Mutual Information (nats)')
|
|||
|
|
plt.title('MI Sensitivity to Bin Count')
|
|||
|
|
plt.grid(True, alpha=0.3)
|
|||
|
|
plt.show()
|
|||
|
|
|
|||
|
|
# Recommend stable range
|
|||
|
|
mi_std = np.std(mi_values)
|
|||
|
|
stable_range = [b for b, m in zip(bin_range, mi_values)
|
|||
|
|
if abs(m - np.mean(mi_values)) < mi_std]
|
|||
|
|
print(f"Stable bin range: {min(stable_range)}-{max(stable_range)}")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Performance Notes
|
|||
|
|
|
|||
|
|
- **Rust Backend**: 20-50x faster than pure Python/NumPy implementations.
|
|||
|
|
|
|||
|
|
- **Python Fallback**: Uses NumPy's `histogram` and `histogram2d` functions.
|
|||
|
|
|
|||
|
|
- **Memory**: O(n + n_bins²) for MI, O(n + n_bins) for entropy.
|
|||
|
|
|
|||
|
|
- **Time Complexity**: O(n) for histogram construction, O(n_bins²) for MI computation.
|
|||
|
|
|
|||
|
|
## Properties and Interpretations
|
|||
|
|
|
|||
|
|
### Shannon Entropy
|
|||
|
|
|
|||
|
|
- **Range**: [0, ∞)
|
|||
|
|
- **Zero**: Only for deterministic (constant) variables
|
|||
|
|
- **Maximum**: For continuous uniform distribution: log(range)
|
|||
|
|
- **Units**: nats (natural log) or bits (log₂)
|
|||
|
|
|
|||
|
|
### Mutual Information
|
|||
|
|
|
|||
|
|
- **Range**: [0, ∞)
|
|||
|
|
- **Zero**: For independent variables
|
|||
|
|
- **Maximum**: min(H(X), H(Y)) - when one determines the other
|
|||
|
|
- **Symmetric**: I(X;Y) = I(Y;X)
|
|||
|
|
- **Non-negative**: I(X;Y) ≥ 0 always
|
|||
|
|
|
|||
|
|
### Normalized Mutual Information
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
def normalized_mi(x, y, n_bins=10):
|
|||
|
|
"""Normalize MI to [0, 1] range"""
|
|||
|
|
from optimizr import mutual_information, shannon_entropy
|
|||
|
|
|
|||
|
|
mi = mutual_information(x, y, n_bins)
|
|||
|
|
hx = shannon_entropy(x, n_bins)
|
|||
|
|
hy = shannon_entropy(y, n_bins)
|
|||
|
|
|
|||
|
|
# Normalized by arithmetic mean
|
|||
|
|
nmi_arithmetic = mi / ((hx + hy) / 2)
|
|||
|
|
|
|||
|
|
# Normalized by geometric mean
|
|||
|
|
nmi_geometric = mi / np.sqrt(hx * hy)
|
|||
|
|
|
|||
|
|
# Normalized by minimum
|
|||
|
|
nmi_min = mi / min(hx, hy)
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
'arithmetic': nmi_arithmetic,
|
|||
|
|
'geometric': nmi_geometric,
|
|||
|
|
'min': nmi_min
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Common Pitfalls
|
|||
|
|
|
|||
|
|
1. **Insufficient Data**: Need enough samples for reliable histogram estimation. Rule of thumb: n > 10 × n_bins².
|
|||
|
|
|
|||
|
|
2. **Outliers**: Can dominate histogram bins. Consider robust binning or outlier removal.
|
|||
|
|
|
|||
|
|
3. **Different Scales**: Variables on very different scales may need normalization.
|
|||
|
|
|
|||
|
|
4. **Discrete vs Continuous**: Binning discretizes continuous variables, losing some information.
|
|||
|
|
|
|||
|
|
5. **Interpretation**: MI measures dependency, not causation.
|
|||
|
|
|
|||
|
|
## See Also
|
|||
|
|
|
|||
|
|
- [HMM API](hmm.md) - For regime detection using information-theoretic principles
|
|||
|
|
- [Information Theory Theory](theory/information_theory.md) - Mathematical background
|
|||
|
|
- [Examples](../examples/) - Complete working examples
|