Skip to content

Commit e0583dd

Browse files
committed
Improve test_hist.py
Signed-off-by: Tomas Pereira de Vasconcelos <tomasvasconcelos1@gmail.com>
1 parent 414ce85 commit e0583dd

1 file changed

Lines changed: 130 additions & 58 deletions

File tree

tests/unit/test_hist.py

Lines changed: 130 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -8,89 +8,161 @@
88
bin_trace_samples,
99
)
1010

11-
# Example data
11+
# ==============================================================
12+
# --- bin_trace_samples()
13+
# ==============================================================
1214

13-
SAMPLES_IN = [1, 2, 2, 3, 4]
14-
NBINS = 4
15-
# NOTE: The x values in DENSITIES_OUT correspond to the centers of
16-
# equally spaced bins over the range [1, 4].
17-
# This can be counterintuitive for count data, as the bins
18-
# do not align with the integer sample values.
19-
DENSITIES_OUT = [(1.375, 1), (2.125, 2), (2.875, 1), (3.625, 1)]
20-
X_OUT, Y_OUT = zip(*DENSITIES_OUT, strict=True)
2115

22-
WEIGHTS = [1, 1, 1, 1, 9]
16+
# --- Basic functionality ---
17+
18+
19+
@pytest.mark.parametrize(
20+
("samples", "nbins", "expected"),
21+
[
22+
# Basic case with repeated values
23+
([1, 2, 2, 3, 4], 4, [(1.375, 1), (2.125, 2), (2.875, 1), (3.625, 1)]),
24+
# Single bin aggregates all samples
25+
([1, 2, 3], 1, [(2.0, 3)]),
26+
# Uniform distribution
27+
([0, 1, 2, 3], 4, [(0.375, 1), (1.125, 1), (1.875, 1), (2.625, 1)]),
28+
# All identical samples go to rightmost bin
29+
([3, 3, 3], 2, [(2.75, 0), (3.25, 3)]),
30+
# Negative values
31+
([-2, -1, 0, 1], 2, [(-1.25, 2), (0.25, 2)]),
32+
],
33+
ids=["basic", "single_bin", "uniform", "identical", "negative"],
34+
)
35+
def test_basic_binning(
36+
samples: list[float], nbins: int, expected: list[tuple[float, float]]
37+
) -> None:
38+
result = bin_trace_samples(samples, nbins=nbins)
39+
assert result == expected
2340

24-
# ==============================================================
25-
# --- estimate_density_trace()
26-
# ==============================================================
41+
42+
def test_float_samples_binning() -> None:
43+
result = bin_trace_samples([0.1, 0.5, 0.9], nbins=3)
44+
x_vals, y_vals = zip(*result, strict=True)
45+
assert x_vals == pytest.approx((0.233, 0.5, 0.767), rel=1e-2)
46+
assert y_vals == (1.0, 1.0, 1.0)
2747

2848

29-
def test_bin_trace_samples_simple() -> None:
30-
density_trace = bin_trace_samples(trace_samples=SAMPLES_IN, nbins=NBINS)
31-
x, y = zip(*density_trace, strict=True)
32-
assert x == X_OUT
33-
assert y == Y_OUT
49+
@pytest.mark.parametrize("nbins", [1, 2, 5, 10, 50])
50+
def test_output_length_matches_nbins(nbins: int) -> None:
51+
result = bin_trace_samples([1, 2, 3, 4, 5], nbins=nbins)
52+
assert len(result) == nbins
3453

3554

36-
@pytest.mark.parametrize("nbins", [2, 5, 8, 11])
37-
def test_bin_trace_samples_nbins(nbins: int) -> None:
38-
density_trace = bin_trace_samples(trace_samples=SAMPLES_IN, nbins=nbins)
39-
assert len(density_trace) == nbins
55+
@pytest.mark.parametrize(
56+
"input_type",
57+
[list, tuple, np.array],
58+
ids=["list", "tuple", "ndarray"],
59+
)
60+
def test_accepts_various_input_types(input_type: type) -> None:
61+
samples = input_type([1, 2, 3])
62+
result = bin_trace_samples(samples, nbins=2)
63+
assert len(result) == 2
64+
assert all(isinstance(x, float) and isinstance(y, float) for x, y in result)
4065

4166

42-
@pytest.mark.parametrize("non_finite_value", [np.inf, np.nan, float("inf"), float("nan")])
43-
def test_bin_trace_samples_fails_for_non_finite_values(non_finite_value: float) -> None:
44-
err_msg = "The samples array should not contain any infs or NaNs."
45-
with pytest.raises(ValueError, match=err_msg):
46-
bin_trace_samples(trace_samples=[*SAMPLES_IN[:-1], non_finite_value], nbins=NBINS)
67+
def test_counts_sum_to_sample_size() -> None:
68+
samples = list(range(100))
69+
result = bin_trace_samples(samples, nbins=7)
70+
total_count = sum(y for _, y in result)
71+
assert total_count == len(samples)
4772

4873

49-
def test_bin_trace_samples_weights() -> None:
50-
density_trace = bin_trace_samples(
51-
trace_samples=SAMPLES_IN,
52-
nbins=NBINS,
53-
weights=WEIGHTS,
54-
)
55-
x, y = zip(*density_trace, strict=True)
56-
assert x == X_OUT
57-
assert np.argmax(y) == len(y) - 1
74+
def test_bin_centers_within_data_range() -> None:
75+
samples = [10, 20, 30, 40, 50]
76+
result = bin_trace_samples(samples, nbins=5)
77+
centers = [x for x, _ in result]
78+
assert all(min(samples) <= c <= max(samples) for c in centers)
5879

5980

60-
def test_bin_trace_samples_weights_not_same_length() -> None:
61-
with pytest.raises(
62-
ValueError, match="The weights array should have the same length as the samples array"
63-
):
64-
bin_trace_samples(trace_samples=SAMPLES_IN, nbins=NBINS, weights=[1, 1, 1])
81+
# --- Weights ---
6582

6683

67-
@pytest.mark.parametrize("non_finite_value", [np.inf, np.nan, float("inf"), float("nan")])
68-
def test_bin_trace_samples_weights_fails_for_non_finite_values(
69-
non_finite_value: float,
84+
@pytest.mark.parametrize(
85+
("samples", "weights", "nbins", "expected_counts"),
86+
[
87+
# Weights shift distribution
88+
([1, 2, 3], [10, 1, 1], 3, [10, 1, 1]),
89+
# Zero weights effectively exclude samples
90+
([1, 2, 3], [1, 0, 1], 3, [1, 0, 1]),
91+
# Fractional weights
92+
([1, 2], [0.5, 1.5], 2, [0.5, 1.5]),
93+
],
94+
ids=["heavy_first", "zero_weight", "fractional"],
95+
)
96+
def test_weights_affect_counts(
97+
samples: list[float],
98+
weights: list[float],
99+
nbins: int,
100+
expected_counts: list[float],
70101
) -> None:
71-
err_msg = "The weights array should not contain any infs or NaNs."
72-
with pytest.raises(ValueError, match=err_msg):
73-
bin_trace_samples(
74-
trace_samples=SAMPLES_IN,
75-
nbins=NBINS,
76-
weights=[*WEIGHTS[:-1], non_finite_value],
77-
)
102+
result = bin_trace_samples(samples, nbins=nbins, weights=weights)
103+
counts = [y for _, y in result]
104+
assert counts == pytest.approx(expected_counts)
105+
106+
107+
def test_weighted_counts_sum_to_weight_sum() -> None:
108+
samples = [1, 2, 3, 4, 5]
109+
weights = [2.0, 3.0, 1.5, 0.5, 4.0]
110+
result = bin_trace_samples(samples, nbins=3, weights=weights)
111+
assert sum(y for _, y in result) == pytest.approx(sum(weights))
112+
113+
114+
# --- Error handling ---
115+
116+
117+
@pytest.mark.parametrize(
118+
"non_finite",
119+
[np.inf, -np.inf, np.nan, float("inf"), float("nan")],
120+
ids=["inf", "neg_inf", "nan", "float_inf", "float_nan"],
121+
)
122+
def test_rejects_non_finite_samples(non_finite: float) -> None:
123+
with pytest.raises(ValueError, match="samples array should not contain any infs or NaNs"):
124+
bin_trace_samples([1, 2, non_finite], nbins=2)
125+
126+
127+
@pytest.mark.parametrize(
128+
"non_finite",
129+
[np.inf, -np.inf, np.nan, float("inf"), float("nan")],
130+
ids=["inf", "neg_inf", "nan", "float_inf", "float_nan"],
131+
)
132+
def test_rejects_non_finite_weights(non_finite: float) -> None:
133+
with pytest.raises(ValueError, match="weights array should not contain any infs or NaNs"):
134+
bin_trace_samples([1, 2, 3], nbins=2, weights=[1, non_finite, 1])
135+
136+
137+
@pytest.mark.parametrize(
138+
("samples", "weights"),
139+
[
140+
([1, 2, 3], [1, 2]),
141+
([1, 2], [1, 2, 3]),
142+
([1], []),
143+
],
144+
ids=["weights_short", "weights_long", "empty_weights"],
145+
)
146+
def test_rejects_mismatched_weights_length(samples: list[float], weights: list[float]) -> None:
147+
with pytest.raises(ValueError, match="weights array should have the same length"):
148+
bin_trace_samples(samples, nbins=2, weights=weights)
78149

79150

80151
# ==============================================================
81-
# --- estimate_densities()
152+
# --- bin_samples()
82153
# ==============================================================
83154

84155

85156
def test_bin_samples() -> None:
86-
densities = bin_samples(
87-
samples=[[SAMPLES_IN], [SAMPLES_IN]],
88-
nbins=NBINS,
89-
)
157+
samples = [1, 2, 2, 3, 4]
158+
nbins = 4
159+
expected = [(1.375, 1), (2.125, 2), (2.875, 1), (3.625, 1)]
160+
x_out, y_out = zip(*expected, strict=True)
161+
densities = bin_samples(samples=[[samples], [samples]], nbins=nbins)
90162
assert len(densities) == 2
91163
for densities_row in densities:
92164
assert len(densities_row) == 1
93165
density_trace = next(iter(densities_row))
94166
x, y = zip(*density_trace, strict=True)
95-
assert x == X_OUT
96-
assert y == Y_OUT
167+
assert x == x_out
168+
assert y == y_out

0 commit comments

Comments
 (0)