Skip to content

Commit 587a0dd

Browse files
topper-123jreback
authored andcommitted
DOC: add Raises, Examples and See Also sections to methods at_time/between_time/first/last (#20725)
1 parent bd4332f commit 587a0dd

File tree

5 files changed

+243
-17
lines changed

5 files changed

+243
-17
lines changed

doc/source/whatsnew/v0.23.0.txt

+4
Original file line numberDiff line numberDiff line change
@@ -916,6 +916,10 @@ Datetimelike API Changes
916916
- :class:`CacheableOffset` and :class:`WeekDay` are no longer available in the ``pandas.tseries.offsets`` module (:issue:`17830`)
917917
- ``pandas.tseries.frequencies.get_freq_group()`` and ``pandas.tseries.frequencies.DAYS`` are removed from the public API (:issue:`18034`)
918918
- :func:`Series.truncate` and :func:`DataFrame.truncate` will raise a ``ValueError`` if the index is not sorted instead of an unhelpful ``KeyError`` (:issue:`17935`)
919+
- :attr:`Series.first` and :attr:`DataFrame.first` will now raise a ``TypeError``
920+
rather than ``NotImplementedError`` when index is not a :class:`DatetimeIndex`` (:issue:`20725`).
921+
- :attr:`Series.last` and :attr:`DateFrame.last` will now raise a ``TypeError``
922+
rather than ``NotImplementedError`` when index is not a :class:`DatetimeIndex`` (:issue:`20725`).
919923
- Restricted ``DateOffset`` keyword arguments. Previously, ``DateOffset`` subclasses allowed arbitrary keyword arguments which could lead to unexpected behavior. Now, only valid arguments will be accepted. (:issue:`17176`, :issue:`18226`).
920924
- :func:`pandas.merge` provides a more informative error message when trying to merge on timezone-aware and timezone-naive columns (:issue:`15800`)
921925
- For :class:`DatetimeIndex` and :class:`TimedeltaIndex` with ``freq=None``, addition or subtraction of integer-dtyped array or ``Index`` will raise ``NullFrequencyError`` instead of ``TypeError`` (:issue:`19895`)

pandas/core/generic.py

+131-6
Original file line numberDiff line numberDiff line change
@@ -6761,13 +6761,42 @@ def at_time(self, time, asof=False):
67616761
"""
67626762
Select values at particular time of day (e.g. 9:30AM).
67636763
6764+
Raises
6765+
------
6766+
TypeError
6767+
If the index is not a :class:`DatetimeIndex`
6768+
67646769
Parameters
67656770
----------
67666771
time : datetime.time or string
67676772
67686773
Returns
67696774
-------
67706775
values_at_time : type of caller
6776+
6777+
Examples
6778+
--------
6779+
>>> i = pd.date_range('2018-04-09', periods=4, freq='12H')
6780+
>>> ts = pd.DataFrame({'A': [1,2,3,4]}, index=i)
6781+
>>> ts
6782+
A
6783+
2018-04-09 00:00:00 1
6784+
2018-04-09 12:00:00 2
6785+
2018-04-10 00:00:00 3
6786+
2018-04-10 12:00:00 4
6787+
6788+
>>> ts.at_time('12:00')
6789+
A
6790+
2018-04-09 12:00:00 2
6791+
2018-04-10 12:00:00 4
6792+
6793+
See Also
6794+
--------
6795+
between_time : Select values between particular times of the day
6796+
first : Select initial periods of time series based on a date offset
6797+
last : Select final periods of time series based on a date offset
6798+
DatetimeIndex.indexer_at_time : Get just the index locations for
6799+
values at particular time of the day
67716800
"""
67726801
try:
67736802
indexer = self.index.indexer_at_time(time, asof=asof)
@@ -6780,6 +6809,14 @@ def between_time(self, start_time, end_time, include_start=True,
67806809
"""
67816810
Select values between particular times of the day (e.g., 9:00-9:30 AM).
67826811
6812+
By setting ``start_time`` to be later than ``end_time``,
6813+
you can get the times that are *not* between the two times.
6814+
6815+
Raises
6816+
------
6817+
TypeError
6818+
If the index is not a :class:`DatetimeIndex`
6819+
67836820
Parameters
67846821
----------
67856822
start_time : datetime.time or string
@@ -6790,6 +6827,38 @@ def between_time(self, start_time, end_time, include_start=True,
67906827
Returns
67916828
-------
67926829
values_between_time : type of caller
6830+
6831+
Examples
6832+
--------
6833+
>>> i = pd.date_range('2018-04-09', periods=4, freq='1D20min')
6834+
>>> ts = pd.DataFrame({'A': [1,2,3,4]}, index=i)
6835+
>>> ts
6836+
A
6837+
2018-04-09 00:00:00 1
6838+
2018-04-10 00:20:00 2
6839+
2018-04-11 00:40:00 3
6840+
2018-04-12 01:00:00 4
6841+
6842+
>>> ts.between_time('0:15', '0:45')
6843+
A
6844+
2018-04-10 00:20:00 2
6845+
2018-04-11 00:40:00 3
6846+
6847+
You get the times that are *not* between two times by setting
6848+
``start_time`` later than ``end_time``:
6849+
6850+
>>> ts.between_time('0:45', '0:15')
6851+
A
6852+
2018-04-09 00:00:00 1
6853+
2018-04-12 01:00:00 4
6854+
6855+
See Also
6856+
--------
6857+
at_time : Select values at a particular time of the day
6858+
first : Select initial periods of time series based on a date offset
6859+
last : Select final periods of time series based on a date offset
6860+
DatetimeIndex.indexer_between_time : Get just the index locations for
6861+
values between particular times of the day
67936862
"""
67946863
try:
67956864
indexer = self.index.indexer_between_time(
@@ -7043,22 +7112,50 @@ def first(self, offset):
70437112
Convenience method for subsetting initial periods of time series data
70447113
based on a date offset.
70457114
7115+
Raises
7116+
------
7117+
TypeError
7118+
If the index is not a :class:`DatetimeIndex`
7119+
70467120
Parameters
70477121
----------
70487122
offset : string, DateOffset, dateutil.relativedelta
70497123
70507124
Examples
70517125
--------
7052-
ts.first('10D') -> First 10 days
7126+
>>> i = pd.date_range('2018-04-09', periods=4, freq='2D')
7127+
>>> ts = pd.DataFrame({'A': [1,2,3,4]}, index=i)
7128+
>>> ts
7129+
A
7130+
2018-04-09 1
7131+
2018-04-11 2
7132+
2018-04-13 3
7133+
2018-04-15 4
7134+
7135+
Get the rows for the first 3 days:
7136+
7137+
>>> ts.first('3D')
7138+
A
7139+
2018-04-09 1
7140+
2018-04-11 2
7141+
7142+
Notice the data for 3 first calender days were returned, not the first
7143+
3 days observed in the dataset, and therefore data for 2018-04-13 was
7144+
not returned.
70537145
70547146
Returns
70557147
-------
70567148
subset : type of caller
7149+
7150+
See Also
7151+
--------
7152+
last : Select final periods of time series based on a date offset
7153+
at_time : Select values at a particular time of the day
7154+
between_time : Select values between particular times of the day
70577155
"""
70587156
from pandas.tseries.frequencies import to_offset
70597157
if not isinstance(self.index, DatetimeIndex):
7060-
raise NotImplementedError("'first' only supports a DatetimeIndex "
7061-
"index")
7158+
raise TypeError("'first' only supports a DatetimeIndex index")
70627159

70637160
if len(self.index) == 0:
70647161
return self
@@ -7079,22 +7176,50 @@ def last(self, offset):
70797176
Convenience method for subsetting final periods of time series data
70807177
based on a date offset.
70817178
7179+
Raises
7180+
------
7181+
TypeError
7182+
If the index is not a :class:`DatetimeIndex`
7183+
70827184
Parameters
70837185
----------
70847186
offset : string, DateOffset, dateutil.relativedelta
70857187
70867188
Examples
70877189
--------
7088-
ts.last('5M') -> Last 5 months
7190+
>>> i = pd.date_range('2018-04-09', periods=4, freq='2D')
7191+
>>> ts = pd.DataFrame({'A': [1,2,3,4]}, index=i)
7192+
>>> ts
7193+
A
7194+
2018-04-09 1
7195+
2018-04-11 2
7196+
2018-04-13 3
7197+
2018-04-15 4
7198+
7199+
Get the rows for the last 3 days:
7200+
7201+
>>> ts.last('3D')
7202+
A
7203+
2018-04-13 3
7204+
2018-04-15 4
7205+
7206+
Notice the data for 3 last calender days were returned, not the last
7207+
3 observed days in the dataset, and therefore data for 2018-04-11 was
7208+
not returned.
70897209
70907210
Returns
70917211
-------
70927212
subset : type of caller
7213+
7214+
See Also
7215+
--------
7216+
first : Select initial periods of time series based on a date offset
7217+
at_time : Select values at a particular time of the day
7218+
between_time : Select values between particular times of the day
70937219
"""
70947220
from pandas.tseries.frequencies import to_offset
70957221
if not isinstance(self.index, DatetimeIndex):
7096-
raise NotImplementedError("'last' only supports a DatetimeIndex "
7097-
"index")
7222+
raise TypeError("'last' only supports a DatetimeIndex index")
70987223

70997224
if len(self.index) == 0:
71007225
return self

pandas/core/indexes/datetimes.py

+18-9
Original file line numberDiff line numberDiff line change
@@ -2368,15 +2368,23 @@ def tz_localize(self, tz, ambiguous='raise', errors='raise'):
23682368

23692369
def indexer_at_time(self, time, asof=False):
23702370
"""
2371-
Select values at particular time of day (e.g. 9:30AM)
2371+
Returns index locations of index values at particular time of day
2372+
(e.g. 9:30AM).
23722373
23732374
Parameters
23742375
----------
23752376
time : datetime.time or string
2377+
datetime.time or string in appropriate format ("%H:%M", "%H%M",
2378+
"%I:%M%p", "%I%M%p", "%H:%M:%S", "%H%M%S", "%I:%M:%S%p",
2379+
"%I%M%S%p").
23762380
23772381
Returns
23782382
-------
2379-
values_at_time : TimeSeries
2383+
values_at_time : array of integers
2384+
2385+
See Also
2386+
--------
2387+
indexer_between_time, DataFrame.at_time
23802388
"""
23812389
from dateutil.parser import parse
23822390

@@ -2398,24 +2406,25 @@ def indexer_at_time(self, time, asof=False):
23982406
def indexer_between_time(self, start_time, end_time, include_start=True,
23992407
include_end=True):
24002408
"""
2401-
Select values between particular times of day (e.g., 9:00-9:30AM).
2402-
2403-
Return values of the index between two times. If start_time or
2404-
end_time are strings then tseries.tools.to_time is used to convert to
2405-
a time object.
2409+
Return index locations of values between particular times of day
2410+
(e.g., 9:00-9:30AM).
24062411
24072412
Parameters
24082413
----------
24092414
start_time, end_time : datetime.time, str
24102415
datetime.time or string in appropriate format ("%H:%M", "%H%M",
24112416
"%I:%M%p", "%I%M%p", "%H:%M:%S", "%H%M%S", "%I:%M:%S%p",
2412-
"%I%M%S%p")
2417+
"%I%M%S%p").
24132418
include_start : boolean, default True
24142419
include_end : boolean, default True
24152420
24162421
Returns
24172422
-------
2418-
values_between_time : TimeSeries
2423+
values_between_time : array of integers
2424+
2425+
See Also
2426+
--------
2427+
indexer_at_time, DataFrame.between_time
24192428
"""
24202429
start_time = tools.to_time(start_time)
24212430
end_time = tools.to_time(end_time)

pandas/tests/frame/test_timeseries.py

+66-2
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,59 @@ def test_first_last_valid(self):
539539
assert frame.first_valid_index().freq == frame.index.freq
540540
assert frame.last_valid_index().freq == frame.index.freq
541541

542-
def test_at_time_frame(self):
542+
def test_first_subset(self):
543+
ts = tm.makeTimeDataFrame(freq='12h')
544+
result = ts.first('10d')
545+
assert len(result) == 20
546+
547+
ts = tm.makeTimeDataFrame(freq='D')
548+
result = ts.first('10d')
549+
assert len(result) == 10
550+
551+
result = ts.first('3M')
552+
expected = ts[:'3/31/2000']
553+
assert_frame_equal(result, expected)
554+
555+
result = ts.first('21D')
556+
expected = ts[:21]
557+
assert_frame_equal(result, expected)
558+
559+
result = ts[:0].first('3M')
560+
assert_frame_equal(result, ts[:0])
561+
562+
def test_first_raises(self):
563+
# GH20725
564+
df = pd.DataFrame([[1, 2, 3], [4, 5, 6]])
565+
with pytest.raises(TypeError): # index is not a DatetimeIndex
566+
df.first('1D')
567+
568+
def test_last_subset(self):
569+
ts = tm.makeTimeDataFrame(freq='12h')
570+
result = ts.last('10d')
571+
assert len(result) == 20
572+
573+
ts = tm.makeTimeDataFrame(nper=30, freq='D')
574+
result = ts.last('10d')
575+
assert len(result) == 10
576+
577+
result = ts.last('21D')
578+
expected = ts['2000-01-10':]
579+
assert_frame_equal(result, expected)
580+
581+
result = ts.last('21D')
582+
expected = ts[-21:]
583+
assert_frame_equal(result, expected)
584+
585+
result = ts[:0].last('3M')
586+
assert_frame_equal(result, ts[:0])
587+
588+
def test_last_raises(self):
589+
# GH20725
590+
df = pd.DataFrame([[1, 2, 3], [4, 5, 6]])
591+
with pytest.raises(TypeError): # index is not a DatetimeIndex
592+
df.last('1D')
593+
594+
def test_at_time(self):
543595
rng = date_range('1/1/2000', '1/5/2000', freq='5min')
544596
ts = DataFrame(np.random.randn(len(rng), 2), index=rng)
545597
rs = ts.at_time(rng[1])
@@ -569,7 +621,13 @@ def test_at_time_frame(self):
569621
rs = ts.at_time('16:00')
570622
assert len(rs) == 0
571623

572-
def test_between_time_frame(self):
624+
def test_at_time_raises(self):
625+
# GH20725
626+
df = pd.DataFrame([[1, 2, 3], [4, 5, 6]])
627+
with pytest.raises(TypeError): # index is not a DatetimeIndex
628+
df.at_time('00:00')
629+
630+
def test_between_time(self):
573631
rng = date_range('1/1/2000', '1/5/2000', freq='5min')
574632
ts = DataFrame(np.random.randn(len(rng), 2), index=rng)
575633
stime = time(0, 0)
@@ -629,6 +687,12 @@ def test_between_time_frame(self):
629687
else:
630688
assert (t < etime) or (t >= stime)
631689

690+
def test_between_time_raises(self):
691+
# GH20725
692+
df = pd.DataFrame([[1, 2, 3], [4, 5, 6]])
693+
with pytest.raises(TypeError): # index is not a DatetimeIndex
694+
df.between_time(start_time='00:00', end_time='12:00')
695+
632696
def test_operation_on_NaT(self):
633697
# Both NaT and Timestamp are in DataFrame.
634698
df = pd.DataFrame({'foo': [pd.NaT, pd.NaT,

0 commit comments

Comments
 (0)