6
6
from pandas .core .dtypes .missing import notna , isna
7
7
from pandas .core .dtypes .generic import ABCDatetimeIndex , ABCPeriodIndex
8
8
from pandas .core .dtypes .dtypes import IntervalDtype
9
- from pandas .core .dtypes .cast import maybe_convert_platform , find_common_type
9
+ from pandas .core .dtypes .cast import (
10
+ maybe_convert_platform , find_common_type , maybe_downcast_to_dtype )
10
11
from pandas .core .dtypes .common import (
11
12
_ensure_platform_int ,
12
13
is_list_like ,
@@ -1465,8 +1466,13 @@ def interval_range(start=None, end=None, periods=None, freq=None,
1465
1466
1466
1467
Notes
1467
1468
-----
1468
- Of the three parameters: ``start``, ``end``, and ``periods``, exactly two
1469
- must be specified.
1469
+ Of the four parameters ``start``, ``end``, ``periods``, and ``freq``,
1470
+ exactly three must be specified. If ``freq`` is omitted, the resulting
1471
+ ``IntervalIndex`` will have ``periods`` linearly spaced elements between
1472
+ ``start`` and ``end``, inclusively.
1473
+
1474
+ To learn more about datetime-like frequency strings, please see `this link
1475
+ <http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases>`__.
1470
1476
1471
1477
Returns
1472
1478
-------
@@ -1505,6 +1511,14 @@ def interval_range(start=None, end=None, periods=None, freq=None,
1505
1511
(2017-03-01, 2017-04-01]]
1506
1512
closed='right', dtype='interval[datetime64[ns]]')
1507
1513
1514
+ Specify ``start``, ``end``, and ``periods``; the frequency is generated
1515
+ automatically (linearly spaced).
1516
+
1517
+ >>> pd.interval_range(start=0, end=6, periods=4)
1518
+ IntervalIndex([(0.0, 1.5], (1.5, 3.0], (3.0, 4.5], (4.5, 6.0]]
1519
+ closed='right',
1520
+ dtype='interval[float64]')
1521
+
1508
1522
The ``closed`` parameter specifies which endpoints of the individual
1509
1523
intervals within the ``IntervalIndex`` are closed.
1510
1524
@@ -1516,19 +1530,21 @@ def interval_range(start=None, end=None, periods=None, freq=None,
1516
1530
--------
1517
1531
IntervalIndex : an Index of intervals that are all closed on the same side.
1518
1532
"""
1519
- if com ._count_not_none (start , end , periods ) != 2 :
1520
- raise ValueError ('Of the three parameters: start, end, and periods, '
1521
- 'exactly two must be specified' )
1522
-
1523
1533
start = com ._maybe_box_datetimelike (start )
1524
1534
end = com ._maybe_box_datetimelike (end )
1525
- endpoint = next (com ._not_none (start , end ))
1535
+ endpoint = start if start is not None else end
1536
+
1537
+ if freq is None and com ._any_none (periods , start , end ):
1538
+ freq = 1 if is_number (endpoint ) else 'D'
1539
+
1540
+ if com ._count_not_none (start , end , periods , freq ) != 3 :
1541
+ raise ValueError ('Of the four parameters: start, end, periods, and '
1542
+ 'freq, exactly three must be specified' )
1526
1543
1527
1544
if not _is_valid_endpoint (start ):
1528
1545
msg = 'start must be numeric or datetime-like, got {start}'
1529
1546
raise ValueError (msg .format (start = start ))
1530
-
1531
- if not _is_valid_endpoint (end ):
1547
+ elif not _is_valid_endpoint (end ):
1532
1548
msg = 'end must be numeric or datetime-like, got {end}'
1533
1549
raise ValueError (msg .format (end = end ))
1534
1550
@@ -1538,8 +1554,7 @@ def interval_range(start=None, end=None, periods=None, freq=None,
1538
1554
msg = 'periods must be a number, got {periods}'
1539
1555
raise TypeError (msg .format (periods = periods ))
1540
1556
1541
- freq = freq or (1 if is_number (endpoint ) else 'D' )
1542
- if not is_number (freq ):
1557
+ if freq is not None and not is_number (freq ):
1543
1558
try :
1544
1559
freq = to_offset (freq )
1545
1560
except ValueError :
@@ -1552,28 +1567,34 @@ def interval_range(start=None, end=None, periods=None, freq=None,
1552
1567
_is_type_compatible (end , freq )]):
1553
1568
raise TypeError ("start, end, freq need to be type compatible" )
1554
1569
1570
+ # +1 to convert interval count to breaks count (n breaks = n-1 intervals)
1571
+ if periods is not None :
1572
+ periods += 1
1573
+
1555
1574
if is_number (endpoint ):
1575
+ # compute the period/start/end if unspecified (at most one)
1556
1576
if periods is None :
1557
- periods = int ((end - start ) // freq )
1558
-
1559
- if start is None :
1560
- start = end - periods * freq
1561
-
1562
- # force end to be consistent with freq (lower if freq skips over end)
1563
- end = start + periods * freq
1564
-
1565
- # end + freq for inclusive endpoint
1566
- breaks = np .arange (start , end + freq , freq )
1567
- elif isinstance (endpoint , Timestamp ):
1568
- # add one to account for interval endpoints (n breaks = n-1 intervals)
1569
- if periods is not None :
1570
- periods += 1
1571
- breaks = date_range (start = start , end = end , periods = periods , freq = freq )
1577
+ periods = int ((end - start ) // freq ) + 1
1578
+ elif start is None :
1579
+ start = end - (periods - 1 ) * freq
1580
+ elif end is None :
1581
+ end = start + (periods - 1 ) * freq
1582
+
1583
+ # force end to be consistent with freq (lower if freq skips end)
1584
+ if freq is not None :
1585
+ end -= end % freq
1586
+
1587
+ breaks = np .linspace (start , end , periods )
1588
+ if all (is_integer (x ) for x in com ._not_none (start , end , freq )):
1589
+ # np.linspace always produces float output
1590
+ breaks = maybe_downcast_to_dtype (breaks , 'int64' )
1572
1591
else :
1573
- # add one to account for interval endpoints (n breaks = n-1 intervals)
1574
- if periods is not None :
1575
- periods += 1
1576
- breaks = timedelta_range (start = start , end = end , periods = periods ,
1577
- freq = freq )
1592
+ # delegate to the appropriate range function
1593
+ if isinstance (endpoint , Timestamp ):
1594
+ range_func = date_range
1595
+ else :
1596
+ range_func = timedelta_range
1597
+
1598
+ breaks = range_func (start = start , end = end , periods = periods , freq = freq )
1578
1599
1579
1600
return IntervalIndex .from_breaks (breaks , name = name , closed = closed )
0 commit comments