Repository: benchopt/benchopt
Pull request #289 (2022-01-20 09:41:50)

ENH add X_density argument to make_correlated_data to generate sparse design matrix

codecov-commenter: # [Codecov](https://codecov.io/gh/benchopt/benchOpt/pull/289?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=benchopt) Report
> Merging [#289](https://codecov.io/gh/benchopt/benchOpt/pull/289?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=benchopt) (7ff77bc) into [main](https://codecov.io/gh/benchopt/benchOpt/commit/2addd69ce56eeb7d904bb79c1701e52b3f154ad6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=benchopt) (2addd69) will **increase** coverage by `0.27%`.
> The diff coverage is `100.00%`.


```diff
@@            Coverage Diff             @@
##             main     #289      +/-   ##
==========================================
+ Coverage   60.84%   61.12%   +0.27%     
==========================================
  Files          41       41              
  Lines        2240     2256      +16     
  Branches      410      414       +4     
==========================================
+ Hits         1363     1379      +16     
  Misses        778      778              
  Partials       99       99              
```



mathurinm: done @agramfort 

Patch:
diff --git a/benchopt/datasets/simulated.py b/benchopt/datasets/simulated.py
index b81a8b2..3d552b5 100644
--- a/benchopt/datasets/simulated.py
+++ b/benchopt/datasets/simulated.py
@@ -7,7 +7,7 @@ from ..utils.checkers import check_random_state
 
 def make_correlated_data(
         n_samples=100, n_features=50, n_tasks=1, rho=0.6, snr=3,
-        w_true=None, density=0.2, random_state=None):
+        w_true=None, density=0.2, X_density=1, random_state=None):
     r"""Generate a linear regression with decaying correlation for the design
     matrix :math:`\rho^{|i-j|}`.
 
@@ -49,10 +49,12 @@ def make_correlated_data(
     random_state: int | RandomState instance | None (default)
         Determines random number generation for data generation. Use an int to
         make the randomness deterministic.
+    X_density: float in ]0, 1]
+        Proportion of elements of X which are non-zero.
 
     Returns
     -------
-    X: ndarray, shape (n_samples, n_features)
+    X: ndarray or CSC matrix, shape (n_samples, n_features)
         A design matrix with Toeplitz covariance.
     y: ndarray, shape (n_samples,) or (n_samples, n_tasks)
         Observation vector/matrix.
@@ -63,6 +65,8 @@ def make_correlated_data(
         raise ValueError("The correlation `rho` should be chosen in [0, 1[.")
     if not 0 < density <= 1:
         raise ValueError("The density should be chosen in ]0, 1].")
+    if not 0 < X_density <= 1:
+        raise ValueError("The density of X should be chosen in ]0, 1].")
     if snr < 0:
         raise ValueError("The snr should be chosen in [0, inf].")
     rng = check_random_state(random_state)
@@ -84,6 +88,12 @@ def make_correlated_data(
     else:
         X = rng.randn(n_samples, n_features)
 
+    if X_density != 1:
+        zeros = rng.binomial(n=1, size=X.shape, p=1 - X_density).astype(bool)
+        X[zeros] = 0.
+        from scipy import sparse
+        X = sparse.csc_matrix(X)
+
     if w_true is None:
         w_true = np.zeros((n_features, n_tasks))
         support = rng.choice(n_features, nnz, replace=False)
diff --git a/doc/whats_new.rst b/doc/whats_new.rst
index 8826dda..7471d2b 100644
--- a/doc/whats_new.rst
+++ b/doc/whats_new.rst
@@ -37,10 +37,13 @@ API
 - 'stop_strategy' attribute is replaced by 'stopping_strategy' to harmonize
   with 'stopping_criterion', by `Benoît Malézieux`_ (:gh:`274`).
 
-- Add `import_from` method in `safe_import_context` to allow importing common
+- Add ``import_from`` method in ``safe_import_context`` to allow importing common
   files and packages without install from `BENCHMARK_DIR/utils`,
   by `Thomas Moreau`_ (:gh:`286`).
 
+- Add ``X_density`` argument to ``datasets.make_correlated_data`` to simulate
+  sparse design matrices, by `Mathurin Massias`_ (:gh:`289`).
+
 .. _changes_1_1:
 
 Version 1.1 - 22-04-2021


Test patch:
diff --git a/benchopt/datasets/tests/test_dataset_simulated.py b/benchopt/datasets/tests/test_dataset_simulated.py
index f117ea8..6cab5a1 100644
--- a/benchopt/datasets/tests/test_dataset_simulated.py
+++ b/benchopt/datasets/tests/test_dataset_simulated.py
@@ -1,5 +1,6 @@
 import pytest
 import numpy as np
+from scipy import sparse
 from numpy.linalg import norm
 
 from benchopt.datasets.simulated import make_correlated_data
@@ -63,6 +64,23 @@ def test_correlated_n_tasks(n_tasks):
         assert y.shape[1] == w_true.shape[1]
 
 
+@pytest.mark.parametrize("X_density", [-1, 0.2, 0.7, 1, 1.2])
+def test_correlated_sparse_X(X_density):
+    if not 0 < X_density <= 1:
+        np.testing.assert_raises(ValueError, make_correlated_data,
+                                 X_density=X_density)
+    else:
+        X, y, _ = make_correlated_data(X_density=X_density, random_state=0)
+        if X_density == 1:
+            assert isinstance(X, np.ndarray)
+        else:
+            assert isinstance(X, sparse.csc_matrix)
+            # check that X's density is equal to X_density up to sampling noise
+            np.testing.assert_allclose(
+                X_density * X.shape[0] * X.shape[1], len(X.indices - 1),
+                rtol=0.05)
+
+
 @pytest.mark.parametrize('param_name, p_range', [
     ('rho', [0, 1]), ('density', [0, 1]), ('snr', [0, np.inf])
 ])