Skip to content

Commit 0da17ce

Browse files
Backport PR #40628: ENH: New boundary inputs (#42217)
Co-authored-by: Kiley Hewitt <[email protected]>
1 parent 2441acd commit 0da17ce

File tree

3 files changed

+77
-6
lines changed

3 files changed

+77
-6
lines changed

doc/source/whatsnew/v1.3.0.rst

+2
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ Other enhancements
276276
- Add keyword ``dropna`` to :meth:`DataFrame.value_counts` to allow counting rows that include ``NA`` values (:issue:`41325`)
277277
- :meth:`Series.replace` will now cast results to ``PeriodDtype`` where possible instead of ``object`` dtype (:issue:`41526`)
278278
- Improved error message in ``corr`` and ``cov`` methods on :class:`.Rolling`, :class:`.Expanding`, and :class:`.ExponentialMovingWindow` when ``other`` is not a :class:`DataFrame` or :class:`Series` (:issue:`41741`)
279+
- :meth:`Series.between` can now accept ``left`` or ``right`` as arguments to ``inclusive`` to include only the left or right boundary (:issue:`40245`)
279280
- :meth:`DataFrame.explode` now supports exploding multiple columns. Its ``column`` argument now also accepts a list of str or tuples for exploding on multiple columns at the same time (:issue:`39240`)
280281

281282
.. ---------------------------------------------------------------------------
@@ -838,6 +839,7 @@ Other Deprecations
838839
- Deprecated inference of ``timedelta64[ns]``, ``datetime64[ns]``, or ``DatetimeTZDtype`` dtypes in :class:`Series` construction when data containing strings is passed and no ``dtype`` is passed (:issue:`33558`)
839840
- In a future version, constructing :class:`Series` or :class:`DataFrame` with ``datetime64[ns]`` data and ``DatetimeTZDtype`` will treat the data as wall-times instead of as UTC times (matching DatetimeIndex behavior). To treat the data as UTC times, use ``pd.Series(data).dt.tz_localize("UTC").dt.tz_convert(dtype.tz)`` or ``pd.Series(data.view("int64"), dtype=dtype)`` (:issue:`33401`)
840841
- Deprecated passing lists as ``key`` to :meth:`DataFrame.xs` and :meth:`Series.xs` (:issue:`41760`)
842+
- Deprecated boolean arguments of ``inclusive`` in :meth:`Series.between` to have ``{"left", "right", "neither", "both"}`` as standard argument values (:issue:`40628`)
841843
- Deprecated passing arguments as positional for all of the following, with exceptions noted (:issue:`41485`):
842844

843845
- :func:`concat` (other than ``objs``)

pandas/core/series.py

+28-5
Original file line numberDiff line numberDiff line change
@@ -4967,7 +4967,7 @@ def isin(self, values) -> Series:
49674967
self, method="isin"
49684968
)
49694969

4970-
def between(self, left, right, inclusive=True) -> Series:
4970+
def between(self, left, right, inclusive="both") -> Series:
49714971
"""
49724972
Return boolean Series equivalent to left <= series <= right.
49734973
@@ -4981,8 +4981,9 @@ def between(self, left, right, inclusive=True) -> Series:
49814981
Left boundary.
49824982
right : scalar or list-like
49834983
Right boundary.
4984-
inclusive : bool, default True
4985-
Include boundaries.
4984+
inclusive : {"both", "neither", "left", "right"}
4985+
Include boundaries. Whether to set each bound as closed or open.
4986+
.. versionchanged:: 1.3.0
49864987
49874988
Returns
49884989
-------
@@ -5033,12 +5034,34 @@ def between(self, left, right, inclusive=True) -> Series:
50335034
3 False
50345035
dtype: bool
50355036
"""
5036-
if inclusive:
5037+
if inclusive is True or inclusive is False:
5038+
warnings.warn(
5039+
"Boolean inputs to the `inclusive` argument are deprecated in"
5040+
"favour of `both` or `neither`.",
5041+
FutureWarning,
5042+
stacklevel=2,
5043+
)
5044+
if inclusive:
5045+
inclusive = "both"
5046+
else:
5047+
inclusive = "neither"
5048+
if inclusive == "both":
50375049
lmask = self >= left
50385050
rmask = self <= right
5039-
else:
5051+
elif inclusive == "left":
5052+
lmask = self >= left
5053+
rmask = self < right
5054+
elif inclusive == "right":
5055+
lmask = self > left
5056+
rmask = self <= right
5057+
elif inclusive == "neither":
50405058
lmask = self > left
50415059
rmask = self < right
5060+
else:
5061+
raise ValueError(
5062+
"Inclusive has to be either string of 'both',"
5063+
"'left', 'right', or 'neither'."
5064+
)
50425065

50435066
return lmask & rmask
50445067

pandas/tests/series/methods/test_between.py

+47-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import numpy as np
2+
import pytest
23

34
from pandas import (
45
Series,
@@ -28,7 +29,7 @@ def test_between_datetime_values(self):
2829
expected = ser[3:18].dropna()
2930
tm.assert_series_equal(result, expected)
3031

31-
result = ser[ser.between(ser[3], ser[17], inclusive=False)]
32+
result = ser[ser.between(ser[3], ser[17], inclusive="neither")]
3233
expected = ser[5:16].dropna()
3334
tm.assert_series_equal(result, expected)
3435

@@ -38,3 +39,48 @@ def test_between_period_values(self):
3839
result = ser.between(left, right)
3940
expected = (ser >= left) & (ser <= right)
4041
tm.assert_series_equal(result, expected)
42+
43+
def test_between_inclusive_string(self): # :issue:`40628`
44+
series = Series(date_range("1/1/2000", periods=10))
45+
left, right = series[[2, 7]]
46+
47+
result = series.between(left, right, inclusive="both")
48+
expected = (series >= left) & (series <= right)
49+
tm.assert_series_equal(result, expected)
50+
51+
result = series.between(left, right, inclusive="left")
52+
expected = (series >= left) & (series < right)
53+
tm.assert_series_equal(result, expected)
54+
55+
result = series.between(left, right, inclusive="right")
56+
expected = (series > left) & (series <= right)
57+
tm.assert_series_equal(result, expected)
58+
59+
result = series.between(left, right, inclusive="neither")
60+
expected = (series > left) & (series < right)
61+
tm.assert_series_equal(result, expected)
62+
63+
def test_between_error_args(self): # :issue:`40628`
64+
series = Series(date_range("1/1/2000", periods=10))
65+
left, right = series[[2, 7]]
66+
67+
value_error_msg = (
68+
"Inclusive has to be either string of 'both',"
69+
"'left', 'right', or 'neither'."
70+
)
71+
72+
with pytest.raises(ValueError, match=value_error_msg):
73+
series = Series(date_range("1/1/2000", periods=10))
74+
series.between(left, right, inclusive="yes")
75+
76+
def test_between_inclusive_warning(self):
77+
series = Series(date_range("1/1/2000", periods=10))
78+
left, right = series[[2, 7]]
79+
with tm.assert_produces_warning(FutureWarning):
80+
result = series.between(left, right, inclusive=False)
81+
expected = (series > left) & (series < right)
82+
tm.assert_series_equal(result, expected)
83+
with tm.assert_produces_warning(FutureWarning):
84+
result = series.between(left, right, inclusive=True)
85+
expected = (series >= left) & (series <= right)
86+
tm.assert_series_equal(result, expected)

0 commit comments

Comments
 (0)