nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: dask/dask
Pull request #11684 (2025-01-20 20:53:23)

Fixup dataframe conversion from array methods
- [ ] Closes #xxxx
- [ ] Tests added / passed
- [ ] Passes `pre-commit run --all-files`

Looks like we didn't have a single test hitting this :(


Patch:
diff --git a/dask/array/core.py b/dask/array/core.py
index 78e16b0dd..00e522918 100644
--- a/dask/array/core.py
+++ b/dask/array/core.py
@@ -5999,11 +5999,11 @@ def new_da_object(dsk, name, chunks, meta=None, dtype=None):
     Decides the appropriate output class based on the type of `meta` provided.
     """
     if is_dataframe_like(meta) or is_series_like(meta) or is_index_like(meta):
-        from dask.dataframe.core import new_dd_object
+        from dask.dataframe import from_graph
 
         assert all(len(c) == 1 for c in chunks[1:])
         divisions = [None] * (len(chunks[0]) + 1)
-        return new_dd_object(dsk, name, meta, divisions)
+        return from_graph(dict(dsk), meta, divisions, dsk.layers[name].keys(), name)
     else:
         return Array(dsk, name=name, chunks=chunks, meta=meta, dtype=dtype)
 
diff --git a/dask/dataframe/dask_expr/_collection.py b/dask/dataframe/dask_expr/_collection.py
index 3d4994825..8622e20c0 100644
--- a/dask/dataframe/dask_expr/_collection.py
+++ b/dask/dataframe/dask_expr/_collection.py
@@ -4972,10 +4972,18 @@ def from_array(arr, chunksize=50_000, columns=None, meta=None):
     return new_collection(result)
 
 
-def from_graph(*args, **kwargs):
+def from_graph(layer, _meta, divisions, keys, name_prefix):
     from dask.dataframe.dask_expr.io.io import FromGraph
 
-    return new_collection(FromGraph(*args, **kwargs))
+    return new_collection(
+        FromGraph(
+            layer=layer,
+            _meta=_meta,
+            divisions=divisions,
+            keys=keys,
+            name_prefix=name_prefix,
+        )
+    )
 
 
 @dataframe_creation_dispatch.register_inplace("pandas")


Test patch:
diff --git a/dask/dataframe/tests/test_dataframe.py b/dask/dataframe/tests/test_dataframe.py
index 49bf7a2ab..e9716e420 100644
--- a/dask/dataframe/tests/test_dataframe.py
+++ b/dask/dataframe/tests/test_dataframe.py
@@ -5343,3 +5343,13 @@ def test_dataframe_into_delayed():
     result = delayed(delayed_func)(df)
     assert sum(map(len, result.dask.layers.values())) == 6
     result.compute()
+
+
+def test_array_to_df_conversion():
+    def foo(arr):
+        return pd.DataFrame(arr)
+
+    arr = da.random.random((100, 10), chunks=(10, 10))
+    result = arr.map_blocks(foo, meta=pd.DataFrame(np.random.random((1, 10))))
+    expected = pd.DataFrame(arr.compute())
+    assert_eq(result, expected, check_index=False)
Repository: dask/dask
Pull request #11702 (2025-01-28 12:55:48)

Fix normalize_chunks when squashing into a single chunk
- [ ] Closes #xxxx
- [ ] Tests added / passed
- [ ] Passes `pre-commit run --all-files`


github-actions[bot]: ## Unit Test Results
_See [test report](https://dask.github.io/dask/test_report.html) for an extended history of previous test failures. This is useful for diagnosing flaky tests._

     15 files  ± 0       15 suites  ±0   4h 25m 59s ⏱️ + 1m 36s
 17 144 tests + 1   15 964 ✅ + 1   1 180 💤 ±0  0 ❌ ±0 
211 250 runs  +13  194 227 ✅ +11  17 023 💤 +2  0 ❌ ±0 

Results for commit fa897523. ± Comparison against base commit 4761ccb3.

[test-results]:data:application/gzip;base64,H4sIADbcmGcC/03MSQ7DIAyF4atErLvADkPoZSogIKFmqAisot69hGZa/p+ttxIfBreQZwP80ZAlh3RVn6NOYZ5qK64KlWuqdwmMHf1asrX/J3HDd/hsCB09zeswFLvAxTjHXWKetmkEQH70Oa0Yojxxn5YU28Nu07Xvy3Yex5BKEK87JTm2ThhhqGU9Zd470VPhrEM0AIpLqg35/gA6LwmCGgEAAA==


Patch:
diff --git a/dask/array/core.py b/dask/array/core.py
index 00e522918..1172129ff 100644
--- a/dask/array/core.py
+++ b/dask/array/core.py
@@ -3349,6 +3349,7 @@ def auto_chunks(chunks, shape, limit, dtype, previous_chunks=None):
                     chunks[a] = shape[a]
                     multiplier_remaining = _trivial_aggregate(a)
                     largest_block *= shape[a]
+                    result[a] = (shape[a],)
                     continue
                 elif reduce_case or max(previous_chunks[a]) > max_chunk_size:
                     result[a] = round_to(proposed, ideal_shape[a])


Test patch:
diff --git a/dask/array/tests/test_array_core.py b/dask/array/tests/test_array_core.py
index bf2171d00..7851c4b25 100644
--- a/dask/array/tests/test_array_core.py
+++ b/dask/array/tests/test_array_core.py
@@ -5275,6 +5275,15 @@ def test_nbytes_auto():
         normalize_chunks(("10B", "10B"), shape=(10, 10), limit=20, dtype="float64")
 
 
+def test_auto_chunks():
+    chunks = ((1264, 1264, 1264, 1264, 1264, 1264, 1045), (1264, 491))
+    shape = sum(chunks[0]), sum(chunks[1])
+    result = normalize_chunks(
+        ("auto", "auto"), shape=shape, dtype="int32", previous_chunks=chunks
+    )
+    assert result == ((8629,), (1755,))
+
+
 def test_auto_chunks_h5py():
     h5py = pytest.importorskip("h5py")
Repository: dask/dask
Pull request #11703 (2025-01-28 13:32:11)

Update Dask Cloud Provider documentation to include Nebius as a supported cloud option
- [ ] Closes #xxxx
- [ ] Tests added / passed
- [ ] Passes `pre-commit run --all-files`



Patch:
diff --git a/dask/array/core.py b/dask/array/core.py
index 00e522918..1172129ff 100644
--- a/dask/array/core.py
+++ b/dask/array/core.py
@@ -3349,6 +3349,7 @@ def auto_chunks(chunks, shape, limit, dtype, previous_chunks=None):
                     chunks[a] = shape[a]
                     multiplier_remaining = _trivial_aggregate(a)
                     largest_block *= shape[a]
+                    result[a] = (shape[a],)
                     continue
                 elif reduce_case or max(previous_chunks[a]) > max_chunk_size:
                     result[a] = round_to(proposed, ideal_shape[a])
diff --git a/docs/source/deploying.rst b/docs/source/deploying.rst
index 4d65e48cd..61f409ee5 100644
--- a/docs/source/deploying.rst
+++ b/docs/source/deploying.rst
@@ -103,7 +103,7 @@ Deploying on commercial cloud like AWS, GCP, or Azure is convenient because you
        )
        client = cluster.get_client()
 
-- `Dask Cloud Provider <https://cloudprovider.dask.org/en/latest/>`_: a pure and simple OSS solution that sets up Dask workers on cloud VMs, supporting AWS, GCP, Azure, and also other commercial clouds like Hetzner and Digital Ocean.
+- `Dask Cloud Provider <https://cloudprovider.dask.org/en/latest/>`_: a pure and simple OSS solution that sets up Dask workers on cloud VMs, supporting AWS, GCP, Azure, and also other commercial clouds like Hetzner, Digital Ocean and Nebius.
 
 - `Dask-Yarn <https://yarn.dask.org>`_: deploys Dask on legacy YARN clusters, such as can be set up with AWS EMR or Google Cloud Dataproc.
 


Test patch:
diff --git a/dask/array/tests/test_array_core.py b/dask/array/tests/test_array_core.py
index bf2171d00..7851c4b25 100644
--- a/dask/array/tests/test_array_core.py
+++ b/dask/array/tests/test_array_core.py
@@ -5275,6 +5275,15 @@ def test_nbytes_auto():
         normalize_chunks(("10B", "10B"), shape=(10, 10), limit=20, dtype="float64")
 
 
+def test_auto_chunks():
+    chunks = ((1264, 1264, 1264, 1264, 1264, 1264, 1045), (1264, 491))
+    shape = sum(chunks[0]), sum(chunks[1])
+    result = normalize_chunks(
+        ("auto", "auto"), shape=shape, dtype="int32", previous_chunks=chunks
+    )
+    assert result == ((8629,), (1755,))
+
+
 def test_auto_chunks_h5py():
     h5py = pytest.importorskip("h5py")
Repository: dask/dask
Pull request #11782 (2025-02-25 11:59:05)

Test `dask.dataframe.__all__`
- [ ] Related to #8853
- [x] Tests added / passed
- [x] Passes `pre-commit run --all-files`

This also adds an export for `dask.dataframe.demo`, which was missing from `__all__` and caught by the test.

As requested in https://github.com/dask/dask/pull/11780#pullrequestreview-2640643172 by @phofl


Patch:
diff --git a/dask/dataframe/__init__.py b/dask/dataframe/__init__.py
index eafd2e304..bc58569cd 100644
--- a/dask/dataframe/__init__.py
+++ b/dask/dataframe/__init__.py
@@ -98,6 +98,7 @@ __all__ = [
     "merge",
     "merge_asof",
     "pivot_table",
+    "demo",
     "read_csv",
     "read_fwf",
     "read_hdf",


Test patch:
diff --git a/dask/dataframe/tests/test_api.py b/dask/dataframe/tests/test_api.py
new file mode 100644
index 000000000..7079421ba
--- /dev/null
+++ b/dask/dataframe/tests/test_api.py
@@ -0,0 +1,33 @@
+from __future__ import annotations
+
+from types import ModuleType
+
+import pytest
+
+pytest.importorskip("pandas")
+
+DA_EXPORTED_SUBMODULES = {"backends", "dispatch", "demo"}
+
+
+def test_api():
+    """Tests that `dask.array.__all__` is correct"""
+    import dask.dataframe as ddf
+
+    member_dict = vars(ddf)
+    members = set(member_dict)
+    # unexported submodules
+    members -= {"tests", "dask"}
+    members -= {
+        m
+        for m, mod in member_dict.items()
+        if m not in DA_EXPORTED_SUBMODULES
+        if isinstance(mod, ModuleType)
+        and mod.__package__
+        and mod.__package__.startswith("dask.dataframe")
+    }
+    # imported utility modules
+    members -= {"annotations"}
+    # private utilities and `__dunder__` members
+    members -= {m for m in members if m.startswith("_")}
+
+    assert set(ddf.__all__) == members
Repository: dask/dask
Pull request #11786 (2025-02-26 15:25:41)

Fix assign align for duplicated divisions
- [ ] Closes #xxxx
- [ ] Tests added / passed
- [ ] Passes `pre-commit run --all-files`


github-actions[bot]: ## Unit Test Results
_See [test report](https://dask.github.io/dask/test_report.html) for an extended history of previous test failures. This is useful for diagnosing flaky tests._

      9 files  ±0        9 suites  ±0   3h 30m 8s ⏱️ -36s
 17 804 tests +1   16 589 ✅ ±0   1 214 💤 ±0  1 ❌ +1 
159 310 runs  +9  147 153 ✅ +8  12 156 💤 ±0  1 ❌ +1 

For more details on these failures, see [this check](https://github.com/dask/dask/runs/37864851835).

Results for commit 9b793aeb. ± Comparison against base commit f1c2b8a1.

[test-results]:data:application/gzip;base64,H4sIABk7v2cC/02MzQ6DIBAGX8Vw7gEWVti+TAOICak/DcKp6bsXTUSPM9/ufNkYp7CxZ0ePjm0l5gZDSTbHdakooOemqjrmfRbacHXyayve77JHQ5d8x8/xKa7D0cZpd02ElNZUDa8mleVII0lxcksrLVA22dLYn+6WPvhe9us8x1yBkdMkbXCDMEaBRoVWOw40SjAEqMEKCNJx9vsDAXxC6BgBAAA=


Patch:
diff --git a/dask/dataframe/dask_expr/_expr.py b/dask/dataframe/dask_expr/_expr.py
index 763f07a1b..adfadfbd1 100644
--- a/dask/dataframe/dask_expr/_expr.py
+++ b/dask/dataframe/dask_expr/_expr.py
@@ -3493,6 +3493,7 @@ class MaybeAlignPartitions(Expr):
                 dfs[0].divisions == df.divisions and df.known_divisions for df in dfs
             )
             or len(self.divisions) == 2
+            and max(map(lambda x: len(x.divisions), dfs)) == 2
         ):
             return self._expr_cls(*self.operands)
         elif self.divisions[0] is None:


Test patch:
diff --git a/dask/dataframe/dask_expr/tests/test_collection.py b/dask/dataframe/dask_expr/tests/test_collection.py
index 6f241ff38..bbd7fb43f 100644
--- a/dask/dataframe/dask_expr/tests/test_collection.py
+++ b/dask/dataframe/dask_expr/tests/test_collection.py
@@ -2808,3 +2808,15 @@ def test_projection_on_series():
     result = df.replace(1, 5)
     assert_eq(result, pdf.replace(1, 5))
     assert_eq(result.fillna(0), pdf.replace(1, 5).fillna(0))
+
+
+def test_align_known_divisions_in_assign():
+    df = pd.DataFrame({"a": [1, 2], 1: [5, 6]})
+    ddf = from_pandas(df, npartitions=2)
+    ddf = ddf.assign(c=df["a"])
+    assert ddf.npartitions == 1
+    ddf = ddf.repartition(npartitions=1)
+    assert ddf.optimize().npartitions == 1
+    df = df.assign(c=df["a"])
+    assert_eq(ddf, df)
+    assert_eq(ddf.optimize(), df)
Repository: dask/dask
Pull request #11787 (2025-02-26 16:10:29)

Fix divisions calculation with duplicates
- [ ] Closes #xxxx
- [ ] Tests added / passed
- [ ] Passes `pre-commit run --all-files`



Patch:
diff --git a/dask/dataframe/dask_expr/_expr.py b/dask/dataframe/dask_expr/_expr.py
index 763f07a1b..02a57624e 100644
--- a/dask/dataframe/dask_expr/_expr.py
+++ b/dask/dataframe/dask_expr/_expr.py
@@ -3493,6 +3493,7 @@ class MaybeAlignPartitions(Expr):
                 dfs[0].divisions == df.divisions and df.known_divisions for df in dfs
             )
             or len(self.divisions) == 2
+            and max(map(lambda x: len(x.divisions), dfs)) == 2
         ):
             return self._expr_cls(*self.operands)
         elif self.divisions[0] is None:
@@ -3653,6 +3654,7 @@ class OpAlignPartitions(MaybeAlignPartitions):
             len(dfs) == 1
             or all(dfs[0].divisions == df.divisions for df in dfs)
             or len(self.divisions) == 2
+            and max(map(lambda x: len(x.divisions), dfs)) == 2
         ):
             return self._op(self.frame, self.op, self.other, *self.operands[3:])
 
@@ -4022,6 +4024,8 @@ def calc_divisions_for_align(*exprs, allow_shuffle=True):
     dfs = [df for df in exprs if isinstance(df, Expr) and df.ndim > 0]
     if not all(df.known_divisions for df in dfs):
         return (None,) * (max(df.npartitions for df in dfs) + 1)
+    if all(dfs[0].divisions == df.divisions for df in dfs):
+        return dfs[0].divisions
     divisions = list(unique(merge_sorted(*[df.divisions for df in dfs])))
     if len(divisions) == 1:  # single value for index
         divisions = (divisions[0], divisions[0])


Test patch:
diff --git a/dask/dataframe/dask_expr/tests/test_collection.py b/dask/dataframe/dask_expr/tests/test_collection.py
index 6f241ff38..bbd7fb43f 100644
--- a/dask/dataframe/dask_expr/tests/test_collection.py
+++ b/dask/dataframe/dask_expr/tests/test_collection.py
@@ -2808,3 +2808,15 @@ def test_projection_on_series():
     result = df.replace(1, 5)
     assert_eq(result, pdf.replace(1, 5))
     assert_eq(result.fillna(0), pdf.replace(1, 5).fillna(0))
+
+
+def test_align_known_divisions_in_assign():
+    df = pd.DataFrame({"a": [1, 2], 1: [5, 6]})
+    ddf = from_pandas(df, npartitions=2)
+    ddf = ddf.assign(c=df["a"])
+    assert ddf.npartitions == 1
+    ddf = ddf.repartition(npartitions=1)
+    assert ddf.optimize().npartitions == 1
+    df = df.assign(c=df["a"])
+    assert_eq(ddf, df)
+    assert_eq(ddf.optimize(), df)
diff --git a/dask/dataframe/tests/test_dataframe.py b/dask/dataframe/tests/test_dataframe.py
index 4f8c7e5b3..55e448357 100644
--- a/dask/dataframe/tests/test_dataframe.py
+++ b/dask/dataframe/tests/test_dataframe.py
@@ -4511,6 +4511,9 @@ def test_map_partition_sparse():
 def test_mixed_dask_array_operations():
     df = pd.DataFrame({"x": [1, 2, 3]}, index=[4, 5, 6])
     ddf = dd.from_pandas(df, npartitions=2)
+    assert (ddf.x + ddf.x.values).npartitions >= (
+        ddf.x + ddf.x.values
+    ).optimize().npartitions
 
     assert_eq(df.x + df.x.values, ddf.x + ddf.x.values)
     assert_eq(df.x.values + df.x, ddf.x.values + ddf.x)
Repository: dask/dask
Pull request #11788 (2025-02-26 17:00:26)

Speed up normalize_pickle by 50 percent
This code is pretty nasty as it is. There is also a bug that affects the happy path since `cloudpickle` is *always* called once even if pickle works flawlessly. This change should speed up tokenize of trivial objects by 50%


Patch:
diff --git a/dask/tokenize.py b/dask/tokenize.py
index b0f0a5a77..9c7af3cc3 100644
--- a/dask/tokenize.py
+++ b/dask/tokenize.py
@@ -235,8 +235,10 @@ def _normalize_pickle(o: object) -> tuple:
     buffers: list[pickle.PickleBuffer] = []
     pik: int | None = None
     pik2: int
-
+    success = False
     for mod in [pickle, cloudpickle]:
+        if success:
+            break
         for _ in range(3):
             buffers.clear()
             try:
@@ -246,6 +248,7 @@ def _normalize_pickle(o: object) -> tuple:
             except Exception:
                 break
             if pik == pik2:
+                success = True
                 break
             pik = pik2
         else:


Test patch:
diff --git a/dask/tests/test_tokenize.py b/dask/tests/test_tokenize.py
index 4bf3ca224..6e1c165e8 100644
--- a/dask/tests/test_tokenize.py
+++ b/dask/tests/test_tokenize.py
@@ -1440,6 +1440,23 @@ def test_tokenize_pandas_arrow_strings():
     assert any(str(tok) == "string" for tok in flatten(tokens))
 
 
+class APickleable:
+    counter = 0
+
+    def __reduce__(self):
+        APickleable.counter += 1
+        return APickleable, ()
+
+
+def test_normalize_pickle():
+    a = APickleable()
+    tokenize(a)
+    # We're pickling multiple times because pickle is caching things on
+    # instances such that subsequent pickles can yield different results.
+    # For a trivial object like this, this should only happen twice
+    assert APickleable.counter <= 2
+
+
 def test_tokenize_recursive_respects_ensure_deterministic():
     class Foo:
         def __dask_tokenize__(self):
Repository: dask/dask
Pull request #11792 (2025-02-27 11:12:51)

Ensure map_blocks generates unique tokens
Tokenization of map_blocks is not always unique. At the very least for the branch for enforce_ndim this is not true but the tokenization also didn't include all arguments.




Patch:
diff --git a/dask/array/blockwise.py b/dask/array/blockwise.py
index c1e7704b4..1d2a063b7 100644
--- a/dask/array/blockwise.py
+++ b/dask/array/blockwise.py
@@ -246,7 +246,18 @@ def blockwise(
     if not out:
         out = "{}-{}".format(
             token or utils.funcname(func).strip("_"),
-            base.tokenize(func, out_ind, argindsstr, dtype, **kwargs),
+            base.tokenize(
+                func,
+                out_ind,
+                argindsstr,
+                adjust_chunks,
+                new_axes,
+                align_arrays,
+                concatenate,
+                meta,
+                dtype,
+                **kwargs,
+            ),
         )
 
     graph = core_blockwise(
diff --git a/dask/array/core.py b/dask/array/core.py
index 41c22b802..c42c1536b 100644
--- a/dask/array/core.py
+++ b/dask/array/core.py
@@ -815,7 +815,7 @@ def map_blocks(
         )
         name = token
 
-    name = f"{name or funcname(func)}-{tokenize(func, dtype, chunks, drop_axis, new_axis, *args, **kwargs)}"
+    token = f"{name or funcname(func)}"
     new_axes = {}
 
     if isinstance(drop_axis, Number):
@@ -885,7 +885,7 @@ def map_blocks(
             *concat(argpairs),
             expected_ndim=len(out_ind),
             _func=func,
-            name=name,
+            token=token,
             new_axes=new_axes,
             dtype=dtype,
             concatenate=True,
@@ -899,7 +899,7 @@ def map_blocks(
             func,
             out_ind,
             *concat(argpairs),
-            name=name,
+            token=token,
             new_axes=new_axes,
             dtype=dtype,
             concatenate=True,


Test patch:
diff --git a/dask/array/tests/test_array_core.py b/dask/array/tests/test_array_core.py
index dbc79c970..c894ca695 100644
--- a/dask/array/tests/test_array_core.py
+++ b/dask/array/tests/test_array_core.py
@@ -1857,6 +1857,16 @@ def test_map_blocks_no_array_args():
     assert_eq(x, np.arange(8, dtype=np.float32))
 
 
+def test_map_blocks_unique_name_enforce_dim():
+    def func(some_3d, block_info=None):
+        return some_3d
+
+    input_arr = da.zeros((3, 4, 5), chunks=((3,), (4,), (5,)), dtype=np.float32)
+    x = da.map_blocks(func, input_arr, enforce_ndim=True, dtype=np.float32)
+    y = da.map_blocks(func, input_arr, enforce_ndim=False, dtype=np.float32)
+    assert x._name != y._name
+
+
 def test_map_blocks_unique_name_chunks_dtype():
     def func(block_info=None):
         loc = block_info[None]["array-location"]
Repository: dask/dask
Pull request #11846 (2025-03-24 13:42:03)

Do not inject median in describe for pandas 3
Our CI upstream build is currently failing. 

We're intentionally appending the median to be consistent with pandas

```python
# Behavior stable
In [1]: import pandas as pd
In [2]: pd.DataFrame({"A": range(10)}).describe(percentiles=[0.2, 0.6])
Out[2]:
              A
count  10.00000
mean    4.50000
std     3.02765
min     0.00000
20%     1.80000
50%     4.50000
60%     5.40000
max     9.00000
```

the dev version is no longer returning the 50% quantil if it wasn't provided. I'm not sure if this was an intentional change in pandas since I couldn't find anything mentioning this in the changelog

cc @TomAugspurger 

fjetter: This behavior is described in https://github.com/pandas-dev/pandas/issues/11866 but the issue isn't closed

Patch:
diff --git a/dask/_expr.py b/dask/_expr.py
index f79da9424..dfb1d9093 100644
--- a/dask/_expr.py
+++ b/dask/_expr.py
@@ -176,7 +176,7 @@ class Expr:
             object.__setattr__(self, name, value)
             return
         try:
-            params = object.__getattribute__(type(self), "_parameters")
+            params = type(self)._parameters
             operands = object.__getattribute__(self, "operands")
             operands[params.index(name)] = value
         except ValueError:
diff --git a/dask/dataframe/dask_expr/_describe.py b/dask/dataframe/dask_expr/_describe.py
index 03452edb1..0f9fe42d4 100644
--- a/dask/dataframe/dask_expr/_describe.py
+++ b/dask/dataframe/dask_expr/_describe.py
@@ -5,6 +5,7 @@ import functools
 import numpy as np
 from pandas.core.dtypes.common import is_datetime64_any_dtype, is_timedelta64_dtype
 
+from dask.dataframe._compat import PANDAS_GE_300
 from dask.dataframe.dask_expr._expr import (
     Blockwise,
     DropnaSeries,
@@ -43,7 +44,8 @@ class DescribeNumeric(Reduction):
             percentiles = self.percentiles or [0.25, 0.5, 0.75]
         else:
             percentiles = np.array(self.percentiles)
-            percentiles = np.append(percentiles, 0.5)
+            if not PANDAS_GE_300:
+                percentiles = np.append(percentiles, 0.5)
             percentiles = np.unique(percentiles)
             percentiles = list(percentiles)
 


Test patch:
diff --git a/dask/tests/test_expr.py b/dask/tests/test_expr.py
index f7f146915..2907e5e5a 100644
--- a/dask/tests/test_expr.py
+++ b/dask/tests/test_expr.py
@@ -5,12 +5,26 @@ import pytest
 from dask._expr import Expr
 
 
-def test_setattr():
-    class MyExpr(Expr):
-        _parameters = ["foo", "bar"]
+class MyExpr(Expr):
+    _parameters = ["foo", "bar"]
+
+
+class MyExpr2(MyExpr):
+    # A subclass that inherits parameters
+    pass
 
+
+def test_setattr():
     e = MyExpr(foo=1, bar=2)
     e.bar = 3
     assert e.bar == 3
     with pytest.raises(AttributeError):
         e.baz = 4
+
+
+def test_setattr2():
+    e = MyExpr2(foo=1, bar=2)
+    e.bar = 3
+    assert e.bar == 3
+    with pytest.raises(AttributeError):
+        e.baz = 4