Freelance Journal

From Almaty, Kazakhstan to the world!

We Put Lyubishchev on PyPI with interesting benchmarks

pythonmachine learningsklearntaxonomyopen source

Last week I published a library called lyubishchev on PyPI. The previous article explained why — Alexander Lyubishchev developed multivariate classification methods in 1943, twenty years before the researchers whose names ended up in scipy.spatial.distance. The library was a citation as much as a tool

Then I ran actual benchmarks. The results were more interesting than I expected


The Setup

Three standard sklearn datasets. Four classifiers. Five-fold stratified cross-validation

from sklearn.datasets import load_iris, load_wine, load_breast_cancer
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.naive_bayes import GaussianNB
from lyubishchev import LyubishchevClassifier

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

No hyperparameter tuning. Default settings for everything except LyubishchevClassifier(standardize=True) — standardization on because the method requires features on comparable scales, same as any distance-based approach


The Results

Dataset Lyubishchev QDA LDA GaussianNB
Iris · 150 samples · 4 features 0.9800 ± 0.0267 0.9800 ± 0.0267 0.9733 ± 0.0389 0.9467 ± 0.0400
Wine · 178 samples · 13 features 0.9889 ± 0.0136 0.9889 ± 0.0136 0.9830 ± 0.0139 0.9719 ± 0.0252
Breast Cancer · 569 samples · 30 features 0.9543 ± 0.0035 0.9561 ± 0.0055 0.9561 ± 0.0200 0.9385 ± 0.0235

Run It Yourself

Three commands. You need Python installed, nothing else unusual

# Install the libraries
pip install lyubishchev scikit-learn

# Run the benchmark
python3 << 'EOF'
import warnings
import numpy as np
from sklearn.datasets import load_iris, load_wine, load_breast_cancer
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis, LinearDiscriminantAnalysis
from sklearn.naive_bayes import GaussianNB
from lyubishchev import LyubishchevClassifier

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

datasets = {
    "Iris          ": load_iris(return_X_y=True),
    "Wine          ": load_wine(return_X_y=True),
    "Breast Cancer ": load_breast_cancer(return_X_y=True),
}

classifiers = {
    "LyubishchevClassifier": LyubishchevClassifier(standardize=True),
    "QDA (sklearn)        ": QuadraticDiscriminantAnalysis(),
    "LDA (sklearn)        ": LinearDiscriminantAnalysis(),
    "GaussianNB (sklearn) ": GaussianNB(),
}

for ds_name, (X, y) in datasets.items():
    print(f"\n{ds_name.strip()}")
    for clf_name, clf in classifiers.items():
        try:
            with warnings.catch_warnings():
                warnings.simplefilter("ignore")
                scores = cross_val_score(clf, X, y, cv=cv, scoring="accuracy")
            print(f"  {clf_name} {scores.mean():.4f} ± {scores.std():.4f}")
        except Exception:
            print(f"  {clf_name} CRASH")
EOF

You will see QDA complete all three datasets on sklearn 1.6+. The numbers should match the table above exactly — same random seed, same splits


What Is Actually Happening on Breast Cancer

Breast Cancer is the interesting row. 30 features, 569 samples, many correlated measurements. This is where real data lives

On this dataset all three discriminant classifiers score within two tenths of a percent of each other on accuracy. The difference worth noting is that LyubishchevClassifier has the lowest standard deviation — ± 0.0035 versus QDA's ± 0.0055 and LDA's ± 0.0200. More consistent predictions across folds

More importantly: this dataset exposes a version-dependent fragility in sklearn's own QDA

from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis
qda_unsafe = QuadraticDiscriminantAnalysis(reg_param=0.0)

On sklearn 1.6, QDA applies a small silent regularization and runs. On sklearn 1.9+, it raises ValueError: covariance matrix is not full rank and crashes entirely. The behavior depends on which version you have installed — and you may not know which behaviour you are relying on until it breaks in production

LyubishchevClassifier applies reg_covar explicitly and by design. The regularization is not a patch — it is part of the method. Lyubishchev's 1943 formulation required a well-conditioned covariance matrix, so the diagonal ridge was structural from the start. The result is deterministic behaviour across sklearn versions: it fits, it predicts, and it does not change behaviour silently when you upgrade


What This Is and Is Not

LyubishchevClassifier is mathematically equivalent to regularized QDA. It is not magic. On Iris and Wine — clean, low-feature datasets — it produces identical results to sklearn's QDA down to four decimal places, because the two methods are doing the same thing when the covariance is non-singular

The difference is robustness. Real datasets are not Iris. They have correlated features, more features than samples in some classes, and measurement scales that vary by orders of magnitude. LyubishchevClassifier was built for that reality, because that is the reality Lyubishchev was working in — flea beetle measurements with complex correlation structure, identified by a field entomologist with a ruler


The API

It is fully sklearn-compatible. Passes check_estimator. Works inside Pipeline and GridSearchCV

from lyubishchev import LyubishchevClassifier
import numpy as np

clf = LyubishchevClassifier(standardize=True, reg_covar=1e-6)
clf.fit(X_train, y_train)

labels = clf.predict(X_test)
proba  = clf.predict_proba(X_test)
score  = clf.score(X_test, y_test)

# Works in a Pipeline
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

pipe = Pipeline([
    ('scaler', StandardScaler()),
    ('clf', LyubishchevClassifier()),
])
pipe.fit(X_train, y_train)

The lower-level functions from the original release are still there:

from lyubishchev import divergence_coefficient, scatter_ellipse, classify, transgression

Install

pip install lyubishchev

Source: github.com/AkzhanBerdi/lyubishchev

Primary source: Lyubishchev, A.A. (1943). Programma obshchey sistematiki. Manuscript, 22 November 1943. zin.ru/animalia/coleoptera/rus/lyubis05.htm


What Is Next

The library is listed under scikit-learn-contrib (#81) — the official sklearn ecosystem for compatible packages. An R version has been submitted to CRAN. A proposal to add lyubishchev_divergence to scipy.spatial.distance is open at scipy #25335, tagged by a SciPy maintainer

If you have used the library, hit an issue, or want to contribute — the repo is open


Yours, Bad Dog

Written by Bad Dog