From 5b50319febf92ec0e37383dc3b5308d8092e1b44 Mon Sep 17 00:00:00 2001 From: aakriti Date: Fri, 3 Oct 2025 17:12:01 -0400 Subject: [PATCH 1/7] Added legend_loc parameter to plot function --- pandas/plotting/_matplotlib/core.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index b8f59363b5107..62d991b520e49 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -173,6 +173,7 @@ def __init__( stacked: bool = False, label: Hashable | None = None, style=None, + legend_loc: str | tuple[float, float] ='best', **kwds, ) -> None: # if users assign an empty list or tuple, raise `ValueError` @@ -241,6 +242,8 @@ def __init__( self.legend = legend self.legend_handles: list[Artist] = [] self.legend_labels: list[Hashable] = [] + self.legend_loc = legend_loc + self.logx = type(self)._validate_log_kwd("logx", logx) self.logy = type(self)._validate_log_kwd("logy", logy) @@ -909,7 +912,8 @@ def _make_legend(self) -> None: title = self.legend_title if len(handles) > 0: - ax.legend(handles, labels, loc="best", title=title) + # ax.legend(handles, labels, loc="best", title=title) + ax.legend(handles, labels, loc=self.legend_loc, title=title) elif self.subplots and self.legend: for ax in self.axes: @@ -920,7 +924,8 @@ def _make_legend(self) -> None: "No artists with labels found to put in legend.", UserWarning, ) - ax.legend(loc="best") + # ax.legend(loc="best") + ax.legend(loc=self.legend_loc) @final @staticmethod From 65d1c25da280f1e3e5733ff0fa9ba6f58129bdbd Mon Sep 17 00:00:00 2001 From: aakriti Date: Mon, 6 Oct 2025 20:32:11 -0400 Subject: [PATCH 2/7] chore: ruff format --- pandas/plotting/_matplotlib/core.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index 62d991b520e49..6133b613d3fca 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -173,7 +173,7 @@ def __init__( stacked: bool = False, label: Hashable | None = None, style=None, - legend_loc: str | tuple[float, float] ='best', + legend_loc: str | tuple[float, float] = "best", **kwds, ) -> None: # if users assign an empty list or tuple, raise `ValueError` @@ -244,7 +244,6 @@ def __init__( self.legend_labels: list[Hashable] = [] self.legend_loc = legend_loc - self.logx = type(self)._validate_log_kwd("logx", logx) self.logy = type(self)._validate_log_kwd("logy", logy) self.loglog = type(self)._validate_log_kwd("loglog", loglog) From 804ef8229ba50f0358b0e27ffa423580344ac4da Mon Sep 17 00:00:00 2001 From: aakriti Date: Mon, 6 Oct 2025 22:21:46 -0400 Subject: [PATCH 3/7] Added tests for legend_loc --- .../tests/plotting/frame/test_frame_legend.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/pandas/tests/plotting/frame/test_frame_legend.py b/pandas/tests/plotting/frame/test_frame_legend.py index 755293e0bf6d7..535014e19782b 100644 --- a/pandas/tests/plotting/frame/test_frame_legend.py +++ b/pandas/tests/plotting/frame/test_frame_legend.py @@ -11,6 +11,7 @@ _check_legend_labels, _check_legend_marker, _check_text_labels, + _check_plot_works, ) mpl = pytest.importorskip("matplotlib") @@ -260,3 +261,55 @@ def test_missing_markers_legend_using_style(self): _check_legend_labels(ax, labels=["A", "B", "C"]) _check_legend_marker(ax, expected_markers=[".", ".", "."]) + + def test_no_legend_when_legend_false_legend_loc(self): + df = DataFrame({"a": [1, 2, 3]}) + ax = _check_plot_works(df.plot, legend=False, legend_loc="upper right") + assert ax.get_legend() is None + + def test_df_legend_labels_secondary_y_legend_loc(self): + pytest.importorskip("scipy") + df = DataFrame(np.random.default_rng(2).random((3, 3)), columns=["a", "b", "c"]) + df2 = DataFrame( + np.random.default_rng(2).random((3, 3)), columns=["d", "e", "f"] + ) + df3 = DataFrame( + np.random.default_rng(2).random((3, 3)), columns=["g", "h", "i"] + ) + + # preserves "best" behaviour if no legend_loc specified + ax = df.plot(legend=True, secondary_y="b", legend_loc=None) + _check_legend_labels(ax, labels=["a", "b (right)", "c"]) + ax = df2.plot(legend=False, ax=ax) + _check_legend_labels(ax, labels=["a", "b (right)", "c"]) + ax = df3.plot(kind="bar", legend=True, secondary_y="h", legend_loc="upper right", ax=ax) + _check_legend_labels(ax, labels=["a", "b (right)", "c", "g", "h (right)", "i"]) + + @pytest.mark.parametrize( + "kind, labels", + [ + ("line", ["a", "b"]), + ("bar", ["a", "b"]), + ("scatter", ["b"]), # scatter(x="a", y="b") usually labels the y series + ]) + def test_across_kinds_legend_loc(self, kind, labels): + df = DataFrame({"a": [1, 2, 3], "b": [3, 2, 1]}) + if kind == "scatter": + ax = _check_plot_works( + df.plot, + kind="scatter", + x="a", + y="b", + label="b", + legend=True, + legend_loc="upper right" + ) + else: + ax = _check_plot_works( + df.plot, + kind=kind, + legend=True, + legend_loc="upper right" + ) + _check_legend_labels(ax, labels=labels) + assert ax.get_legend() is not None \ No newline at end of file From 5a924a3a596a186b7a35f90d796057e19edf9603 Mon Sep 17 00:00:00 2001 From: aakriti Date: Mon, 6 Oct 2025 22:26:45 -0400 Subject: [PATCH 4/7] ruff formatting --- .../tests/plotting/frame/test_frame_legend.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/pandas/tests/plotting/frame/test_frame_legend.py b/pandas/tests/plotting/frame/test_frame_legend.py index 535014e19782b..f3f92877f5662 100644 --- a/pandas/tests/plotting/frame/test_frame_legend.py +++ b/pandas/tests/plotting/frame/test_frame_legend.py @@ -282,16 +282,19 @@ def test_df_legend_labels_secondary_y_legend_loc(self): _check_legend_labels(ax, labels=["a", "b (right)", "c"]) ax = df2.plot(legend=False, ax=ax) _check_legend_labels(ax, labels=["a", "b (right)", "c"]) - ax = df3.plot(kind="bar", legend=True, secondary_y="h", legend_loc="upper right", ax=ax) + ax = df3.plot( + kind="bar", legend=True, secondary_y="h", legend_loc="upper right", ax=ax + ) _check_legend_labels(ax, labels=["a", "b (right)", "c", "g", "h (right)", "i"]) @pytest.mark.parametrize( - "kind, labels", - [ - ("line", ["a", "b"]), - ("bar", ["a", "b"]), - ("scatter", ["b"]), # scatter(x="a", y="b") usually labels the y series - ]) + "kind, labels", + [ + ("line", ["a", "b"]), + ("bar", ["a", "b"]), + ("scatter", ["b"]), # scatter(x="a", y="b") usually labels the y series + ], + ) def test_across_kinds_legend_loc(self, kind, labels): df = DataFrame({"a": [1, 2, 3], "b": [3, 2, 1]}) if kind == "scatter": @@ -302,14 +305,11 @@ def test_across_kinds_legend_loc(self, kind, labels): y="b", label="b", legend=True, - legend_loc="upper right" + legend_loc="upper right", ) else: ax = _check_plot_works( - df.plot, - kind=kind, - legend=True, - legend_loc="upper right" - ) + df.plot, kind=kind, legend=True, legend_loc="upper right" + ) _check_legend_labels(ax, labels=labels) - assert ax.get_legend() is not None \ No newline at end of file + assert ax.get_legend() is not None From 5171dc44cb74fe09cfbd3801512903374639c1bd Mon Sep 17 00:00:00 2001 From: aakriti Date: Mon, 6 Oct 2025 22:36:04 -0400 Subject: [PATCH 5/7] removed extra line --- pandas/tests/plotting/frame/test_frame_legend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/plotting/frame/test_frame_legend.py b/pandas/tests/plotting/frame/test_frame_legend.py index f3f92877f5662..1331d25d3d4bf 100644 --- a/pandas/tests/plotting/frame/test_frame_legend.py +++ b/pandas/tests/plotting/frame/test_frame_legend.py @@ -292,7 +292,7 @@ def test_df_legend_labels_secondary_y_legend_loc(self): [ ("line", ["a", "b"]), ("bar", ["a", "b"]), - ("scatter", ["b"]), # scatter(x="a", y="b") usually labels the y series + ("scatter", ["b"]), ], ) def test_across_kinds_legend_loc(self, kind, labels): From 6a583ebc95419f02cca4f7111aebb1b02613f92b Mon Sep 17 00:00:00 2001 From: aakriti Date: Tue, 7 Oct 2025 08:08:59 -0400 Subject: [PATCH 6/7] isort --- pandas/tests/plotting/frame/test_frame_legend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/plotting/frame/test_frame_legend.py b/pandas/tests/plotting/frame/test_frame_legend.py index 1331d25d3d4bf..84354914fd6ba 100644 --- a/pandas/tests/plotting/frame/test_frame_legend.py +++ b/pandas/tests/plotting/frame/test_frame_legend.py @@ -10,8 +10,8 @@ from pandas.tests.plotting.common import ( _check_legend_labels, _check_legend_marker, - _check_text_labels, _check_plot_works, + _check_text_labels, ) mpl = pytest.importorskip("matplotlib") From 5ef599f5b5e699702a57cc37129d088794eb0a2d Mon Sep 17 00:00:00 2001 From: aakriti Date: Tue, 7 Oct 2025 14:21:02 -0400 Subject: [PATCH 7/7] removed comments --- pandas/plotting/_matplotlib/core.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index 6133b613d3fca..3416f4c757888 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -911,7 +911,6 @@ def _make_legend(self) -> None: title = self.legend_title if len(handles) > 0: - # ax.legend(handles, labels, loc="best", title=title) ax.legend(handles, labels, loc=self.legend_loc, title=title) elif self.subplots and self.legend: @@ -923,7 +922,6 @@ def _make_legend(self) -> None: "No artists with labels found to put in legend.", UserWarning, ) - # ax.legend(loc="best") ax.legend(loc=self.legend_loc) @final