Skip to content

Commit 943ed8f

Browse files
committed
ENH: Add dtype parameter to IntervalIndex constructors and deprecate from_intervals
1 parent 9872d67 commit 943ed8f

File tree

11 files changed

+419
-301
lines changed

11 files changed

+419
-301
lines changed

doc/source/api.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1617,7 +1617,6 @@ IntervalIndex Components
16171617
IntervalIndex.from_arrays
16181618
IntervalIndex.from_tuples
16191619
IntervalIndex.from_breaks
1620-
IntervalIndex.from_intervals
16211620
IntervalIndex.contains
16221621
IntervalIndex.left
16231622
IntervalIndex.right

doc/source/whatsnew/v0.23.0.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,8 @@ Other Enhancements
207207
:func:`pandas.api.extensions.register_index_accessor`, accessor for libraries downstream of pandas
208208
to register custom accessors like ``.cat`` on pandas objects. See
209209
:ref:`Registering Custom Accessors <developer.register-accessors>` for more (:issue:`14781`).
210-
211-
212210
- ``IntervalIndex.astype`` now supports conversions between subtypes when passed an ``IntervalDtype`` (:issue:`19197`)
211+
- :class:`IntervalIndex` and its associated constructor methods (``from_arrays``, ``from_breaks``, ``from_tuples``) have gained a ``dtype`` parameter (:issue:`19262`)
213212

214213
.. _whatsnew_0230.api_breaking:
215214

@@ -329,6 +328,7 @@ Deprecations
329328
- ``Series.valid`` is deprecated. Use :meth:`Series.dropna` instead (:issue:`18800`).
330329
- :func:`read_excel` has deprecated the ``skip_footer`` parameter. Use ``skipfooter`` instead (:issue:`18836`)
331330
- The ``is_copy`` attribute is deprecated and will be removed in a future version (:issue:`18801`).
331+
- ``IntervalIndex.from_intervals`` is deprecated in favor of the :class:`IntervalIndex` constructor (:issue:`19263`)
332332

333333

334334
.. _whatsnew_0230.prior_deprecations:

pandas/core/indexes/base.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,8 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None,
200200
# interval
201201
if is_interval_dtype(data) or is_interval_dtype(dtype):
202202
from .interval import IntervalIndex
203-
return IntervalIndex(data, dtype=dtype, name=name, copy=copy)
203+
return IntervalIndex(data, dtype=dtype, name=name, copy=copy,
204+
**kwargs)
204205

205206
# index-like
206207
elif isinstance(data, (np.ndarray, Index, ABCSeries)):
@@ -313,8 +314,7 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None,
313314
return Float64Index(subarr, copy=copy, name=name)
314315
elif inferred == 'interval':
315316
from .interval import IntervalIndex
316-
return IntervalIndex.from_intervals(subarr, name=name,
317-
copy=copy)
317+
return IntervalIndex(subarr, name=name, copy=copy)
318318
elif inferred == 'boolean':
319319
# don't support boolean explicitly ATM
320320
pass

pandas/core/indexes/category.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ def __array__(self, dtype=None):
341341
def astype(self, dtype, copy=True):
342342
if is_interval_dtype(dtype):
343343
from pandas import IntervalIndex
344-
return IntervalIndex.from_intervals(np.array(self))
344+
return IntervalIndex(np.array(self))
345345
elif is_categorical_dtype(dtype):
346346
# GH 18630
347347
dtype = self.dtype._update_dtype(dtype)

pandas/core/indexes/interval.py

Lines changed: 62 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
""" define the IntervalIndex """
22

33
import numpy as np
4+
import warnings
45

56
from pandas.core.dtypes.missing import notna, isna
67
from pandas.core.dtypes.generic import ABCDatetimeIndex, ABCPeriodIndex
@@ -151,6 +152,8 @@ class IntervalIndex(IntervalMixin, Index):
151152
Name to be stored in the index.
152153
copy : boolean, default False
153154
Copy the meta-data
155+
dtype : dtype or None, default None
156+
If None, dtype will be inferred
154157
155158
Attributes
156159
----------
@@ -167,7 +170,6 @@ class IntervalIndex(IntervalMixin, Index):
167170
from_arrays
168171
from_tuples
169172
from_breaks
170-
from_intervals
171173
contains
172174
173175
Examples
@@ -181,8 +183,7 @@ class IntervalIndex(IntervalMixin, Index):
181183
182184
It may also be constructed using one of the constructor
183185
methods: :meth:`IntervalIndex.from_arrays`,
184-
:meth:`IntervalIndex.from_breaks`, :meth:`IntervalIndex.from_intervals`
185-
and :meth:`IntervalIndex.from_tuples`.
186+
:meth:`IntervalIndex.from_breaks`, and :meth:`IntervalIndex.from_tuples`.
186187
187188
See further examples in the doc strings of ``interval_range`` and the
188189
mentioned constructor methods.
@@ -211,8 +212,7 @@ class IntervalIndex(IntervalMixin, Index):
211212

212213
_mask = None
213214

214-
def __new__(cls, data, closed=None,
215-
name=None, copy=False, dtype=None,
215+
def __new__(cls, data, closed=None, name=None, copy=False, dtype=None,
216216
fastpath=False, verify_integrity=True):
217217

218218
if fastpath:
@@ -245,19 +245,28 @@ def __new__(cls, data, closed=None,
245245

246246
closed = closed or infer_closed
247247

248-
return cls._simple_new(left, right, closed, name,
249-
copy=copy, verify_integrity=verify_integrity)
248+
return cls._simple_new(left, right, closed, name, copy=copy,
249+
dtype=dtype, verify_integrity=verify_integrity)
250250

251251
@classmethod
252-
def _simple_new(cls, left, right, closed=None, name=None,
253-
copy=False, verify_integrity=True):
252+
def _simple_new(cls, left, right, closed=None, name=None, copy=False,
253+
dtype=None, verify_integrity=True):
254254
result = IntervalMixin.__new__(cls)
255255

256-
if closed is None:
257-
closed = 'right'
256+
closed = closed or 'right'
258257
left = _ensure_index(left, copy=copy)
259258
right = _ensure_index(right, copy=copy)
260259

260+
if dtype is not None:
261+
# GH 19262
262+
dtype = pandas_dtype(dtype)
263+
if not is_interval_dtype(dtype):
264+
msg = 'dtype must be an IntervalDtype, got {dtype}'
265+
raise TypeError(msg.format(dtype=dtype))
266+
elif dtype.subtype is not None:
267+
left = left.astype(dtype.subtype)
268+
right = right.astype(dtype.subtype)
269+
261270
# coerce dtypes to match if needed
262271
if is_float_dtype(left) and is_integer_dtype(right):
263272
right = right.astype(left.dtype)
@@ -304,7 +313,7 @@ def _shallow_copy(self, left=None, right=None, **kwargs):
304313
# only single value passed, could be an IntervalIndex
305314
# or array of Intervals
306315
if not isinstance(left, IntervalIndex):
307-
left = type(self).from_intervals(left)
316+
left = self._constructor(left)
308317

309318
left, right = left.left, left.right
310319
else:
@@ -322,7 +331,7 @@ def _validate(self):
322331
Verify that the IntervalIndex is valid.
323332
"""
324333
if self.closed not in _VALID_CLOSED:
325-
raise ValueError("invalid options for 'closed': {closed}"
334+
raise ValueError("invalid option for 'closed': {closed}"
326335
.format(closed=self.closed))
327336
if len(self.left) != len(self.right):
328337
raise ValueError('left and right must have the same length')
@@ -356,7 +365,7 @@ def _engine(self):
356365

357366
@property
358367
def _constructor(self):
359-
return type(self).from_intervals
368+
return type(self)
360369

361370
def __contains__(self, key):
362371
"""
@@ -402,7 +411,8 @@ def contains(self, key):
402411
return False
403412

404413
@classmethod
405-
def from_breaks(cls, breaks, closed='right', name=None, copy=False):
414+
def from_breaks(cls, breaks, closed='right', name=None, copy=False,
415+
dtype=None):
406416
"""
407417
Construct an IntervalIndex from an array of splits
408418
@@ -417,6 +427,8 @@ def from_breaks(cls, breaks, closed='right', name=None, copy=False):
417427
Name to be stored in the index.
418428
copy : boolean, default False
419429
copy the data
430+
dtype : dtype or None, default None
431+
If None, dtype will be inferred
420432
421433
Examples
422434
--------
@@ -430,18 +442,17 @@ def from_breaks(cls, breaks, closed='right', name=None, copy=False):
430442
interval_range : Function to create a fixed frequency IntervalIndex
431443
IntervalIndex.from_arrays : Construct an IntervalIndex from a left and
432444
right array
433-
IntervalIndex.from_intervals : Construct an IntervalIndex from an array
434-
of Interval objects
435445
IntervalIndex.from_tuples : Construct an IntervalIndex from a
436446
list/array of tuples
437447
"""
438448
breaks = maybe_convert_platform_interval(breaks)
439449

440450
return cls.from_arrays(breaks[:-1], breaks[1:], closed,
441-
name=name, copy=copy)
451+
name=name, copy=copy, dtype=dtype)
442452

443453
@classmethod
444-
def from_arrays(cls, left, right, closed='right', name=None, copy=False):
454+
def from_arrays(cls, left, right, closed='right', name=None, copy=False,
455+
dtype=None):
445456
"""
446457
Construct an IntervalIndex from a a left and right array
447458
@@ -458,6 +469,8 @@ def from_arrays(cls, left, right, closed='right', name=None, copy=False):
458469
Name to be stored in the index.
459470
copy : boolean, default False
460471
copy the data
472+
dtype : dtype or None, default None
473+
If None, dtype will be inferred
461474
462475
Examples
463476
--------
@@ -471,22 +484,23 @@ def from_arrays(cls, left, right, closed='right', name=None, copy=False):
471484
interval_range : Function to create a fixed frequency IntervalIndex
472485
IntervalIndex.from_breaks : Construct an IntervalIndex from an array of
473486
splits
474-
IntervalIndex.from_intervals : Construct an IntervalIndex from an array
475-
of Interval objects
476487
IntervalIndex.from_tuples : Construct an IntervalIndex from a
477488
list/array of tuples
478489
"""
479490
left = maybe_convert_platform_interval(left)
480491
right = maybe_convert_platform_interval(right)
481492

482-
return cls._simple_new(left, right, closed, name=name,
483-
copy=copy, verify_integrity=True)
493+
return cls._simple_new(left, right, closed, name=name, copy=copy,
494+
dtype=dtype, verify_integrity=True)
484495

485496
@classmethod
486-
def from_intervals(cls, data, name=None, copy=False):
497+
def from_intervals(cls, data, closed=None, name=None, copy=False,
498+
dtype=None):
487499
"""
488500
Construct an IntervalIndex from a 1d array of Interval objects
489501
502+
.. deprecated:: 0.23.0
503+
490504
Parameters
491505
----------
492506
data : array-like (1-dimensional)
@@ -496,6 +510,8 @@ def from_intervals(cls, data, name=None, copy=False):
496510
Name to be stored in the index.
497511
copy : boolean, default False
498512
by-default copy the data, this is compat only and ignored
513+
dtype : dtype or None, default None
514+
If None, dtype will be inferred
499515
500516
Examples
501517
--------
@@ -521,16 +537,14 @@ def from_intervals(cls, data, name=None, copy=False):
521537
IntervalIndex.from_tuples : Construct an IntervalIndex from a
522538
list/array of tuples
523539
"""
524-
if isinstance(data, IntervalIndex):
525-
left, right, closed = data.left, data.right, data.closed
526-
name = name or data.name
527-
else:
528-
data = maybe_convert_platform_interval(data)
529-
left, right, closed = intervals_to_interval_bounds(data)
530-
return cls.from_arrays(left, right, closed, name=name, copy=False)
540+
msg = ('IntervalIndex.from_intervals is deprecated and will be '
541+
'removed in a future version; use IntervalIndex(...) instead')
542+
warnings.warn(msg, FutureWarning, stacklevel=2)
543+
return cls(data, closed=closed, name=name, copy=copy, dtype=dtype)
531544

532545
@classmethod
533-
def from_tuples(cls, data, closed='right', name=None, copy=False):
546+
def from_tuples(cls, data, closed='right', name=None, copy=False,
547+
dtype=None):
534548
"""
535549
Construct an IntervalIndex from a list/array of tuples
536550
@@ -545,6 +559,8 @@ def from_tuples(cls, data, closed='right', name=None, copy=False):
545559
Name to be stored in the index.
546560
copy : boolean, default False
547561
by-default copy the data, this is compat only and ignored
562+
dtype : dtype or None, default None
563+
If None, dtype will be inferred
548564
549565
Examples
550566
--------
@@ -559,8 +575,6 @@ def from_tuples(cls, data, closed='right', name=None, copy=False):
559575
right array
560576
IntervalIndex.from_breaks : Construct an IntervalIndex from an array of
561577
splits
562-
IntervalIndex.from_intervals : Construct an IntervalIndex from an array
563-
of Interval objects
564578
"""
565579
if len(data):
566580
left, right = [], []
@@ -571,15 +585,25 @@ def from_tuples(cls, data, closed='right', name=None, copy=False):
571585
if isna(d):
572586
lhs = rhs = np.nan
573587
else:
574-
lhs, rhs = d
588+
try:
589+
lhs, rhs = d
590+
except ValueError:
591+
msg = ('IntervalIndex.from_tuples requires tuples of '
592+
'length 2, got {tpl}').format(tpl=d)
593+
raise ValueError(msg)
594+
except TypeError:
595+
msg = ('IntervalIndex.from_tuples received an invalid '
596+
'item, {tpl}').format(tpl=d)
597+
raise TypeError(msg)
575598
left.append(lhs)
576599
right.append(rhs)
577600

578601
# TODO
579602
# if we have nulls and we previous had *only*
580603
# integer data, then we have changed the dtype
581604

582-
return cls.from_arrays(left, right, closed, name=name, copy=False)
605+
return cls.from_arrays(left, right, closed, name=name, copy=False,
606+
dtype=dtype)
583607

584608
def to_tuples(self, na_tuple=True):
585609
"""
@@ -921,7 +945,7 @@ def get_loc(self, key, method=None):
921945
Examples
922946
---------
923947
>>> i1, i2 = pd.Interval(0, 1), pd.Interval(1, 2)
924-
>>> index = pd.IntervalIndex.from_intervals([i1, i2])
948+
>>> index = pd.IntervalIndex([i1, i2])
925949
>>> index.get_loc(1)
926950
0
927951
@@ -937,7 +961,7 @@ def get_loc(self, key, method=None):
937961
relevant intervals.
938962
939963
>>> i3 = pd.Interval(0, 2)
940-
>>> overlapping_index = pd.IntervalIndex.from_intervals([i2, i3])
964+
>>> overlapping_index = pd.IntervalIndex([i2, i3])
941965
>>> overlapping_index.get_loc(1.5)
942966
array([0, 1], dtype=int64)
943967
"""

pandas/core/reshape/tile.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,8 +348,7 @@ def _format_labels(bins, precision, right=True,
348348
# account that we are all right closed
349349
v = adjust(labels[0].left)
350350

351-
i = IntervalIndex.from_intervals(
352-
[Interval(v, labels[0].right, closed='right')])
351+
i = IntervalIndex([Interval(v, labels[0].right, closed='right')])
353352
labels = i.append(labels[1:])
354353

355354
return labels

pandas/tests/categorical/test_constructors.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,7 @@ def test_constructor_unsortable(self):
7676
def test_constructor_interval(self):
7777
result = Categorical([Interval(1, 2), Interval(2, 3), Interval(3, 6)],
7878
ordered=True)
79-
ii = IntervalIndex.from_intervals([Interval(1, 2),
80-
Interval(2, 3),
81-
Interval(3, 6)])
79+
ii = IntervalIndex([Interval(1, 2), Interval(2, 3), Interval(3, 6)])
8280
exp = Categorical(ii, ordered=True)
8381
tm.assert_categorical_equal(result, exp)
8482
tm.assert_index_equal(result.categories, ii)

0 commit comments

Comments
 (0)