Leaderboard  /  seaborn-3069

mwaskom__seaborn-3069

mwaskom/seaborn · v0.12  ·  15 steps · 856,669 tokens · 245s
partial
0.979

The task

The bug report the agent was given — plus the gold fix and hidden tests, shown here for reference. The agent saw only the problem statement.

Problem statementgithub issue
Nominal scale should be drawn the same way as categorical scales
Three distinctive things happen on the categorical axis in seaborn's categorical plots:

1. The scale is drawn to +/- 0.5 from the first and last tick, rather than using the normal margin logic
2. A grid is not shown, even when it otherwise would be with the active style
3. If on the y axis, the axis is inverted

It probably makes sense to have `so.Nominal` scales (including inferred ones) do this too. Some comments on implementation:

1. This is actually trickier than you'd think; I may have posted an issue over in matplotlib about this at one point, or just discussed on their gitter. I believe the suggested approach i …
Gold patchheld back · the reference fix
diff --git a/seaborn/_core/plot.py b/seaborn/_core/plot.py--- a/seaborn/_core/plot.py+++ b/seaborn/_core/plot.py@@ -25,7 +25,7 @@ from seaborn._stats.base import Stat from seaborn._core.data import PlotData from seaborn._core.moves import Move-from seaborn._core.scales import Scale+from seaborn._core.scales import Scale, Nominal from seaborn._core.subplots import Subplots from seaborn._core.groupby import GroupBy from seaborn._core.properties import PROPERTIES, Property@@ -1238,7 +1238,6 @@ def _setup_scales( # This only affects us when sharing *paired* axes. This is a novel/niche # behavior, so we will raise rather than hack together a workaround. if axis is not None and Version(mpl.__version__) < Version("3.4.0"):- from seaborn._core.scales import Nominal paired_axis = axis in p._pair_spec.get("structure", {}) cat_scale = isinstance(scale, Nominal) ok_dim = {"x": "col", "y": "row"}[axis]@@ -1631,6 +1630,7 @@ def _finalize_figure(self, p: Plot) -> None: ax = sub["ax"] for axis in "xy": axis_key = sub[axis]+ axis_obj = getattr(ax, f"{axis}axis") # Axis limits if axis_key in p._limits:@@ -1644,6 +1644,17 @@ def _finalize_figure(self, p: Plot) -> None: hi = cast(float, hi) + 0.5 ax.set(**{f"{axis}lim": (lo, hi)}) + # Nominal scale special-casing+ if isinstance(self._scales.get(axis_key), Nominal):+ axis_obj.grid(False, which="both")+ if axis_key not in p._limits:+ nticks = len(axis_obj.get_major_ticks())+ lo, hi = -.5, nticks - .5+ if axis == "y":+ lo, hi = hi, lo+ set_lim = getattr(ax, f"set_{axis}lim")+ set_lim(lo, hi, auto=None)+ engine_default = None if p._target is not None else "tight" layout_engine = p._layout_spec.get("engine", engine_default) set_layout_engine(self._figure, layout_engine)
Hidden testtest_patch · applied only to grade
diff --git a/tests/_core/test_plot.py b/tests/_core/test_plot.py--- a/tests/_core/test_plot.py+++ b/tests/_core/test_plot.py@@ -645,6 +645,28 @@ def test_undefined_variable_raises(self): with pytest.raises(RuntimeError, match=err): p.plot() + def test_nominal_x_axis_tweaks(self):++ p = Plot(x=["a", "b", "c"], y=[1, 2, 3])+ ax1 = p.plot()._figure.axes[0]+ assert ax1.get_xlim() == (-.5, 2.5)+ assert not any(x.get_visible() for x in ax1.xaxis.get_gridlines())++ lim = (-1, 2.1)+ ax2 = p.limit(x=lim).plot()._figure.axes[0]+ assert ax2.get_xlim() == lim++ def test_nominal_y_axis_tweaks(self):++ p = Plot(x=[1, 2, 3], y=["a", "b", "c"])+ ax1 = p.plot()._figure.axes[0]+ assert ax1.get_ylim() == (2.5, -.5)+ assert not any(y.get_visible() for y in ax1.yaxis.get_gridlines())++ lim = (-1, 2.1)+ ax2 = p.limit(y=lim).plot()._figure.axes[0]+ assert ax2.get_ylim() == lim+ class TestPlotting:
0FAIL → PASS (of 2)
94PASS → PASS
94 / 96graded tests

The agent run

The agent's step-by-step attempt — its reasoning, the tools it called, and the patch it produced.

System promptharness.agent.build_system_prompt
You are an AI coding assistant working inside a code repository that contains a bug. Investigate the code, find the bug, and fix it.

Your working directory is: /testbed
All four tools (read_file, write_file, edit_file, run_bash) already run from that directory. Use plain relative paths; do not prefix shell commands with cd. Fix the bug by editing source files only; do not create or edit test files.
Step 01 tool use
Let me understand the bug. The issue is that `Nominal` scale should behave the same way as categorical scales in seaborn's categorical plots. The three behaviors mentioned are: 1. Scale drawn to +/- 0.5 from first and last tick 2. No grid shown 3. Y-axis inverted for nominal scales on y-axis Let me explore the codebase to understand the structure and find where these behaviors are implemented for categorical plots and where `Nominal` scale is defined.
run_bash{ command: "find /testbed -type f -name \"*.py\" | head -50" }
tool result · run_bash
exitcode: 0 stdout: /testbed/ci/cache_datasets.py /testbed/ci/check_gallery.py /testbed/examples/multiple_bivariate_kde.py /testbed/examples/smooth_bivariate_kde.py /testbed/examples/jitter_stripplot.py /testbed/examples/part_whole_bars.py /testbed/examples/faceted_histogram.py /testbed/examples/multiple_conditional_kde.py /testbed/examples/marginal_ticks.py /testbed/examples/spreadsheet_heatmap.py /testbed/examples/structured_heatmap.py /testbed/examples/multiple_regression. …
Step 02 tool use
Let me look for the relevant source files.
run_bash{ command: "find /testbed -type f -name \"*.py\" -path \"*/seaborn/*\" | sort" }
tool result · run_bash
exitcode: 0 stdout: /testbed/seaborn/__init__.py /testbed/seaborn/_compat.py /testbed/seaborn/_core/__init__.py /testbed/seaborn/_core/data.py /testbed/seaborn/_core/groupby.py /testbed/seaborn/_core/moves.py /testbed/seaborn/_core/plot.py /testbed/seaborn/_core/properties.py /testbed/seaborn/_core/rules.py /testbed/seaborn/_core/scales.py /testbed/seaborn/_core/subplots.py /testbed/seaborn/_core/typing.py /testbed/seaborn/_decorators.py /testbed/seaborn/_docstrings.py /testb …
Step 03 tool use
The key files to look at are: - `/testbed/seaborn/_core/scales.py` - where `Nominal` scale should be defined - `/testbed/seaborn/_core/plot.py` - where `Plotter._finalize_figure` is - `/testbed/seaborn/categorical.py` - for understanding how categorical plots work Let me start by reading these files.
read_file{ path: "/testbed/seaborn/_core/scales.py" }
tool result · read_file
from __future__ import annotations import re from copy import copy from collections.abc import Sequence from dataclasses import dataclass from functools import partial from typing import Any, Callable, Tuple, Optional, ClassVar import numpy as np import matplotlib as mpl from matplotlib.ticker import ( Locator, Formatter, AutoLocator, AutoMinorLocator, FixedLocator, LinearLocator, LogLocator, SymmetricalLogLocator, MaxNLocator, Multipl …
read_file{ path: "/testbed/seaborn/_core/plot.py" }
tool result · read_file
"""The classes for specifying and compiling a declarative visualization.""" from __future__ import annotations import io import os import re import sys import inspect import itertools import textwrap from contextlib import contextmanager from collections import abc from collections.abc import Callable, Generator from typing import Any, List, Optional, cast from cycler import cycler import pandas as pd from pandas import DataFrame, Series, Index import matplotlib as mpl from …
read_file{ path: "/testbed/seaborn/categorical.py" }
tool result · read_file
from textwrap import dedent from numbers import Number import warnings from colorsys import rgb_to_hls from functools import partial import numpy as np import pandas as pd try: from scipy.stats import gaussian_kde _no_scipy = False except ImportError: from .external.kde import gaussian_kde _no_scipy = True import matplotlib as mpl from matplotlib.collections import PatchCollection import matplotlib.patches as Patches import matplotlib.pyplot as plt from sea …
Step 04 tool use
Let me now look at the `_oldcore.py` to understand how `scale_categorical` works, since it's called in categorical.py:
read_file{ path: "/testbed/seaborn/_oldcore.py" }
tool result · read_file
import warnings import itertools from copy import copy from functools import partial from collections import UserString from collections.abc import Iterable, Sequence, Mapping from numbers import Number from datetime import datetime import numpy as np import pandas as pd import matplotlib as mpl from ._decorators import ( share_init_params_with_map, ) from .external.version import Version from .palettes import ( QUAL_PALETTES, color_palette, ) from .utils import …
Step 05 tool use
Now I understand the codebase better. Let me focus on the three things that need to happen for `Nominal` scale: 1. **Scale drawn to +/- 0.5 from first and last tick**: In `categorical.py`, the `_adjust_cat_axis` method does this: ```python if axis == "x": ax.xaxis.grid(False) ax.set_xlim(-.5, n - .5, auto=None) else: ax.yaxis.grid(False) ax.set_ylim(n - .5, -.5, auto=None) ``` 2. **No grid on categorical axis**: Also in `_adjust_cat_axis`, `ax.xaxis.grid(False)` or `ax.yaxis.grid(False)`. 3. **Y-axis inverted for categorical y**: In `_attach` method of `VectorPlotter`: ```python if self.var_types.get("y", None) == "categorical": for ax in ax_list: ax.yaxis.set_inverted(True) ``` Now I need to look at how `Nominal` scale is used in `Plotter._finalize_figure` and `Plotter._setup_scales`. Let me check what happens currently. Looking at `_finalize_figure` in plot.py: - It handles axis limits from `p._limits` - It sets layout engine - But it does NOT handle the +/- 0.5 margin for Nominal scales, nor does it handle grid or y-axis inversion. Also in `_setup_scales`, when a `Nominal` scale is used, the `_setup` method creates a `CatScale` which extends `LinearScale` (not actually a categorical scale). But it sets up the axis using `set_scale_obj` which sets the scale object on the axis. The key insight here is: 1. For Nominal scales, the axis limits should be set to `(-0.5, n - 0.5)` (for x) or `(n - 0.5, -0.5)` (for y, inverted). 2. Grid should be turned off for the nominal axis. 3. For nominal y, the axis should be inverted. Let me look at where the Nominal scale is setup and how it integrates. The key file to modify is `plot.py`, specifically the `_finalize_figure` method and potentially `_setup_scales`. Let me also look at how the Nominal scale's `_setup` works to understand what information is available at finalize time. In `Nominal._setup`: - It creates a `CatScale` (extends `LinearScale`) - Sets up `mpl_scale` - The units seed is stored, and the pipeline includes `convert_units` But there's no special handling for the margin or grid. Let me check what `CatScale` does - it's essentially a `LinearScale` with a `set_default_locators_and_formatters` that does nothing (the `...`). Now let me think about where to implement the fix. The three behaviors need to happen: 1. **+/- 0.5 margin**: This should happen in `_finalize_figure` where axis limits are set. We need to detect if the scale for an axis is `Nominal` and set the limits accordingly. 2. **No grid**: Should also happen in `_finalize_figure`. For nominal axes, disable the grid. 3. **Y-axis inversion**: For nominal y axis, invert the axis. Let me look more closely at how scales are tracked in the Plotter to know how to check if a scale is Nominal.
read_file{ path: "/testbed/seaborn/_core/plot.py", offset: "575", limit: "100" }
tool result · read_file
ERROR: bad arguments for 'read_file': Toolbox.read_file() got an unexpected keyword argument 'offset'
Step 06 tool use
Let me read the _finalize_figure method again more carefully.
run_bash{ command: "grep -n \"_finalize_figure\" /testbed/seaborn/_core/plot.py" }
tool result · run_bash
exitcode: 0 stdout: 854: plotter._finalize_figure(self) 1628: def _finalize_figure(self, p: Plot) -> None: stderr:
Step 07 tool use
Let me look at the _finalize_figure method and the surrounding code to understand the full context.
read_file{ path: "/testbed/seaborn/_core/plot.py", offset: "1628", limit: "50" }
tool result · read_file
ERROR: bad arguments for 'read_file': Toolbox.read_file() got an unexpected keyword argument 'offset'
Step 08 tool use
The read_file tool doesn't support offset. Let me use run_bash with sed to get specific line ranges.
run_bash{ command: "sed -n '1628,1670p' /testbed/seaborn/_core/plot.py" }
tool result · run_bash
exitcode: 0 stdout: def _finalize_figure(self, p: Plot) -> None: for sub in self._subplots: ax = sub["ax"] for axis in "xy": axis_key = sub[axis] # Axis limits if axis_key in p._limits: convert_units = getattr(ax, f"{axis}axis").convert_units a, b = p._limits[axis_key] lo = a if a is None else convert_units(a) hi …
Step 09 tool use
Now I need to understand the full picture. The `_finalize_figure` method currently only handles explicit limits set by the user. But for Nominal scales, we need to: 1. Set the limits to `(-0.5, n - 0.5)` for x, or `(n - 0.5, -0.5)` for y (inverted) 2. Disable the grid for the nominal axis 3. Invert the y-axis for nominal y Now let me check what `self._scales` looks like and how to determine if a scale is Nominal.
run_bash{ command: "grep -n \"self._scales\" /testbed/seaborn/_core/plot.py" }
tool result · run_bash
exitcode: 0 stdout: 218: self._scales = {} 291: new._scales.update(self._scales) 881: self._scales: dict[str, Scale] = {} 1110: scales = self._scales.copy() 1186: variables = [v for v in variables if v not in self._scales] 1251: self._scales[var] = Scale._identity() 1253: self._scales[var] = scale._setup(var_df[var], prop) 1420: scales = self._scales.copy() 1422: scales["x"] …
Step 10 tool use
Now let me look at how scales are stored and how to detect Nominal. The `self._scales` dict maps variable names to Scale objects. In `_setup_scales`, after `scale._setup()` is called, the result is stored in `self._scales[var]`. So we can check `isinstance(self._scales.get(axis_key), Nominal)` in `_finalize_figure`. But wait, there's a subtlety. The `_setup` method returns a copy of the scale, and the original scale type is preserved. So we can check `isinstance(self._scales.get(axis_key), Nominal)`. However, there's another issue. The `_finalize_figure` method also needs to know the number of categories to set the limits properly. Let me check what information is available on the Nominal scale after `_setup`. Looking at `Nominal._setup`: - It creates `units_seed` via `categorical_order(data, new.order)` which gives the ordered list of categories. - `new._legend` is set to `units_seed, list(stringify(units_seed))` - The `_matplotlib_scale` is a `CatScale` So after setup, we can find the number of categories from the scale object. But `_legend` is the best place to get the number of categories. Actually, let me reconsider the approach. The `_finalize_figure` method is where things need to happen. Let me think about what needs to change: 1. For Nominal scales on x: set `ax.set_xlim(-0.5, n - 0.5)`, disable x grid 2. For Nominal scales on y: set `ax.set_ylim(n - 0.5, -0.5)`, disable y grid, invert y axis But we also need the number of categories `n`. This can be obtained from the scale's `_legend` tuple, which contains `(units_seed, labels)`. So `len(scale._legend[0])` gives the number of categories. Wait, but there's another issue: the `_legend` might not be set when `prop.legend` is False. Let me check... In `Nominal._setup`: ```python if prop.legend: new._legend = units_seed, list(stringify(units_seed)) ``` So if the property doesn't have a legend, `_legend` stays None. We need another way to get the number of categories. One approach: store `units_seed` (or its length) on the Nominal scale object itself. But we could also compute it from the axis ticks. Actually, looking at the old categorical code in `_adjust_cat_axis`: ```python n = len(getattr(ax, f"get_{axis}ticks")()) ``` This gets the number of ticks from the axis. But that might not be reliable at `_finalize_figure` time. Let me look at what happens with the CatScale. The `CatScale` is set on the axis via `set_scale_obj`. The locator/formatter used is `StrCategoryLocator`/`StrCategoryFormatter`. These should properly set up the ticks. Actually, let me re-read the `Nominal._setup` method more carefully. It creates a `CatScale` that extends `LinearScale` and overrides `set_default_locators_and_formatters` with `...` (which means "do nothing"). So the locators and formatters from `Nominal._get_locators` and `Nominal._get_formatter` are NOT being used! Instead, when `_setup_scales` calls `set_scale_obj(view["ax"], axis, view_scale._matplotlib_scale)`, it sets the scale, and then matplotlib's default locator/formatter logic kicks in. Wait, let me re-read. The `CatScale.name = None` is set to work around mpl<3.4 compat issues. And `set_default_locators_and_formatters` is a no-op. But then in `_setup_scales`: ```python view_scale = scale._setup(seed_values, prop, axis=axis_obj) set_scale_obj(view["ax"], axis, view_scale._matplotlib_scale) ``` The `set_scale_obj` sets the scale on the axis. But the locators and formatters would have been set by `axis_obj.set_major_locator` etc. when `_setup` is called with an actual axis. Let me look at `_setup` again: ```python def _setup(self, data, prop, axis=None): ... mpl_scale = CatScale(data.name) if axis is None: axis = PseudoAxis(mpl_scale) axis.set_view_interval(0, len(units_seed) - 1) new._matplotlib_scale = mpl_scale axis.update_units(stringify(np.array(units_seed))) ... ``` When `axis` is an actual matplotlib axis (which happens in `_setup_scales`), `update_units` is ... [truncated 490 characters]
read_file{ path: "/testbed/seaborn/_compat.py" }
tool result · read_file
import numpy as np import matplotlib as mpl from seaborn.external.version import Version def MarkerStyle(marker=None, fillstyle=None): """ Allow MarkerStyle to accept a MarkerStyle object as parameter. Supports matplotlib < 3.3.0 https://github.com/matplotlib/matplotlib/pull/16692 """ if isinstance(marker, mpl.markers.MarkerStyle): if fillstyle is None: return marker else: marker = marker.get_marker() ret …
Step 11 tool use
Now I see something important. In `set_scale_obj`, when `scale.name is None` (which is the case for `CatScale`), it returns early without actually setting the scale on the axis. This is a hack. So for Nominal scales, the axis scale is not actually being changed to a categorical scale via `set_scale_obj`. But `update_units` is called on the axis in `Nominal._setup`, which should set up the converter and locator/formatter. Now, let me think about what to do practically. Looking at the existing categorical code in `categorical.py`: 1. `_adjust_cat_axis` sets: - `ax.xaxis.grid(False)` / `ax.yaxis.grid(False)` - `ax.set_xlim(-.5, n - .5, auto=None)` / `ax.set_ylim(n - .5, -.5, auto=None)` 2. In `VectorPlotter._attach`: - `ax.yaxis.set_inverted(True)` for categorical y Now for the objects interface with `Nominal` scale, I need to implement similar behavior in `_finalize_figure`. Let me check what information is available at `_finalize_figure` time. I need: - Whether the scale is Nominal (can check `isinstance(self._scales.get(axis_key), Nominal)`) - The number of categories (can get from `self._scales[axis_key]._legend` if it exists) But the `_legend` attribute is only set when `prop.legend` is True. Let me think of a better way. Actually, looking at `Nominal._setup` again, the `units_seed` is used to set up the axis via `axis.update_units(stringify(np.array(units_seed)))`. The number of categories is `len(units_seed)`. We could store this on the scale object. Wait, I can also get the number of ticks from the axis at finalize time, similar to how `_adjust_cat_axis` does: ```python n = len(getattr(ax, f"get_{axis}ticks")()) ``` This should work at finalize time because the axis has already been set up. But actually, there might be a simpler approach. Let me look at how the limits are already handled. The `Nominal._setup` method, when `axis is None` (PseudoAxis), does: ```python axis.set_view_interval(0, len(units_seed) - 1) ``` But when `axis` is a real axis, this doesn't happen. The axis limits are left at their defaults. So the fix should be in `_finalize_figure` to: 1. For Nominal scales, set the axis limits to +/- 0.5 from the first and last tick 2. Disable the grid 3. Invert y-axis for nominal y Let me also check if there's a way to store that a scale is "categorical-like" on the scale object. The cleanest approach would be to: 1. In `Nominal._setup`, store the number of categories (e.g., `new._n_categories = len(units_seed)`) 2. In `_finalize_figure`, check if the scale for each axis is Nominal and apply the three behaviors But I should also consider: what if the user has explicitly set limits? The current code in `_finalize_figure` only sets limits when `axis_key in p._limits`. For Nominal scales, we'd want to set limits even when not explicitly specified, but only if the user hasn't set their own limits. Let me now write the fix.
run_bash{ command: "grep -n \"class Nominal\" /testbed/seaborn/_core/scales.py" }
tool result · run_bash
exitcode: 0 stdout: 138:class Nominal(Scale): stderr:
Step 12 tool use
Let me now look at the Nominal class more carefully, particularly the `_setup` method, and understand how we can store the number of categories for later use.
run_bash{ command: "sed -n '138,250p' /testbed/seaborn/_core/scales.py" }
tool result · run_bash
exitcode: 0 stdout: class Nominal(Scale): """ A categorical scale without relative importance / magnitude. """ # Categorical (convert to strings), un-sortable values: tuple | str | list | dict | None = None order: list | None = None _priority: ClassVar[int] = 3 def _setup( self, data: Series, prop: Property, axis: Axis | None = None, ) -> Scale: new = copy(self) if new._tick_params is None: new = new. …
Step 13 tool use
Now I have a good understanding of the code. Let me plan the changes: 1. **In `scales.py` - `Nominal._setup`**: Store the number of categories (length of `units_seed`) on the scale object so it can be used later. I'll add `new._n_categories = len(units_seed)`. 2. **In `plot.py` - `_finalize_figure`**: Add logic to: - Check if the scale for each axis is a `Nominal` scale - If so, set the axis limits to +/- 0.5 from the first and last tick - Disable the grid on that axis - If on y axis, invert the axis But wait, I need to think about this more carefully. The `_finalize_figure` iterates over subplots and axes. For each axis, it has `axis_key` which is the variable key (e.g., "x", "y", "x0", etc.). The scales are stored in `self._scales` keyed by variable name. So I need to check `self._scales.get(axis_key)`. But there's a complication: the `_scales` dict maps variable names to the Scale objects returned by `_setup`. These are copies of the original scale, so they should preserve the type. I can check `isinstance(self._scales.get(axis_key), Nominal)`. Also, I need to be careful about the order of operations: the grid should be disabled, the limits set, and for y, the axis inverted. But the limits setting should respect user-specified limits if they exist. Let me think about what happens when the user explicitly sets limits. If they set limits, we should probably still disable the grid and invert the y axis, but not override their limits. Let me now implement the changes. First, let me modify `Nominal._setup` to store `_n_categories`:
edit_file{ path: "/testbed/seaborn/_core/scales.py", old_string: " if prop.legend:\n new._legend = units_seed, list(stringify(units_seed))\n\n return new", new_string: " if prop.legend:\n new._legend = units_seed, list(stringify(units_seed))\n\n new._n_categories = len(units_seed)\n\n return new" }
tool result · edit_file
File /testbed/seaborn/_core/scales.py edited successfully.
Step 14 tool use
Now let me modify `_finalize_figure` in `plot.py` to handle Nominal scales. I need to: 1. Import Nominal at the top of plot.py 2. Add the logic in `_finalize_figure` Let me check the imports in plot.py first.
run_bash{ command: "sed -n '1,40p' /testbed/seaborn/_core/plot.py" }
tool result · run_bash
exitcode: 0 stdout: """The classes for specifying and compiling a declarative visualization.""" from __future__ import annotations import io import os import re import sys import inspect import itertools import textwrap from contextlib import contextmanager from collections import abc from collections.abc import Callable, Generator from typing import Any, List, Optional, cast from cycler import cycler import pandas as pd from pandas import DataFrame, Series, Index import ma …
Step 15 tool use
I need to import `Nominal` in `plot.py`. Currently it imports `Scale` from `scales`. I need to also import `Nominal`. Let me update the import. Now let me implement the `_finalize_figure` changes. The key logic: 1. For each axis, check if the scale is a Nominal scale 2. If so: a. Disable grid on that axis b. If on y axis, invert the axis c. If no user-specified limits, set limits to +/- 0.5 from first/last tick Let me look at the current `_finalize_figure` again:
run_bash{ command: "sed -n '1628,1650p' /testbed/seaborn/_core/plot.py" }
tool result · run_bash
exitcode: 0 stdout: def _finalize_figure(self, p: Plot) -> None: for sub in self._subplots: ax = sub["ax"] for axis in "xy": axis_key = sub[axis] # Axis limits if axis_key in p._limits: convert_units = getattr(ax, f"{axis}axis").convert_units a, b = p._limits[axis_key] lo = a if a is None else convert_units(a) hi …
Agent diffcaptured before grading
diff --git a/seaborn/_core/scales.py b/seaborn/_core/scales.pyindex bbd71ec1..382724d1 100644--- a/seaborn/_core/scales.py+++ b/seaborn/_core/scales.py@@ -225,6 +225,8 @@ class Nominal(Scale): if prop.legend: new._legend = units_seed, list(stringify(units_seed)) + new._n_categories = len(units_seed)+ return new def tick(self, locator: Locator | None = None):