Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Frisch-Newton Interior Point Solver for Quantile Regression without Penalization - #32002

Closed
s3alfisc wants to merge 8 commits into
scikit-learn:mainfrom
s3alfisc:main
Closed

Frisch-Newton Interior Point Solver for Quantile Regression without Penalization#32002
s3alfisc wants to merge 8 commits into
scikit-learn:mainfrom
s3alfisc:main

Conversation

@s3alfisc

Copy link
Copy Markdown

Reference Issues/PRs

See this issue. #31708

What does this implement/fix? Explain your changes.

Implements a fast Frisch-Newton Interior Point Solver for Vanilla Quantile Regression without penalization.

@github-actions

github-actions Bot commented Aug 24, 2025

Copy link
Copy Markdown

❌ Linting issues

This PR is introducing linting issues. Here's a summary of the issues. Note that you can avoid having linting issues by enabling pre-commit hooks. Instructions to enable them can be found here.

You can see the details of the linting issues under the lint job here


ruff check

ruff detected issues. Please run ruff check --fix --output-format=full locally, fix the remaining issues, and push the changes. Here you can see the detected issues. Note that the installed ruff version is ruff=0.11.7.

Details

sklearn/linear_model/_quantile.py:262:89: E501 Line too long (91 > 88)
    |
260 |         else:
261 |             if self.solver in ["highs", "highs-ds", "highs-ipm"]:
262 |                 # Note that highs methods always use a sparse CSC memory layout internally,
    |                                                                                         ^^^ E501
263 |                 # even for optimization problems parametrized using dense numpy arrays.
264 |                 # Therefore, we work with CSC matrices as early as possible to limit
    |

sklearn/linear_model/_quantile.py:375:21: RUF013 PEP 484 prohibits implicit `Optional`
    |
373 |     q: float,
374 |     backoff: float = 0.9995,
375 |     solver_options: dict = None,
    |                     ^^^^ RUF013
376 | ):
    |
    = help: Convert to `T | None`

Found 2 errors.
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).

ruff format

ruff detected issues. Please run ruff format locally and push the changes. Here you can see the detected issues. Note that the installed ruff version is ruff=0.11.7.

Details

--- sklearn/linear_model/_quantile.py
+++ sklearn/linear_model/_quantile.py
@@ -348,7 +348,6 @@
 
 
 def _cold_start(A: np.ndarray, c: np.ndarray, q: float):
-
     "Set starting values for the Frisch-Newton algorithm."
     n = A.shape[1]
     x = np.full(n, 1.0 - q)
@@ -374,7 +373,6 @@
     backoff: float = 0.9995,
     solver_options: dict = None,
 ):
-
     """
     Implements the Frisch-Newton Interior Point Solver for Quantile Regression
     following Koenker and Ng (2005, https://link.springer.com/article/10.1007/s10255-005-0231-1).

1 file would be reformatted, 925 files already formatted

mypy

mypy detected issues. Please fix them locally and push the changes. Here you can see the detected issues. Note that the installed mypy version is mypy=1.15.0.

Details

sklearn/linear_model/_quantile.py:375: error: Incompatible default for argument "solver_options" (default has type "None", argument has type "dict[Any, Any]")  [assignment]
sklearn/linear_model/_quantile.py:375: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
sklearn/linear_model/_quantile.py:375: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
Found 1 error in 1 file (checked 565 source files)

Generated for commit: 9a5238d. Link to the linter CI: here

@ogrisel ogrisel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a shallow first pass of review. Please also update the existing tests to include the new solver (or extend them to deal with the fact that it only accept alpha=0).

Please also add a changelog entry as explained here:

https://github.com/scikit-learn/scikit-learn/blob/main/doc/whats_new/upcoming_changes/README.md

BTW, do you think this solver could be generalized to handle non-zero l2 or (l1 + l2) regularization?

@@ -46,7 +46,11 @@ class QuantileRegressor(LinearModel, RegressorMixin, BaseEstimator):
solver : {'highs-ds', 'highs-ipm', 'highs', 'interior-point', \
'revised simplex'}, default='highs'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to also add the new method to the list of possible values above.

)

if self.solver == "frisch-newton" and alpha != 0:
raise ValueError("Frisch-Newton solver does not support alpha != 0.")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please recall the actual value of alpha in the error message.

Comment thread sklearn/linear_model/_quantile.py Outdated
if not success:
warnings.warn(
f"The 'frisch-newton' solver did not converge after {max_iter} iterations."
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This warning should be an instance of sklearn.exceptions.ConvergenceWarning.

"""
if solver_options is None:
solver_options = {}
tol = solver_options.get("tol", 1e-7)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to instead tol as an estimator parameter directly, as is customary for many scikit-learn estimators. We might also want to make this tolerance criterion independent of data scale if this is not already the case. Since the convergence is related to a duality gap, we could have a look at how it's done in ElasticNet and co.

Co-authored-by: Olivier Grisel <[email protected]>
@s3alfisc

Copy link
Copy Markdown
Author

Thanks for taking a first look at this @ogrisel, I'll work on this within the next days!


"""
Implements the Frisch-Newton Interior Point Solver for Quantile Regression
following Koenker and Ng (2005, https://link.springer.com/article/10.1007/s10255-005-0231-1).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the Paper is behind a paywall, it would be nice to give a short derivation, like in

# After rescaling alpha, the minimization problem is
# min sum(pinball loss) + alpha * L1
# Use linear programming formulation of quantile regression
# min_x c x
# A_eq x = b_eq
# 0 <= x
# x = (s0, s, t0, t, u, v) = slack variables >= 0
# intercept = s0 - t0
# coef = s - t
# c = (0, alpha * 1_p, 0, alpha * 1_p, quantile * 1_n, (1-quantile) * 1_n)
# residual = y - X@coef - intercept = u - v
# A_eq = (1_n, X, -1_n, -X, diag(1_n), -diag(1_n))
# b_eq = y
# p = n_features
# n = n_samples
# 1_n = vector of length n with entries equal one
# see https://stats.stackexchange.com/questions/384909/


params, _it = frisch_newton_solver(
A=X.T,
b=(1 - self.quantile) * X.T @ np.ones(n_indices),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
b=(1 - self.quantile) * X.T @ np.ones(n_indices),
b=(1 - self.quantile) * X.sum(axis=0),

A_eq = sparse.hstack([X, -X, eye, -eye], format="csc")
else:
eye = np.eye(n_indices)
if self.solver == "frisch-newton":

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self.solver == "frisch-newton":
if self.solver == "frisch-newton":

Could you add frisch-newton after the linprog? This way the comment around line 202/210 makes more sense and the FN-solver gets closer to its implementation.

Comment on lines +351 to +352

"Set starting values for the Frisch-Newton algorithm."

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Set starting values for the Frisch-Newton algorithm."
"""Set starting values for the Frisch-Newton algorithm."""

return min(_bound(x, dx, backoff), _bound(s, ds, backoff))


def _cold_start(A: np.ndarray, c: np.ndarray, q: float):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def _cold_start(A: np.ndarray, c: np.ndarray, q: float):
def _cold_start(A, c, q):

I guess it's fine without type annotations. A can be sparse, so ndarray is not the correct type.



def frisch_newton_solver(
A: np.ndarray,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
A: np.ndarray,
A: np.ndarray,

Again annotations, at least A can be sparse.


mu_curr = _duality_gap(x=x, z=z, s=s, w=w)

for _it in range(max_iter):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for _it in range(max_iter):
for it in range(max_iter):

or just i.

@lorentzenchr lorentzenchr added the autoclose PR automatically closed 14 days after setting the label label Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

⏰ This pull request might be automatically closed in two weeks from now.

Thank you for your contribution to scikit-learn and for the effort you have put into this PR. This pull request does not yet meet the quality and clarity needed for an effective review. Project maintainers have limited time for code reviews, and our goal is to prioritize well-prepared contributions to keep scikit-learn maintainable.

To increase the chance of a productive review, please refer to: How do I improve my issue or pull request? As the author, you are responsible for driving this PR, which entails doing necessary background research as well as presenting its context and your thought process. If you are a new contributor, or do not know how to fulfill these requirements, we recommend that you familiarise yourself with scikit-learn's development conventions via other contribution types (e.g., reviewing PRs) before submitting code.

Scikit-learn maintainers cannot provide one-to-one guidance on this PR. However, if you ask focused, well-researched questions, a community member may be willing to help. 💬

If you substantially improve this PR within two weeks, a team member may remove the autoclose label and the PR stays open. Cosmetic changes or incomplete fixes will not be sufficient. Maintainers will assess improvements on their own schedule. Please do not ping (@) maintainers.

@s3alfisc s3alfisc closed this Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autoclose PR automatically closed 14 days after setting the label module:linear_model

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants