Repository: scikit-learn/scikit-learn
Pull request: Fixes API Deprecate n_alphas in LinearModelCV
Fixes #30467

Updated LinearModelCV and derived classes LassoCV, ElasticNetCV, MultiTaskElasticNetCV, MultiTaskLassoCV to remove `n_alphas` parameter from the constructor. `alphas` parameter is updated to support an integer or array-like argument. Functionality of `n_alphas` is preserved by passing an integer to `alphas`. Parameter_constraints updated accordingly.

Unit tests created to verify correct warning messages are raised upon usage of `n_alphas` parameter. Unit tests created to verify correct warning message if using the default value of `alphas`. All unit tests passing and all warnings suppressed on existing test cases using filter warnings.

All classes are backwards compatible with `n_alphas` until 1.8, before it will be removed. doc_strings updated to effectively communicate changes. 



Problem statement:
API Deprecate n_alphas in LinearModelCV
In LassoCV, ElasticNetCV, ... we have two parameters, `alphas` and `n_alphas`, that have the same purpose, i.e. determine the alpha values to test.

I'd be in favor of deprecating `n_alphas` and make `alphas` accept either an int or an array-like, filling both roles.

I chose to keep `alphas` and not the other because `RidgeCV` has `alphas` and no `n_alphas` (although `alphas` can't be an int there, maybe an enhancement to make ?), and the most recent param of this kind, `threshold` in `TunedThresholdClassifierCV`, follows this naming pattern and fills both roles.

Hints from issue discussion:
Hi, can I take a shot at refactoring?
sure ! You can take a look at the [contributing guide](https://scikit-learn.org/dev/developers/contributing.html) and especially the [deprecation section](https://scikit-learn.org/dev/developers/contributing.html#deprecation) to get started.

Commit message: Merge branch 'main' into LinearModelCV_api_refactor

Patch to review:
diff --git a/doc/whats_new/upcoming_changes/sklearn.linear_model/30616.api.rst b/doc/whats_new/upcoming_changes/sklearn.linear_model/30616.api.rst
new file mode 100644
index 0000000000000..425702783a7e2
--- /dev/null
+++ b/doc/whats_new/upcoming_changes/sklearn.linear_model/30616.api.rst
@@ -0,0 +1,13 @@
+```rst
+- The parameter `n_alphas` has been deprecated in the following classes:
+:class:`linear_model.ElasticNetCV` and :class:`linear_model.LassoCV`
+and :class:`linear_model.MultiTaskElasticNetCV` 
+and :class:`linear_model.MultiTaskLassoCV`.
+- The parameter `alphas` now supports integer and array-like argument. 
+Passing an integer, k, to `alphas` will generate k alpha values to 
+compute along the regularization path. Passing an array-like object into
+`alphas` will compute each item in the array as an alpha value
+along the regularization. 
+- To achieve similar behavior as passing an integer into `n_alphas`
+in scikit-learn 1.7+, pass an integer into the argument `alphas`.
+```
diff --git a/sklearn/linear_model/_coordinate_descent.py b/sklearn/linear_model/_coordinate_descent.py
index b98cf08925910..1fa000b45e708 100644
--- a/sklearn/linear_model/_coordinate_descent.py
+++ b/sklearn/linear_model/_coordinate_descent.py
@@ -1495,8 +1495,15 @@ class LinearModelCV(MultiOutputMixin, LinearModel, ABC):
 
     _parameter_constraints: dict = {
         "eps": [Interval(Real, 0, None, closed="neither")],
-        "n_alphas": [Interval(Integral, 1, None, closed="left")],
-        "alphas": ["array-like", None],
+        "n_alphas": [
+            Interval(Integral, 1, None, closed="left"),
+            StrOptions({"deprecated"}),
+        ],
+        "alphas": [
+            Interval(Integral, 1, None, closed="left"),
+            "array-like",
+            StrOptions({"warn"}),
+        ],
         "fit_intercept": ["boolean"],
         "precompute": [StrOptions({"auto"}), "array-like", "boolean"],
         "max_iter": [Interval(Integral, 1, None, closed="left")],
@@ -1514,8 +1521,10 @@ class LinearModelCV(MultiOutputMixin, LinearModel, ABC):
     def __init__(
         self,
         eps=1e-3,
-        n_alphas=100,
-        alphas=None,
+        ############ TODO:
+        ############ Need to change _parameter_constraints dictionary
+        n_alphas="deprecated",
+        alphas="warn",
         fit_intercept=True,
         precompute="auto",
         max_iter=1000,
@@ -1691,12 +1700,33 @@ def fit(self, X, y, sample_weight=None, **params):
             l1_ratios = [
                 1,
             ]
+
         path_params.pop("cv", None)
         path_params.pop("n_jobs", None)
 
-        alphas = self.alphas
+        alphas = None
         n_l1_ratio = len(l1_ratios)
 
+        if self.n_alphas != "deprecated":
+            warnings.warn(
+                "`n_alphas` was deprecated in 1.7 and will be removed in 1.9"
+                " use `alphas` to set number of alphas values to test along"
+                " regularization path",
+                FutureWarning,
+            )
+            self.alphas = self.n_alphas
+
+        # self.alphas is only string with value "warn"
+        # if the default value is used in the constructor
+        if isinstance(self.alphas, str):
+            warnings.warn(
+                "The default value of `alphas` will change from None to 100 in 1.7"
+                " Since 1.7, ``alphas`` supports an integer argument which generates"
+                " ``alphas`` number of alpha values along the regularization path.",
+                FutureWarning,
+            )
+            self.alphas = 100
+
         check_scalar_alpha = partial(
             check_scalar,
             target_type=Real,
@@ -1704,7 +1734,8 @@ def fit(self, X, y, sample_weight=None, **params):
             include_boundaries="left",
         )
 
-        if alphas is None:
+        # In Deprecation, alphas' default value will change to 100
+        if isinstance(self.alphas, Integral):
             alphas = [
                 _alpha_grid(
                     X,
@@ -1712,7 +1743,7 @@ def fit(self, X, y, sample_weight=None, **params):
                     l1_ratio=l1_ratio,
                     fit_intercept=self.fit_intercept,
                     eps=self.eps,
-                    n_alphas=self.n_alphas,
+                    n_alphas=self.alphas,
                     copy_X=self.copy_X,
                     sample_weight=sample_weight,
                 )
@@ -1720,10 +1751,10 @@ def fit(self, X, y, sample_weight=None, **params):
             ]
         else:
             # Making sure alphas entries are scalars.
-            for index, alpha in enumerate(alphas):
+            for index, alpha in enumerate(self.alphas):
                 check_scalar_alpha(alpha, f"alphas[{index}]")
             # Making sure alphas is properly ordered.
-            alphas = np.tile(np.sort(alphas)[::-1], (n_l1_ratio, 1))
+            alphas = np.tile(np.sort(self.alphas)[::-1], (n_l1_ratio, 1))
 
         # We want n_alphas to be the number of alphas used for each l1_ratio.
         n_alphas = len(alphas[0])
@@ -1809,7 +1840,8 @@ def fit(self, X, y, sample_weight=None, **params):
 
         self.l1_ratio_ = best_l1_ratio
         self.alpha_ = best_alpha
-        if self.alphas is None:
+        # If alphas auto-generated, used by deprecated n_alphas
+        if isinstance(self.alphas, Integral):
             self.alphas_ = np.asarray(alphas)
             if n_l1_ratio == 1:
                 self.alphas_ = self.alphas_[0]
@@ -1899,9 +1931,18 @@ class LassoCV(RegressorMixin, LinearModelCV):
     n_alphas : int, default=100
         Number of alphas along the regularization path.
 
-    alphas : array-like, default=None
-        List of alphas where to compute the models.
-        If ``None`` alphas are set automatically.
+    .. deprecated:: 1.6
+        ``n_alphas`` was deprecated in 1.6 and will be removed in 1.8.
+        Use parameter ``alphas`` instead.
+
+    alphas : array-like or int, default=100
+        List of alphas of where to compute the models.
+        If int, ``alphas`` number of values are generated automatically
+
+    .. versionchanged:: 1.6
+        The default value of ``alphas`` will change from None to 100 in 1.6.
+        Since 1.6, ``alphas`` supports an integer argument which generates
+        ``alphas`` number of alpha values along the regularization path.
 
     fit_intercept : bool, default=True
         Whether to calculate the intercept for this model. If set
@@ -2051,8 +2092,8 @@ def __init__(
         self,
         *,
         eps=1e-3,
-        n_alphas=100,
-        alphas=None,
+        n_alphas="deprecated",
+        alphas="warn",
         fit_intercept=True,
         precompute="auto",
         max_iter=1000,
@@ -2081,6 +2122,7 @@ def __init__(
             random_state=random_state,
             selection=selection,
         )
+        print(n_alphas)
 
     def _get_estimator(self):
         return Lasso()
@@ -2155,11 +2197,20 @@ class ElasticNetCV(RegressorMixin, LinearModelCV):
         ``alpha_min / alpha_max = 1e-3``.
 
     n_alphas : int, default=100
-        Number of alphas along the regularization path, used for each l1_ratio.
+        Number of alphas along the regularization path.
 
-    alphas : array-like, default=None
-        List of alphas where to compute the models.
-        If None alphas are set automatically.
+    .. deprecated:: 1.7
+        ``n_alphas`` was deprecated in 1.7 and will be removed in 1.9.
+        Use parameter ``alphas`` instead.
+
+    alphas : array-like or int, default=100
+        List of alphas of where to compute the models.
+        If int, ``alphas`` number of values are generated automatically
+
+    .. versionchanged:: 1.7
+        The default value of ``alphas`` will change from None to 100 in 1.7
+        Since 1.7, ``alphas`` supports an integer argument which generates
+        ``alphas`` number of alpha values along the regularization path.
 
     fit_intercept : bool, default=True
         Whether to calculate the intercept for this model. If set
@@ -2328,8 +2379,8 @@ def __init__(
         *,
         l1_ratio=0.5,
         eps=1e-3,
-        n_alphas=100,
-        alphas=None,
+        n_alphas="deprecated",
+        alphas="warn",
         fit_intercept=True,
         precompute="auto",
         max_iter=1000,
@@ -2847,9 +2898,18 @@ class MultiTaskElasticNetCV(RegressorMixin, LinearModelCV):
     n_alphas : int, default=100
         Number of alphas along the regularization path.
 
-    alphas : array-like, default=None
-        List of alphas where to compute the models.
-        If not provided, set automatically.
+    .. deprecated:: 1.7
+        ``n_alphas`` was deprecated in 1.7 and will be removed in 1.9.
+        Use parameter ``alphas`` instead.
+
+    alphas : array-like or int, default=100
+        List of alphas of where to compute the models.
+        If int, ``alphas`` number of values are generated automatically
+
+    .. versionchanged:: 1.7
+        The default value of ``alphas`` will change from None to 100 in 1.7
+        Since 1.7, ``alphas`` supports an integer argument which generates
+        ``alphas`` number of alpha values along the regularization path.
 
     fit_intercept : bool, default=True
         Whether to calculate the intercept for this model. If set
@@ -2993,8 +3053,8 @@ def __init__(
         *,
         l1_ratio=0.5,
         eps=1e-3,
-        n_alphas=100,
-        alphas=None,
+        n_alphas="deprecated",
+        alphas="warn",
         fit_intercept=True,
         max_iter=1000,
         tol=1e-4,
@@ -3090,9 +3150,18 @@ class MultiTaskLassoCV(RegressorMixin, LinearModelCV):
     n_alphas : int, default=100
         Number of alphas along the regularization path.
 
-    alphas : array-like, default=None
-        List of alphas where to compute the models.
-        If not provided, set automatically.
+    .. deprecated:: 1.7
+        ``n_alphas`` was deprecated in 1.7 and will be removed in 1.9.
+        Use parameter ``alphas`` instead.
+
+    alphas : array-like or int, default=100
+        List of alphas of where to compute the models.
+        If int, ``alphas`` number of values are generated automatically
+
+    .. versionchanged:: 1.7
+        The default value of ``alphas`` will change from None to 100 in 1.7
+        Since 1.7, ``alphas`` supports an integer argument which generates
+        ``alphas`` number of alpha values along the regularization path.
 
     fit_intercept : bool, default=True
         Whether to calculate the intercept for this model. If set
@@ -3232,8 +3301,8 @@ def __init__(
         self,
         *,
         eps=1e-3,
-        n_alphas=100,
-        alphas=None,
+        n_alphas="deprecated",
+        alphas="warn",
         fit_intercept=True,
         max_iter=1000,
         tol=1e-4,
diff --git a/sklearn/linear_model/tests/test_coordinate_descent.py b/sklearn/linear_model/tests/test_coordinate_descent.py
index 2eefe45e068d3..3eda126ab4878 100644
--- a/sklearn/linear_model/tests/test_coordinate_descent.py
+++ b/sklearn/linear_model/tests/test_coordinate_descent.py
@@ -241,6 +241,10 @@ def build_dataset(n_samples=50, n_features=200, n_informative_features=10, n_tar
     return X, y, X_test, y_test
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 def test_lasso_cv():
     X, y, X_test, y_test = build_dataset()
     max_iter = 150
@@ -271,6 +275,10 @@ def test_lasso_cv():
     assert clf.score(X_test, y_test) > 0.99
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 def test_lasso_cv_with_some_model_selection():
     from sklearn import datasets
     from sklearn.model_selection import ShuffleSplit
@@ -283,6 +291,10 @@ def test_lasso_cv_with_some_model_selection():
     pipe.fit(X, y)
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 def test_lasso_cv_positive_constraint():
     X, y, X_test, y_test = build_dataset()
     max_iter = 500
@@ -300,6 +312,10 @@ def test_lasso_cv_positive_constraint():
     assert min(clf_constrained.coef_) >= 0
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 @pytest.mark.parametrize(
     "alphas, err_type, err_msg",
     [
@@ -360,6 +376,10 @@ def _scale_alpha_inplace(estimator, n_samples):
     estimator.set_params(alpha=alpha)
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 @pytest.mark.filterwarnings("ignore::sklearn.exceptions.ConvergenceWarning")
 @pytest.mark.parametrize(
     "LinearModel, params",
@@ -435,6 +455,10 @@ def test_lasso_path_return_models_vs_new_return_gives_same_coefficients():
     )
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 def test_enet_path():
     # We use a large number of samples and of informative features so that
     # the l1_ratio selected is more toward ridge than lasso
@@ -499,6 +523,10 @@ def test_enet_path():
     assert_almost_equal(clf1.alpha_, clf2.alpha_)
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 def test_path_parameters():
     X, y, _, _ = build_dataset()
     max_iter = 100
@@ -557,6 +585,10 @@ def test_enet_positive_constraint():
     assert min(enet.coef_) >= 0
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 def test_enet_cv_positive_constraint():
     X, y, X_test, y_test = build_dataset()
     max_iter = 500
@@ -576,6 +608,10 @@ def test_enet_cv_positive_constraint():
     assert min(enetcv_constrained.coef_) >= 0
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 def test_uniform_targets():
     enet = ElasticNetCV(n_alphas=3)
     m_enet = MultiTaskElasticNetCV(n_alphas=3)
@@ -673,6 +709,10 @@ def test_enet_multitarget():
         assert_array_almost_equal(dual_gap[k], estimator.dual_gap_)
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 def test_multioutput_enetcv_error():
     rng = np.random.RandomState(0)
     X = rng.randn(10, 2)
@@ -682,6 +722,10 @@ def test_multioutput_enetcv_error():
         clf.fit(X, y)
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 def test_multitask_enet_and_lasso_cv():
     X, y, _, _ = build_dataset(n_features=50, n_targets=3)
     clf = MultiTaskElasticNetCV(cv=3).fit(X, y)
@@ -709,6 +753,10 @@ def test_multitask_enet_and_lasso_cv():
     assert 10 == len(clf.alphas_)
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 def test_1d_multioutput_enet_and_multitask_enet_cv():
     X, y, _, _ = build_dataset(n_features=10)
     y = y[:, np.newaxis]
@@ -722,6 +770,10 @@ def test_1d_multioutput_enet_and_multitask_enet_cv():
     assert_almost_equal(clf.intercept_, clf1.intercept_[0])
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 def test_1d_multioutput_lasso_and_multitask_lasso_cv():
     X, y, _, _ = build_dataset(n_features=10)
     y = y[:, np.newaxis]
@@ -734,6 +786,10 @@ def test_1d_multioutput_lasso_and_multitask_lasso_cv():
     assert_almost_equal(clf.intercept_, clf1.intercept_[0])
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 @pytest.mark.parametrize("csr_container", CSR_CONTAINERS)
 def test_sparse_input_dtype_enet_and_lassocv(csr_container):
     X, y, _, _ = build_dataset(n_features=10)
@@ -1067,6 +1123,10 @@ def test_enet_float_precision():
             )
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 @pytest.mark.filterwarnings("ignore::sklearn.exceptions.ConvergenceWarning")
 def test_enet_l1_ratio():
     # Test that an error message is raised if an estimator that
@@ -1179,6 +1239,10 @@ def test_sparse_input_convergence_warning(csr_container):
         Lasso().fit(csr_container(X, dtype=np.float32), y)
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 @pytest.mark.parametrize(
     "precompute, inner_precompute",
     [
@@ -1204,6 +1268,10 @@ def fit(self, X, y):
     assert calls > 0
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 def test_multi_task_lasso_cv_dtype():
     n_samples, n_features = 10, 3
     rng = np.random.RandomState(42)
@@ -1303,6 +1371,10 @@ def test_enet_sample_weight_consistency(
     assert_allclose(reg1.coef_, reg2.coef_, rtol=1e-6)
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 @pytest.mark.parametrize("fit_intercept", [True, False])
 @pytest.mark.parametrize("sparse_container", [None] + CSC_CONTAINERS)
 def test_enet_cv_sample_weight_correctness(
@@ -1379,6 +1451,10 @@ def test_enet_cv_sample_weight_correctness(
     assert reg_with_weights.intercept_ == pytest.approx(reg_with_repetitions.intercept_)
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 @pytest.mark.parametrize("sample_weight", [False, True])
 def test_enet_cv_grid_search(sample_weight):
     """Test that ElasticNetCV gives same result as GridSearchCV."""
@@ -1414,6 +1490,10 @@ def test_enet_cv_grid_search(sample_weight):
     assert reg.alpha_ == pytest.approx(gs.best_params_["alpha"])
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 @pytest.mark.parametrize("fit_intercept", [True, False])
 @pytest.mark.parametrize("l1_ratio", [0, 0.5, 1])
 @pytest.mark.parametrize("precompute", [False, True])
@@ -1468,6 +1548,10 @@ def test_enet_cv_sample_weight_consistency(
         assert_allclose(reg.intercept_, intercept)
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 @pytest.mark.parametrize("X_is_sparse", [False, True])
 @pytest.mark.parametrize("fit_intercept", [False, True])
 @pytest.mark.parametrize("sample_weight", [np.array([10, 1, 10, 1]), None])
@@ -1488,6 +1572,10 @@ def test_enet_alpha_max_sample_weight(X_is_sparse, fit_intercept, sample_weight)
     assert_array_less(1e-3, np.max(np.abs(reg.coef_)))
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 @pytest.mark.parametrize("estimator", [ElasticNetCV, LassoCV])
 def test_linear_models_cv_fit_with_loky(estimator):
     # LinearModelsCV.fit performs operations on fancy-indexed memmapped
@@ -1622,6 +1710,10 @@ def test_read_only_buffer():
     clf.fit(X, y)
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 @pytest.mark.parametrize(
     "EstimatorCV",
     [ElasticNetCV, LassoCV, MultiTaskElasticNetCV, MultiTaskLassoCV],
@@ -1638,6 +1730,10 @@ def test_cv_estimators_reject_params_with_no_routing_enabled(EstimatorCV):
         estimator.fit(X, y, groups=groups)
 
 
+@pytest.mark.filterwarnings("ignore:`n_alphas` was deprecated in 1.7:FutureWarning")
+@pytest.mark.filterwarnings(
+    "ignore:The default value of `alphas` will change:FutureWarning"
+)
 @pytest.mark.parametrize(
     "MultiTaskEstimatorCV",
     [MultiTaskElasticNetCV, MultiTaskLassoCV],
@@ -1680,3 +1776,89 @@ def split(self, X, y=None, groups=None, sample_weight=None):
     )
     estimator = MultiTaskEstimatorCV(cv=splitter)
     estimator.fit(X, y, sample_weight=sample_weight)
+
+
+@pytest.mark.parametrize(
+    "model, n_alphas",
+    [
+        (LassoCV, 5),
+        (ElasticNetCV, 5),
+        (MultiTaskLassoCV, 5),
+        (MultiTaskElasticNetCV, 5),
+    ],
+)
+def test_LinearModelCV_deprecated_n_alphas(model, n_alphas):
+
+    n_samples, n_features = 100, 3
+    X = np.random.random((n_samples, n_features))
+    y = np.random.random((n_samples))
+
+    mX = np.random.random((n_samples, n_features))
+    my = np.random.random((n_samples, 2))
+
+    msg = (
+        "`n_alphas` was deprecated in 1.7 and will be removed in 1.9"
+        " use `alphas` to set number of alphas values to test along"
+        " regularization path"
+    )
+
+    # Asses warning message raised by LinearModelCV given n_alphas is used
+    with pytest.warns(FutureWarning, match=msg):
+        clf = model(n_alphas=n_alphas)
+        if clf._is_multitask():
+            clf = clf.fit(mX, my)
+        else:
+            clf = clf.fit(X, y)
+
+    # Asses no warning message raised when n_alphas is not used
+    with warnings.catch_warnings():
+        warnings.simplefilter("error")
+        clf = model(alphas=n_alphas)
+        if clf._is_multitask():
+            clf = clf.fit(mX, my)
+        else:
+            clf = clf.fit(X, y)
+
+
+@pytest.mark.parametrize(
+    "model, alphas",
+    [
+        (LassoCV, 5),
+        (LassoCV, [1, 100, 1000]),
+        (ElasticNetCV, 5),
+        (ElasticNetCV, [1, 100, 1000]),
+        (MultiTaskLassoCV, 5),
+        (MultiTaskLassoCV, [1, 100, 1000]),
+        (MultiTaskElasticNetCV, 5),
+        (MultiTaskElasticNetCV, [1, 100, 1000]),
+    ],
+)
+def test_LinearModelCV_warning_default_alphas(model, alphas):
+
+    n_samples, n_features = 100, 3
+    X = np.random.random((n_samples, n_features))
+    y = np.random.random((n_samples))
+    mX = np.random.random((n_samples, n_features))
+    my = np.random.random((n_samples, 2))
+
+    msg = (
+        "The default value of `alphas` will change from None to 100 in 1.7"
+        " Since 1.7, ``alphas`` supports an integer argument which generates"
+        " ``alphas`` number of alpha values along the regularization path."
+    )
+
+    with pytest.warns(FutureWarning, match=msg):
+        clf = model()
+        if clf._is_multitask():
+            clf.fit(mX, my)
+        else:
+            clf.fit(X, y)
+
+    # Asses no warning message raised when value passed into alphas
+    with warnings.catch_warnings():
+        warnings.simplefilter("error")
+        clf = model(alphas=alphas)
+        if clf._is_multitask():
+            clf.fit(mX, my)
+        else:
+            clf.fit(X, y)


Code review comments:
diff_hunk: @@ -1691,39 +1700,61 @@ def fit(self, X, y, sample_weight=None, **params):
             l1_ratios = [
                 1,
             ]
+
         path_params.pop("cv", None)
         path_params.pop("n_jobs", None)
 
-        alphas = self.alphas
+        alphas = None
         n_l1_ratio = len(l1_ratios)
 
+        if self.n_alphas != "deprecated":
+            warnings.warn(
+                "`n_alphas` was deprecated in 1.7 and will be removed in 1.9"
+                " use `alphas` to set number of alphas values to test along"
+                " regularization path",
+                FutureWarning,
+            )
+            self.alphas = self.n_alphas
+
+        # self.alphas is only string with value "warn"
+        # if the default value is used in the constructor
+        if isinstance(self.alphas, str):
+            warnings.warn(
+                "The default value of `alphas` will change from None to 100 in 1.7"
+                " Since 1.7, ``alphas`` supports an integer argument which generates"
+                " ``alphas`` number of alpha values along the regularization path.",
+                FutureWarning,
+            )
+            self.alphas = 100
path: sklearn/linear_model/_coordinate_descent.py
text: @user1:
```suggestion
        n_l1_ratio = len(l1_ratios)

        if self.n_alphas != "deprecated" and self.n_alphas != "warn":
            raise ValueError(
                  "Both `n_alphas` and `alphas` were provided. Please provide only"
                  " `alphas` since `n_alphas` is deprecated and will be removed in"
                  " 1.9. You can now pass an integer to `alphas` to specify the number"
                  " of alpha values to test along the regularization path."
            )

        if self.n_alphas != "deprecated" or self.n_alphas == "warn":
            # We construct one warning for all the cases
            warnings.warn(
                 "`n_alphas` was deprecated in 1.7 and will be removed in 1.9. "
                 "`alphas` now accepts an integer argument as well, which removes "
                 "the need to pass `n_alphas`. The default value of `alphas` will "
                 "change from None to 100 in 1.9. Pass an explicit value to `alphas` "
                 "and don't pass `n_alphas` to silence this warning.",
                 FutureWarning,
            )

        alphas = None
        if self.n_alphas != "deprecated":
            alphas = self.n_alphas
        elif self.alphas == "warn":
            alphas = 100
```

we need to cover the case where both are provided, and raise only one warning.

diff_hunk: @@ -1495,8 +1495,15 @@ class LinearModelCV(MultiOutputMixin, LinearModel, ABC):
 
     _parameter_constraints: dict = {
         "eps": [Interval(Real, 0, None, closed="neither")],
-        "n_alphas": [Interval(Integral, 1, None, closed="left")],
-        "alphas": ["array-like", None],
+        "n_alphas": [
+            Interval(Integral, 1, None, closed="left"),
+            StrOptions({"deprecated"}),
+        ],
+        "alphas": [
+            Interval(Integral, 1, None, closed="left"),
+            "array-like",
+            StrOptions({"warn"}),
path: sklearn/linear_model/_coordinate_descent.py
text: @user1:
```suggestion
            Hidden(StrOptions({"warn"})),
```

diff_hunk: @@ -1495,8 +1495,15 @@ class LinearModelCV(MultiOutputMixin, LinearModel, ABC):
 
     _parameter_constraints: dict = {
         "eps": [Interval(Real, 0, None, closed="neither")],
-        "n_alphas": [Interval(Integral, 1, None, closed="left")],
-        "alphas": ["array-like", None],
+        "n_alphas": [
+            Interval(Integral, 1, None, closed="left"),
+            StrOptions({"deprecated"}),
path: sklearn/linear_model/_coordinate_descent.py
text: @user1:
```suggestion
            Hidden(StrOptions({"deprecated"})),
```

diff_hunk: @@ -0,0 +1,13 @@
+```rst
path: doc/whats_new/upcoming_changes/sklearn.linear_model/30616.api.rst
text: @user1:
Please have a look at the other changelog entries.

You don't need the triple backtick and the `rst` tag here, and the lines need to be indented after the first line. Also, you need to rearrange the changelog into a single bullet point and convey the message in that single point.

diff_hunk: @@ -1514,8 +1521,10 @@ class LinearModelCV(MultiOutputMixin, LinearModel, ABC):
     def __init__(
         self,
         eps=1e-3,
-        n_alphas=100,
-        alphas=None,
+        ############ TODO:
+        ############ Need to change _parameter_constraints dictionary
path: sklearn/linear_model/_coordinate_descent.py
text: @user1:
put a `TODO(1.9): remove deprecated options` where the dictionary exists instead