ReMoDe#

ReMoDe (Reproducible Mode Detection) is a method for finding meaningful peaks (modes) in ordinal or discrete count data.

This is useful in many applied settings, for example identifying polarization in survey responses or separating symptom-incidence groups in clinical scales. Existing modality methods are often descriptive only or not designed for ordinal data. ReMoDe addresses this by combining recursive mode detection with statistical significance testing.

For each detected mode, ReMoDe reports statistical evidence (p-values and approximate Bayes factors) and includes a stability check to assess how robust results are to data perturbations.

Install#

pip install remode

Quickstart#

from remode import ReMoDe

xt_count = [8, 20, 5, 2, 6, 2, 30]
remode = ReMoDe()
result = remode.fit(xt_count)
print(result)
from remode import *
import numpy as np
import matplotlib.pyplot as plt

Tutorial#

## Histogram counts
xt_count = [8, 20, 5, 2, 6, 2, 30]

# Start ReMoDe
remode = ReMoDe(    definition="shape_based", # default; set "peak_based" to exclude uniform distributions
)

# Fit the data and plot maxima
result = remode.fit(xt_count)
print(result)

remode.plot_maxima()
{'nr_of_modes': 2, 'modes': array([1, 6]), 'xt': [8, 20, 5, 2, 6, 2, 30], 'alpha_after_correction': 0.05}
_images/de3d528cec9be13f98c0919c39f6d24fb934ed7449bb6a2f83a0b12452397145.png
# Test robustness using jackknife
robustness = remode.evaluate_stability(percentage_steps=50)
print(robustness["stable_until"])
81.63265306122449
_images/5b4da9624593460523c36ca6c18dff326b697d4627eb9a42bd068849b6cbc696.png
## Converting individual points to count data
# Generate random data
np.random.seed(0    definition="shape_based", # default; set "peak_based" to exclude uniform distributions
)
data = np.random.choice(range(3, 8), 200, p=[0.2, 0.2, 0.3, 0.2, 0.1])

# Start ReMoDe
remode = ReMoDe()

# Create count data
xt_count = remode.format_data(data)

# Fit the data and plot maxima
result = remode.fit(xt_count, levels=np.arange(3, 8))
print(result)

remode.plot_maxima()
{'nr_of_modes': 1, 'modes': array([2]), 'xt': array([41, 34, 71, 36, 18]), 'alpha_after_correction': 0.05}
_images/ea34346ef7573514dc7a4be6358ea109db44286e4d886ec17c241e03602c4199.png

Configuration Options for ReMoDe()#

remode = ReMoDe(
    alpha=0.05,  # alpha level desired (adjusted recursively for multiple testing)
    alpha_correction="descriptive_peaks",  # default; or "max_modes", "none", or custom function
    statistical_test="bootstrap",  # default; or "fisher", "binomial", or custom function
    definition="shape_based",  # default; set "peak_based" to exclude uniform distributions
)
remode = ReMoDe(
    alpha=0.05,
    alpha_correction="max_modes",  # alternative built-in correction
    statistical_test="fisher",
    definition="peak_based",
)

Uniformity Test (peak_based)#

With definition="peak_based", ReMoDe applies a Pearson Chi-square test for uniformity. If the distribution is not significantly different from uniform (p > 0.05), it returns zero modes.

uniform_counts = np.array([10, 10, 10, 10, 10])

uniform_result = ReMoDe(
    statistical_test="fisher",
    definition="peak_based",
).fit(uniform_counts)

print("Number of modes:", uniform_result["nr_of_modes"])
print("Uniform distribution:", uniform_result["uniform_distribution"])
print("Chi-square p-value:", uniform_result["uniformity_p_value"])
# Example of custom statistical test (this is actually `perform_binomial_test`)
from scipy.stats import binomtest


def custom_test(x, candidate, left_min, right_min):
    """Return left/right p-values for a candidate maximum."""
    n_left = x[candidate] + x[left_min]
    n_right = x[candidate] + x[right_min]
    p_left = binomtest(x[candidate], n_left, alternative="greater").pvalue
    p_right = binomtest(x[candidate], n_right, alternative="greater").pvalue
    return p_left, p_right


# Example of custom alpha adjustment
def custom_adjustment(segment_or_length, alpha):
    """Adjust alpha as a function of segment size."""
    if hasattr(segment_or_length, "__len__"):
        length = len(segment_or_length)
    else:
        length = int(segment_or_length)
    return alpha / max(1, length)


remode = ReMoDe(
    alpha=0.05,
    statistical_test=custom_test,
    alpha_correction=custom_adjustment,
)