Skip to content

ENH: add quantile #340

Open

Description

@lucyleeow

It was decided not to add quantile to array-api (data-apis/array-api#795), partly because scipy added array API support to their quantile (scipy/scipy#22352) and partly due to complications (many methods, weight support, nan support).

This RFC is mostly motivated by usage in scikit-learn (as seen from data-apis/array-api#795 (comment) its not used much in other packages).

Although scipy does have an array API quantile, due to our min scipy version, we are currently not able to use it (e.g., scikit-learn/scikit-learn#31671).

More generally, it seems to be a commonly enough used function (which is implemented many array libraries) to warrant considering adding it to here, and having a consistent, single source.

Not sure who would be interested but maybe:
cc @betatim @ogrisel @lorentzenchr (and @rgommers who suggested it)

Activity

  1. lorentzenchr commented on Jul 10, 2025

    @lorentzenchr

    For quantiles, you can have look at my blog post https://lorentzen.ch/index.php/2023/02/11/quantiles-and-their-estimation/.
    For weighted quantiles, I did not find any peer reviewed reference. Therefore, the only certain connection I find is via the (weighted) empirical CDF.

  2. lucascolley commented on Jul 10, 2025

    @lucascolley
    Member

    There was scipy/scipy#22794 (comment), we can ping Matt if that would be helpful. I can't find the further discussions that I can remember right now.

  3. lucyleeow commented on Jul 14, 2025

    @lucyleeow
    Author

    Therefore, the only certain connection I find is via the (weighted) empirical CDF.

    Sorry to clarify, does that mean we should normalize weights (to n) ? Have looked into how normalizing/not normalizing affects weighted quantile results?

  4. lorentzenchr commented on Jul 14, 2025

    @lorentzenchr

    Therefore, the only certain connection I find is via the (weighted) empirical CDF.

    Sorry to clarify, does that mean we should normalize weights (to n) ? Have looked into how normalizing/not normalizing affects weighted quantile results?

    For a weighted CDF, the scale of the weights does not matter, only cumsum/total sum matters. Weights are therefore usually rescaled such that np.sum(weights) == 1.
    You then find this behaviour (independence of scale of weights / invariance under multiplicative constant of weights) in np.quantile(.., weights=.., method="inverted_cdf").

  5. lucyleeow commented on Jul 14, 2025

    @lucyleeow
    Author

    Excuse my ignorance, but in regard to the different types of weights (frequency, sampling, analytical), how 'should' they be handled differently with regards to calculating weighted quantile then..?

  6. lorentzenchr commented on Jul 14, 2025

    @lorentzenchr

    I find the sheer mention of different types of weights very unfortunate in the context of quantile estimation. The already linked post Weights in statistics explains pretty well that if you estimate the mean, different assumptions lead to different estimates of the variance of the estimated mean.
    For quantile estimation, we are not talking about an estimation of the variance of the estimated quantile.

  7. ogrisel commented on Oct 3, 2025

    @ogrisel
    Contributor

    Related: adding argpartition support to array-api-extra should make it possible to implement O(n) complexity weighted percentiles with array API support:

  8. cakedev0 commented on Oct 3, 2025

    @cakedev0
    Contributor

    Hum not just weighted percentile: for non-weighted percentile you need partition too for a O(n) implementation (I think?).

    Edit: Typically, current scipy implementation of quantile is based on xp.sort: https://github.com/scipy/scipy/blob/b1296b9b4393e251511fe8fdd3e58c22a1124899/scipy/stats/_quantile.py#L57

  9. ogrisel commented on Oct 6, 2025

    @ogrisel
    Contributor

    Maybe we can first finalize the PR and its review in scikit-learn and then decide where to contribute this code upstream? Both array-api-extra and SciPy would naturally make sense.

  10. lucyleeow commented on Oct 21, 2025

    @lucyleeow
    Author

    @cakedev0 following on the discussion in #341 here, as I think it may be a better fit.

    It may have been mentioned in #341 but @betatim and I have discussed maybe only (initially) adding one quantile method, for the reasons;

    WDYT @ogrisel ?

  11. cakedev0 commented on Oct 21, 2025

    @cakedev0
    Contributor

    Thank you for this detailed overview of the situation! It's incredibly helpful!

    Based on that, here are the steps I propose:

    1. only support method="linear" and propagate NaNs. This way, we can delegate for all major array libs (including dask and jax).
    2. add support for methods inverted_cdf and average_inverted_cdf and weights. Before doing that, I think it would maybe make sense to add the support for method average_inverted_cdf with weights in numpy.

    And from there, we can either add the support for more methods and/or the support for nan_policy="raise"|"omit".


    Note:

    scipy quantile seems to support torch on CPU and GPU but not jax or dask

    Indeed, you have this decorator above scipy's quantile function:

    @xp_capabilities(skip_backends=(("dask.array", "No take_along_axis yet."),
                                    ("jax.numpy", "No mutation.")))

    For the first step, we'll delegate anyway so we don't care. And for weights support, we'll see, no take_along_axis seems hard to deal with.

  12. ogrisel commented on Oct 21, 2025

    @ogrisel
    Contributor

    Note: dask is focused on very large virtual arrays that span multiple nodes on a cluster of machines. Therefore, dask developers might be reluctant to support operations that are fundamentally not scalable, like sorting arrays and doing big data shuffle.

    For such large arrays, people would probably rely on approximate quantiles (either via subsampling or dedicated sketching algorithms) instead of computing exact quantiles.

  13. lucyleeow commented on Oct 21, 2025

    @lucyleeow
    Author

    only support method="linear" and propagate NaNs. This way, we can delegate for all major array libs (including dask and jax).

    Not sure if delegating is possible, it relies on all packages having consistent behaviour, which we'd need to check. Just taking a quick look at tensorflow tfp.stats.quantiles - and they take num_quantiles, instead of q.

    Also it seems that scipy and numpy axis behaviour also differs: scipy/scipy#23832

  14. cakedev0 commented on Oct 21, 2025

    @cakedev0
    Contributor

    I think tensorflow is not a supported backend of array-api-extra (see https://github.com/data-apis/array-api-extra/blob/main/src/array_api_extra/_lib/_backends.py).
    I already wrote the delegation code for numpy, cupy, torch, dask and jax, and based on the docs, it should be fine (but we'll see if the tests pass).

    Also it seems that scipy and numpy axis behaviour also differs: scipy/scipy#23832

    Ah, thanks for the pointer, I've been scratching my head on this broadcasting thing for a while, this should help ^^

  15. lucyleeow commented on Oct 21, 2025

    @lucyleeow
    Author

    think tensorflow is not a supported backend of array-api-extra

    Ah interesting, I'm sure they had a good reason. Tensorflow is listed as a stakeholder (in array API standard): https://data-apis.org/array-api/latest/purpose_and_scope.html#stakeholders

    If we aren't interested in tensorflow, then seems reasonable, depending on how much tweaking is required for each individual package (as this may be difficult to maintain going forward). Do you want to open a draft PR and we can take a look?

  16. lucyleeow commented on Oct 21, 2025

    @lucyleeow
    Author

    Ah interesting, I'm sure they had a good reason

    Out of interest, why exclude tensorflow @lucascolley ?

  17. lucascolley commented on Oct 21, 2025

    @lucascolley
    Member

    I don't think we are concerned with supporting tensorflow in array-api-extra at least, since it isn't supported by array-api-compat: https://data-apis.org/array-api-compat/supported-array-libraries.html. I'm missing historical context for the relationship between tensorflow and the consortium.

  18. lucyleeow commented on Oct 21, 2025

    @lucyleeow
    Author

    I've been scratching my head on this broadcasting thing for a while,

    For a general guide to broadcasting, this is nice: https://quansight-labs.github.io/ndindex/indexing-guide/other-topics.html#broadcasting

  19. cakedev0 commented on Oct 21, 2025

    @cakedev0
    Contributor

    Sorry, what I meant by "broadcasting thing" was not broadcasting in general but what was done in numpy/scipy for the quantile function related to broadcasting.

    In numpy, there is an error: ValueError: q must be a scalar or 1d. (but it's a bit weird, because it works with 2D q, see issue above).

    So I propose to support only scalar and 1d for q. This should be enough for what is needed in scikit-learn right? And it would make the implementation quite easier I think.

  20. lucascolley commented on Oct 21, 2025

    @lucascolley
    Member

    yes, a good rule of thumb is to only implement a sensible subset of the APIs based on what we need, for more complex functions.

  21. cakedev0 commented on Oct 21, 2025

    @cakedev0
    Contributor

    Ok great! I should be able to make my draft PR readable and tested soon 👍

  22. cakedev0 commented on Oct 22, 2025

    @cakedev0
    Contributor

    I'm extending the scope of my PR. In the end, I prefer doing most of it in one-go to reduce context switchs (and I feel this will be true for reviewers too), because it's not so hard now that I have a clear understanding of the scope and the desired shapes/broadcasting behavior.

    New scope:

    • support for weights
    • support for methods: "linear", "inverted_cdf", "averaged_inverted_cdf"
    • NaNs: unclear yet, but I think that sklearn needs something like nan_policy="omit" for weighted quantile. And usual behavior is nan_policy="propagte" (numpy's behavior for instance). So I might implement both. Or just always omit. What do you think?
  23. lucyleeow commented on Oct 23, 2025

    @lucyleeow
    Author

    A thought (which may or may not be much of a concern): will it be odd that we only support 'linear' if the array library is one of the supported backends but 'average_inverted_cdf' (and not 'linear') otherwise?

    I does mean when writing code, I probably wouldn't use 'linear' because it's not as widely supported as 'average_inverted_cdf'.

    We could of course add support for 'linear', but in that case, we may as well add support for the other continuous methods as well.

    But I am not 100% we have full agreement that limited support of method(s) would be the way to go, WDYT @betatim @ogrisel and maybe @lesteve

  24. cakedev0 commented on Oct 23, 2025

    @cakedev0
    Contributor

    will it be odd that we only support 'linear' if the array library is one of the supported backends but 'average_inverted_cdf' (and not 'linear') otherwise?

    I'm not sure to understand what you mean. For me, the case "otherwise" is empty, because in my understanding "otherwise" means no array-api-compat and hence no ways to get a working xp.

  25. lucyleeow commented on Oct 23, 2025

    @lucyleeow
    Author

    Ah I may have some mis-understanding. In array API extra, are the 'extra' functions ONLY for array libraries supported by array-api-compat?
    I thought they were meant to be written general enough to support 'any' library that supports the standard?

  26. cakedev0 commented on Oct 23, 2025

    @cakedev0
    Contributor

    Hum... My understanding is that any library that supports the standard can be trivially added to array-api-compat so I think it's fair to assume that the set "libraries that support the standard but aren't in array-api-compat" is empty.

    But that's just my understanding, so I'll let @lucascolley or someone else confirm.

  27. cakedev0 commented on Oct 23, 2025

    @cakedev0
    Contributor

    I thought they were meant to be written general enough to support 'any' library that supports the standard?

    They are. They are tested against array_api_strict and array_api_strictest (unless explicitly skipped, a few tests skip strictest, but none skip strict I think).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions