Introduce yourself
I use scikit-learn for general ML work. I found this while looking for a
small contribution to make: the open issue backlog was heavily claimed
already, so I read through some less-trafficked modules directly and found
this in kernel_approximation.py.
Describe the bug and give evidence about its user-facing impact
PolynomialCountSketch.transform's sparse-input branch loops over every
feature and every degree, slicing a single sparse column and densifying it
with .toarray() on each iteration:
for j in range(X_gamma.shape[1]):
for d in range(self.degree):
iHashIndex = self.indexHash_[d, j]
iHashBit = self.bitHash_[d, j]
count_sketches[:, d, iHashIndex] += (
(iHashBit * X_gamma[:, [j]]).toarray().ravel()
)
This means the cost scales with n_features * degree, not with the number of
actual nonzeros, so a sparse matrix with a large number of columns but very
few nonzeros per column is transformed much slower than an equivalent dense
array of the same shape — defeating the purpose of the sparse code path. This
affects anyone using PolynomialCountSketch on sparse, wide, high-dimensional
data (e.g. bag-of-words or one-hot encoded features), where sparse support is
the main reason to reach for this transformer in the first place.
Steps/Code to Reproduce
import time
import numpy as np
import scipy.sparse as sp
from sklearn.kernel_approximation import PolynomialCountSketch
n_samples, n_components = 2000, 300
for n_features in [1000, 4000, 8000]:
X_sparse = sp.random(n_samples, n_features, density=0.01, format="csc", random_state=0)
X_dense = X_sparse.toarray()
ps_sparse = PolynomialCountSketch(degree=2, n_components=n_components, random_state=0).fit(X_sparse)
t0 = time.time()
ps_sparse.transform(X_sparse)
t1 = time.time()
ps_dense = PolynomialCountSketch(degree=2, n_components=n_components, random_state=0).fit(X_dense)
t2 = time.time()
ps_dense.transform(X_dense)
t3 = time.time()
print(f"n_features={n_features} nnz={X_sparse.nnz} sparse={t1 - t0:.4f}s dense={t3 - t2:.4f}s")
Expected Results
Transforming genuinely sparse input (1% density here) should be at least as
fast as transforming the equivalent densified array, since scipy sparse
operations scale with the number of nonzeros rather than the matrix shape.
Actual Results
n_features=1000 nnz=20000 sparse=0.0970s dense=0.0151s
n_features=4000 nnz=80000 sparse=0.3228s dense=0.0380s
n_features=8000 nnz=160000 sparse=0.6310s dense=0.0690s
The sparse path is roughly 9-10x slower than the dense path on the same data
at every size tested, and the gap grows with n_features since the loop
count (not the nonzero count) drives the cost.
Versions
System:
python: 3.14.6 (v3.14.6:c63aec69bd5, Jun 10 2026, 08:07:54) [Clang 21.0.0 (clang-2100.1.1.101)]
executable: /Library/Frameworks/Python.framework/Versions/3.14/bin/python3
machine: macOS-26.5.2-arm64-arm-64bit-Mach-O
Python dependencies:
sklearn: 1.10.dev0
pip: 26.1.2
setuptools: 83.0.0
numpy: 2.5.1
scipy: 1.18.1
Cython: 3.3.0
pandas: None
matplotlib: None
joblib: 1.6.0
threadpoolctl: 3.6.0
narwhals: 2.25.0
Built with OpenMP: True
I'd like to open a PR to fix this — I already have a working, tested fix
(replacing the per-column densification with one sparse matrix
multiplication per degree) and will link the PR here once triage clears.
Warning
This issue is not yet ready for a PR. If you are interested in contributing to scikit-learn, please have a look at our contributing guidelines, and in particular the sections for new contributors and the "Needs triage" label.
Introduce yourself
I use scikit-learn for general ML work. I found this while looking for a
small contribution to make: the open issue backlog was heavily claimed
already, so I read through some less-trafficked modules directly and found
this in
kernel_approximation.py.Describe the bug and give evidence about its user-facing impact
PolynomialCountSketch.transform's sparse-input branch loops over everyfeature and every degree, slicing a single sparse column and densifying it
with
.toarray()on each iteration:This means the cost scales with
n_features * degree, not with the number ofactual nonzeros, so a sparse matrix with a large number of columns but very
few nonzeros per column is transformed much slower than an equivalent dense
array of the same shape — defeating the purpose of the sparse code path. This
affects anyone using
PolynomialCountSketchon sparse, wide, high-dimensionaldata (e.g. bag-of-words or one-hot encoded features), where sparse support is
the main reason to reach for this transformer in the first place.
Steps/Code to Reproduce
Expected Results
Transforming genuinely sparse input (1% density here) should be at least as
fast as transforming the equivalent densified array, since scipy sparse
operations scale with the number of nonzeros rather than the matrix shape.
Actual Results
The sparse path is roughly 9-10x slower than the dense path on the same data
at every size tested, and the gap grows with
n_featuressince the loopcount (not the nonzero count) drives the cost.
Versions
I'd like to open a PR to fix this — I already have a working, tested fix
(replacing the per-column densification with one sparse matrix
multiplication per degree) and will link the PR here once triage clears.