Skip to content

Commit eca7655

Browse files
authored
ENH: New boundary inputs (#40628)
1 parent fa9d66c commit eca7655

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
@@ -4965,7 +4965,7 @@ def isin(self, values) -> Series:
49654965
self, method="isin"
49664966
)
49674967

4968-
def between(self, left, right, inclusive=True) -> Series:
4968+
def between(self, left, right, inclusive="both") -> Series:
49694969
"""
49704970
Return boolean Series equivalent to left <= series <= right.
49714971
@@ -4979,8 +4979,9 @@ def between(self, left, right, inclusive=True) -> Series:
49794979
Left boundary.
49804980
right : scalar or list-like
49814981
Right boundary.
4982-
inclusive : bool, default True
4983-
Include boundaries.
4982+
inclusive : {"both", "neither", "left", "right"}
4983+
Include boundaries. Whether to set each bound as closed or open.
4984+
.. versionchanged:: 1.3.0
49844985
49854986
Returns
49864987
-------
@@ -5031,12 +5032,34 @@ def between(self, left, right, inclusive=True) -> Series:
50315032
3 False
50325033
dtype: bool
50335034
"""
5034-
if inclusive:
5035+
if inclusive is True or inclusive is False:
5036+
warnings.warn(
5037+
"Boolean inputs to the `inclusive` argument are deprecated in"
5038+
"favour of `both` or `neither`.",
5039+
FutureWarning,
5040+
stacklevel=2,
5041+
)
5042+
if inclusive:
5043+
inclusive = "both"
5044+
else:
5045+
inclusive = "neither"
5046+
if inclusive == "both":
50355047
lmask = self >= left
50365048
rmask = self <= right
5037-
else:
5049+
elif inclusive == "left":
5050+
lmask = self >= left
5051+
rmask = self < right
5052+
elif inclusive == "right":
5053+
lmask = self > left
5054+
rmask = self <= right
5055+
elif inclusive == "neither":
50385056
lmask = self > left
50395057
rmask = self < right
5058+
else:
5059+
raise ValueError(
5060+
"Inclusive has to be either string of 'both',"
5061+
"'left', 'right', or 'neither'."
5062+
)
50405063

50415064
return lmask & rmask
50425065

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)