66from pandas .core .dtypes .missing import notna , isna
77from pandas .core .dtypes .generic import ABCDatetimeIndex , ABCPeriodIndex
88from 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 )
1011from pandas .core .dtypes .common import (
1112 _ensure_platform_int ,
1213 is_list_like ,
@@ -1465,8 +1466,13 @@ def interval_range(start=None, end=None, periods=None, freq=None,
14651466
14661467 Notes
14671468 -----
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>`__.
14701476
14711477 Returns
14721478 -------
@@ -1505,6 +1511,14 @@ def interval_range(start=None, end=None, periods=None, freq=None,
15051511 (2017-03-01, 2017-04-01]]
15061512 closed='right', dtype='interval[datetime64[ns]]')
15071513
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+
15081522 The ``closed`` parameter specifies which endpoints of the individual
15091523 intervals within the ``IntervalIndex`` are closed.
15101524
@@ -1516,19 +1530,21 @@ def interval_range(start=None, end=None, periods=None, freq=None,
15161530 --------
15171531 IntervalIndex : an Index of intervals that are all closed on the same side.
15181532 """
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-
15231533 start = com ._maybe_box_datetimelike (start )
15241534 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' )
15261543
15271544 if not _is_valid_endpoint (start ):
15281545 msg = 'start must be numeric or datetime-like, got {start}'
15291546 raise ValueError (msg .format (start = start ))
1530-
1531- if not _is_valid_endpoint (end ):
1547+ elif not _is_valid_endpoint (end ):
15321548 msg = 'end must be numeric or datetime-like, got {end}'
15331549 raise ValueError (msg .format (end = end ))
15341550
@@ -1538,8 +1554,7 @@ def interval_range(start=None, end=None, periods=None, freq=None,
15381554 msg = 'periods must be a number, got {periods}'
15391555 raise TypeError (msg .format (periods = periods ))
15401556
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 ):
15431558 try :
15441559 freq = to_offset (freq )
15451560 except ValueError :
@@ -1552,28 +1567,34 @@ def interval_range(start=None, end=None, periods=None, freq=None,
15521567 _is_type_compatible (end , freq )]):
15531568 raise TypeError ("start, end, freq need to be type compatible" )
15541569
1570+ # +1 to convert interval count to breaks count (n breaks = n-1 intervals)
1571+ if periods is not None :
1572+ periods += 1
1573+
15551574 if is_number (endpoint ):
1575+ # compute the period/start/end if unspecified (at most one)
15561576 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' )
15721591 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 )
15781599
15791600 return IntervalIndex .from_breaks (breaks , name = name , closed = closed )
0 commit comments