Repository: matplotlib/matplotlib

Issue:
[Bug]: Typo in method name: contains_branch_separately
### Bug summary

There is a typo in the method name `contains_branch_seperately` from `lib/matplotlib/transforms.py`: https://github.com/matplotlib/matplotlib/blob/a3fcf0b7c4e5270de3865ca97d23751c8a1c5b2d/lib/matplotlib/transforms.py#L1421

This should be spelled `contains_branch_separately` (`separate` not `seperate`).

Since this is a public API method, I propose:
1. Rename the method to `contains_branch_separately` (correct spelling)
2. Create an alias from the old `contains_branch_seperately` spelling to the new method for backwards compatibility
3. Add a deprecation warning when the old name is used
4. Update any internal uses and documentation to use the correct spelling

Happy to prepare a PR for this if that makes sense!

### Code for reproduction

```Python
# Copied from lib/matplotlib/transforms.py

    def contains_branch_seperately(self, other_transform):
        """
        Return whether the given branch is a sub-tree of this transform on
        each separate dimension.

        A common use for this method is to identify if a transform is a blended
        transform containing an Axes' data transform. e.g.::

            x_isdata, y_isdata = trans.contains_branch_seperately(ax.transData)

        """
        if self.output_dims != 2:
            raise ValueError('contains_branch_seperately only supports '
                             'transforms with 2 output dimensions')
        # for a non-blended transform each separate dimension is the same, so
        # just return the appropriate shape.
        return (self.contains_branch(other_transform), ) * 2
```

### Actual outcome

Code runs as expected, but method name contains a typo.

### Expected outcome

Method name should be correctly spelled as `contains_branch_separately`

### Additional information

_No response_

### Operating system

_No response_

### Matplotlib Version

Current `main` branch, as of a3fcf0b (25 August 2025)

### Matplotlib Backend

_No response_

### Python version

_No response_

### Jupyter version

_No response_

### Installation

None


Discussion:
It has been misspelled for 13 years (it does not look like this is a US vs UK difference) without notice so I'm not sure of the practical cost of leaving it as-is.

Did you find this because you spelled it correctly and get a name error or via a tool that searches for misspellings in function names?

My current thinking is that this is technically wrong, but mostly harmless (function names are full of nonsense words and tab-complete hides a lot of sins) where as if we go through deprecation cycle there is a chance we will break someone due to a completely non-functional API change.

The most I would want to do is correct the spelling, add the the misspelling as a copy, fix internal usage, and only document the correct spelling in the docs, but leave the misspelling there without warning forever.
I found it after having PR #30286 merged, which corrected a whitespace error, so it got me hunting for other misspellings and things of that nature.

I agree that it’s not the highest priority thing in the world and also appreciate the friction that deprecations introduce. Your proposed approach sounds very reasonable:

1. Use `contains_branch_separately` (correct spelling) as the primary method
2. Keep `contains_branch_seperately` as a permanent alias without deprecation warning
3. Update internal usage to the correct spelling
4. Document only the correct spelling

Something like this?

```diff
-    def contains_branch_seperately(self, other_transform):
+    def contains_branch_separately(self, other_transform):
        """
        Return whether the given branch is a sub-tree of this transform on
        each separate dimension.

        A common use for this method is to identify if a transform is a blended
        transform containing an Axes' data transform. e.g.::

-            x_isdata, y_isdata = trans.contains_branch_seperately(ax.transData)
+            x_isdata, y_isdata = trans.contains_branch_separately(ax.transData)

        """
        if self.output_dims != 2:
-            raise ValueError('contains_branch_seperately only supports '
+            raise ValueError('contains_branch_separately only supports '
                             'transforms with 2 output dimensions')
        # for a non-blended transform each separate dimension is the same, so
        # just return the appropriate shape.
        return (self.contains_branch(other_transform), ) * 2
+
+# Permanent alias for backwards compatibility (historical typo)
+contains_branch_seperately = contains_branch_separately
```

(Plus relevant documentation fixes.)

Happy to prepare a PR if this sounds reasonable to you.
Yes, I would review and merge a PR that did that.
I've prepared a PR for your review here: #30475.



Patch:
diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py
index f0bc139bdc11..6da0e925ab4f 100644
--- a/lib/matplotlib/axes/_axes.py
+++ b/lib/matplotlib/axes/_axes.py
@@ -1183,7 +1183,7 @@ def hlines(self, y, xmin, xmax, colors=None, linestyles='solid',
             if self.name == "rectilinear":
                 datalim = lines.get_datalim(self.transData)
                 t = lines.get_transform()
-                updatex, updatey = t.contains_branch_seperately(self.transData)
+                updatex, updatey = t.contains_branch_separately(self.transData)
                 minx = np.nanmin(datalim.xmin)
                 maxx = np.nanmax(datalim.xmax)
                 miny = np.nanmin(datalim.ymin)
@@ -1275,7 +1275,7 @@ def vlines(self, x, ymin, ymax, colors=None, linestyles='solid',
             if self.name == "rectilinear":
                 datalim = lines.get_datalim(self.transData)
                 t = lines.get_transform()
-                updatex, updatey = t.contains_branch_seperately(self.transData)
+                updatex, updatey = t.contains_branch_separately(self.transData)
                 minx = np.nanmin(datalim.xmin)
                 maxx = np.nanmax(datalim.xmax)
                 miny = np.nanmin(datalim.ymin)
@@ -6809,7 +6809,7 @@ def _update_pcolor_lims(self, collection, coords):
                 hasattr(t, '_as_mpl_transform')):
             t = t._as_mpl_transform(self.axes)
 
-        if t and any(t.contains_branch_seperately(self.transData)):
+        if t and any(t.contains_branch_separately(self.transData)):
             trans_to_data = t - self.transData
             coords = trans_to_data.transform(coords)
 
diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py
index fa628b3f34e0..42f6e51786bf 100644
--- a/lib/matplotlib/axes/_base.py
+++ b/lib/matplotlib/axes/_base.py
@@ -2380,9 +2380,9 @@ def add_collection(self, collection, autolim=True):
             # only update the dataLim for x/y if the collection uses transData
             # in this direction.
             x_is_data, y_is_data = (collection.get_transform()
-                                    .contains_branch_seperately(self.transData))
+                                    .contains_branch_separately(self.transData))
             ox_is_data, oy_is_data = (collection.get_offset_transform()
-                                      .contains_branch_seperately(self.transData))
+                                      .contains_branch_separately(self.transData))
             self.update_datalim(
                 points,
                 updatex=x_is_data or ox_is_data,
@@ -2451,7 +2451,7 @@ def _update_line_limits(self, line):
 
         if line_trf == self.transData:
             data_path = path
-        elif any(line_trf.contains_branch_seperately(self.transData)):
+        elif any(line_trf.contains_branch_separately(self.transData)):
             # Compute the transform from line coordinates to data coordinates.
             trf_to_data = line_trf - self.transData
             # If transData is affine we can use the cached non-affine component
@@ -2474,7 +2474,7 @@ def _update_line_limits(self, line):
         if not data_path.vertices.size:
             return
 
-        updatex, updatey = line_trf.contains_branch_seperately(self.transData)
+        updatex, updatey = line_trf.contains_branch_separately(self.transData)
         if self.name != "rectilinear":
             # This block is mostly intended to handle axvline in polar plots,
             # for which updatey would otherwise be True.
@@ -2527,7 +2527,7 @@ def _update_patch_limits(self, patch):
             vertices = np.vstack(vertices)
 
         patch_trf = patch.get_transform()
-        updatex, updatey = patch_trf.contains_branch_seperately(self.transData)
+        updatex, updatey = patch_trf.contains_branch_separately(self.transData)
         if not (updatex or updatey):
             return
         if self.name != "rectilinear":
diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py
index ec6d40805da0..684e15cdf854 100644
--- a/lib/matplotlib/collections.py
+++ b/lib/matplotlib/collections.py
@@ -283,7 +283,7 @@ def get_datalim(self, transData):
 
         offsets = self.get_offsets()
 
-        if any(transform.contains_branch_seperately(transData)):
+        if any(transform.contains_branch_separately(transData)):
             # collections that are just in data units (like quiver)
             # can properly have the axes limits set by their shape +
             # offset.  LineCollections that have no offsets can
diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py
index 643bfce4273a..1125b9c5fbc6 100644
--- a/lib/matplotlib/contour.py
+++ b/lib/matplotlib/contour.py
@@ -1333,7 +1333,7 @@ def _process_args(self, *args, corner_mask=None, algorithm=None, **kwargs):
             # if the transform is not trans data, and some part of it
             # contains transData, transform the xs and ys to data coordinates
             if (t != self.axes.transData and
-                    any(t.contains_branch_seperately(self.axes.transData))):
+                    any(t.contains_branch_separately(self.axes.transData))):
                 trans_to_data = t - self.axes.transData
                 pts = np.vstack([x.flat, y.flat]).T
                 transformed_pts = trans_to_data.transform(pts)
diff --git a/lib/matplotlib/transforms.py b/lib/matplotlib/transforms.py
index 350113c56170..a22a2f7db070 100644
--- a/lib/matplotlib/transforms.py
+++ b/lib/matplotlib/transforms.py
@@ -1418,7 +1418,7 @@ def contains_branch(self, other):
                 return True
         return False
 
-    def contains_branch_seperately(self, other_transform):
+    def contains_branch_separately(self, other_transform):
         """
         Return whether the given branch is a sub-tree of this transform on
         each separate dimension.
@@ -1426,16 +1426,21 @@ def contains_branch_seperately(self, other_transform):
         A common use for this method is to identify if a transform is a blended
         transform containing an Axes' data transform. e.g.::
 
-            x_isdata, y_isdata = trans.contains_branch_seperately(ax.transData)
+            x_isdata, y_isdata = trans.contains_branch_separately(ax.transData)
 
         """
         if self.output_dims != 2:
-            raise ValueError('contains_branch_seperately only supports '
+            raise ValueError('contains_branch_separately only supports '
                              'transforms with 2 output dimensions')
         # for a non-blended transform each separate dimension is the same, so
         # just return the appropriate shape.
         return (self.contains_branch(other_transform), ) * 2
 
+    # Permanent alias for backwards compatibility (historical typo)
+    def contains_branch_seperately(self, other_transform):
+        """:meta private:"""
+        return self.contains_branch_separately(other_transform)
+
     def __sub__(self, other):
         """
         Compose *self* with the inverse of *other*, cancelling identical terms
@@ -2185,7 +2190,7 @@ def __eq__(self, other):
         else:
             return NotImplemented
 
-    def contains_branch_seperately(self, transform):
+    def contains_branch_separately(self, transform):
         return (self._x.contains_branch(transform),
                 self._y.contains_branch(transform))
 
@@ -2411,14 +2416,14 @@ def _iter_break_from_left_to_right(self):
         for left, right in self._b._iter_break_from_left_to_right():
             yield self._a + left, right
 
-    def contains_branch_seperately(self, other_transform):
+    def contains_branch_separately(self, other_transform):
         # docstring inherited
         if self.output_dims != 2:
-            raise ValueError('contains_branch_seperately only supports '
+            raise ValueError('contains_branch_separately only supports '
                              'transforms with 2 output dimensions')
         if self == other_transform:
             return (True, True)
-        return self._b.contains_branch_seperately(other_transform)
+        return self._b.contains_branch_separately(other_transform)
 
     depth = property(lambda self: self._a.depth + self._b.depth)
     is_affine = property(lambda self: self._a.is_affine and self._b.is_affine)
diff --git a/lib/matplotlib/transforms.pyi b/lib/matplotlib/transforms.pyi
index 07d299be297c..4e582904d1d8 100644
--- a/lib/matplotlib/transforms.pyi
+++ b/lib/matplotlib/transforms.pyi
@@ -189,9 +189,10 @@ class Transform(TransformNode):
     @property
     def depth(self) -> int: ...
     def contains_branch(self, other: Transform) -> bool: ...
-    def contains_branch_seperately(
+    def contains_branch_separately(
         self, other_transform: Transform
     ) -> Sequence[bool]: ...
+    contains_branch_seperately = contains_branch_separately  # Alias (historical typo)
     def __sub__(self, other: Transform) -> Transform: ...
     def __array__(self, *args, **kwargs) -> np.ndarray: ...
     def transform(self, values: ArrayLike) -> np.ndarray: ...
@@ -252,7 +253,7 @@ class IdentityTransform(Affine2DBase): ...
 
 class _BlendedMixin:
     def __eq__(self, other: object) -> bool: ...
-    def contains_branch_seperately(self, transform: Transform) -> Sequence[bool]: ...
+    def contains_branch_separately(self, transform: Transform) -> Sequence[bool]: ...
 
 class BlendedGenericTransform(_BlendedMixin, Transform):
     input_dims: Literal[2]


Test patch:
diff --git a/lib/matplotlib/tests/test_transforms.py b/lib/matplotlib/tests/test_transforms.py
index b4db34db5a91..dbff0e1ba39f 100644
--- a/lib/matplotlib/tests/test_transforms.py
+++ b/lib/matplotlib/tests/test_transforms.py
@@ -694,9 +694,9 @@ def test_contains_branch(self):
         assert not self.stack1.contains_branch(self.tn1 + self.ta2)
 
         blend = mtransforms.BlendedGenericTransform(self.tn2, self.stack2)
-        x, y = blend.contains_branch_seperately(self.stack2_subset)
+        x, y = blend.contains_branch_separately(self.stack2_subset)
         stack_blend = self.tn3 + blend
-        sx, sy = stack_blend.contains_branch_seperately(self.stack2_subset)
+        sx, sy = stack_blend.contains_branch_separately(self.stack2_subset)
         assert x is sx is False
         assert y is sy is True