Skip to content

Fix correctness bugs across the core modules - #7

Merged
adimajo merged 15 commits into
masterfrom
fix/correctness-bugs
Oct 29, 2026
Merged

adimajo merged 15 commits into
masterfrom
fix/correctness-bugs

Conversation

@adimajo

@adimajo adimajo commented Oct 29, 2026

Copy link
Copy Markdown
Owner

First stream of correctness fixes across the core modules, as 15 small commits
that each keep the tests green and carry their own test.

  • generate_data: the log-odds were an integer array, so float coefficients
    were truncated; labels are drawn with an explicit size; plot=True opens
    one figure per feature pair instead of overplotting.
  • gini_utils: np.float (removed from NumPy) replaced by float, so gini
    works again; the level check accepted 0 and negative values.
  • best_formula: no more IndexError when a level only holds missing values.
  • discretize: the shape error for categorical columns named predictors_cont.
  • Glmdisc.__init__: an unfitted SEM model raises NotFittedError instead of
    TypeError; m_start and burn_in must be integers.
  • plot: logodd works with only continuous or only categorical features and
    with a single feature; last French comment translated.
  • fit: the initial labels are drawn with np.random.randint from the right
    number of levels (the last level was never used and a one-level feature
    crashed); each LabelEncoder is fitted once instead of four times per
    feature, in _init_disc and in the NN initializer.

Tests: 67 pass. The 24 tests in tests/tests_NN fail exactly as they do on
master (Keras 3 rejects lr, backend.function is gone); that is the subject
of a follow-up.

Not changed, noticed on the way: _discretize_sem labels missing values with
max(new data) + 1, which depends on the scored batch; the NN initializer
still caps the number of groups at the number of levels minus one.

log_odd was an integer array, so each float coefficient added to it was
silently truncated toward zero and the drawn labels followed the wrong
probabilities. Accumulate in a float array instead.
Draw exactly one label per observation instead of relying on the shape of
the probability vector to set the output size.
generate_data(plot=True) drew every pair of features on the same axes,
so the scatterplots overlapped into an unreadable cloud. Open a figure
per pair, label its axes and show it without blocking, as the plot
method does.
The np.float alias was removed in NumPy 1.24, so every call to gini
raised AttributeError. Use the builtin float, and add the first tests of
the Gini index and its DeLong variance against scikit-learn's AUC.
"not level < 1 and level > 0" parses as "(level >= 1) and (level > 0)",
so 0 and negative levels were accepted and produced NaN intervals. Test
that the level lies strictly between 0 and 1 instead.
When a continuous feature had a quantization level made only of missing
values, its lower bound was NaN, nanmin matched nothing and indexing the
empty result raised IndexError. Ignore NaN bounds and only drop the
smallest one when some remain.
The error raised when the number of categorical columns does not match
the fitted model talked about predictors_cont, pointing users at the
wrong argument. Fix the message for both the SEM and NN checks.
best_reglog is None before fit, which scikit-learn's check_is_fitted
rejects with a TypeError, so predict and discretize on a new instance
did not raise the documented NotFittedError. Test for None first.
m_start=2.5 passed the range check and crashed later in range(), and
burn_in was never checked. Require integers, and a non-negative burn_in.
The GAM input was built from both predictors_cont and predictors_qual
unconditionally, so a model fitted on continuous features only, or on
categorical ones only, crashed on the missing block. Build the design
from the blocks that exist.
plt.subplots returns a bare Axes rather than an array when there is a
single subplot, so enumerate(axs) raised TypeError. Ask for a 2D array
of axes.
The encoder of every categorical feature was fitted four times per
feature to recompute its number of levels, in addition to the fit that
is stored in affectations. Read the level count from the stored encoder.
For a categorical feature with fewer levels than m_start the initial
labels were drawn from range(max_label), which left out the last level
and made np.random.choice fail on an empty list for a one-level feature.
Draw from the right number of levels with np.random.randint, which also
replaces the list-based choice for the continuous features.
_initialize_neural_net fitted a fresh LabelEncoder per categorical
feature just to read its largest label, although fit already stored
one in affectations. Read it from there.
@adimajo
adimajo merged commit f21c745 into master Oct 29, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant