{"record":{"id":"f827055a4fac44c5","repo":"pola-rs/polars","slug":"percentiles-must-all-be-in-the-range-0-1","errorCode":null,"errorMessage":"`percentiles` must all be in the range [0, 1]","messagePattern":"`percentiles` must all be in the range \\[0, 1\\]","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"py-polars/src/polars/_utils/various.py","lineNumber":603,"sourceCode":"    return False\n\n\ndef parse_percentiles(\n    percentiles: Sequence[float] | float | None, *, inject_median: bool = False\n) -> Sequence[float]:\n    \"\"\"\n    Transforms raw percentiles into our preferred format, adding the 50th percentile.\n\n    Raises a ValueError if the percentile sequence is invalid\n    (e.g. outside the range [0, 1])\n    \"\"\"\n    if isinstance(percentiles, float):\n        percentiles = [percentiles]\n    elif percentiles is None:\n        percentiles = []\n    if not all((0 <= p <= 1) for p in percentiles):\n        msg = \"`percentiles` must all be in the range [0, 1]\"\n        raise ValueError(msg)\n\n    sub_50_percentiles = sorted(p for p in percentiles if p < 0.5)\n    at_or_above_50_percentiles = sorted(p for p in percentiles if p >= 0.5)\n\n    if inject_median and (\n        not at_or_above_50_percentiles or at_or_above_50_percentiles[0] != 0.5\n    ):\n        at_or_above_50_percentiles = [0.5, *at_or_above_50_percentiles]\n\n    return [*sub_50_percentiles, *at_or_above_50_percentiles]\n\n\ndef re_escape(s: str) -> str:\n    \"\"\"Escape a string for use in a Polars (Rust) regex.\"\"\"\n    # note: almost the same as the standard python 're.escape' function, but\n    # escapes _only_ those metachars with meaning to the rust regex crate\n    re_rust_metachars = r\"\\\\?()|\\[\\]{}^$#&~.+*-\"\n    return re.sub(f\"([{re_rust_metachars}])\", r\"\\\\\\1\", s)","sourceCodeStart":585,"sourceCodeEnd":621,"githubUrl":"https://github.com/pola-rs/polars/blob/df599052daf96e7a9cc30a3b0c6bd25d6947e3c0/py-polars/src/polars/_utils/various.py#L585-L621","documentation":"ValueError from parse_percentiles (py-polars/src/polars/_utils/various.py:588-603). LazyFrame.describe(percentiles=...) (and any API routing through parse_percentiles) requires every supplied percentile to be a fraction in the closed interval [0.0, 1.0]; polars then internally injects the median (0.5) and sorts the list. Values outside [0, 1] — most commonly whole-number percentages like 50 or 95 — raise immediately.","triggerScenarios":"lf.describe(percentiles=[0.1, 50, 0.9]); lf.describe(percentiles=95); negative values or values > 1 such as [1.5].","commonSituations":"Copy-pasting percentile integers from configure-style tooling or report specs ('show the 95th percentile'); mixing units with APIs that take 0-100 (some plotting/reporting libs); user-facing knobs forwarded unvalidated.","solutions":["Convert percentages to fractions: divide by 100 ([50, 95] -> [0.5, 0.95])","Drop the value entirely if you meant the median — 0.5 is injected automatically","Validate user input before the call: assert all(0 <= p <= 1 for p in percentiles)"],"exampleFix":"# before\nlf.describe(percentiles=[10, 50, 90])  # ValueError\n\n# after\nlf.describe(percentiles=[0.10, 0.50, 0.90])","handlingStrategy":"validation","validationCode":"def norm_percentiles(ps: list[float]) -> list[float]:\n    out = [p / 100 if p > 1 else p for p in ps]\n    if not all(0.0 <= p <= 1.0 for p in out):\n        raise ValueError('percentiles must be fractions in [0, 1]')\n    return out","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Normalize 0-100 style inputs by dividing by 100 before calling describe","0.5 is auto-injected — don't pass the median explicitly","Validate user-facing percentile settings at the config boundary"],"tags":["polars","describe","percentiles","valueerror"],"backgroundTag":null,"analyzedSha":"df599052daf96e7a9cc30a3b0c6bd25d6947e3c0","analyzedAt":"2026-08-16T12:10:03.978Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}