diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 56a5412d4ecfc..c977585ef2942 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -455,7 +455,7 @@ Conversion - Bug in :meth:`Series.view` and :meth:`Index.view` when converting between datetime-like (``datetime64[ns]``, ``datetime64[ns, tz]``, ``timedelta64``, ``period``) dtypes (:issue:`39788`) - Bug in creating a :class:`DataFrame` from an empty ``np.recarray`` not retaining the original dtypes (:issue:`40121`) - Bug in :class:`DataFrame` failing to raise ``TypeError`` when constructing from a ``frozenset`` (:issue:`40163`) -- +- Bug in :class:`Index` construction silently ignoring a passed ``dtype`` when the data cannot be cast to that dtype (:issue:`21311`) Strings ^^^^^^^ diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 8b67b98b32f7f..edc740b425c56 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -66,7 +66,6 @@ can_hold_element, find_common_type, infer_dtype_from, - maybe_cast_to_integer_array, validate_numeric_casting, ) from pandas.core.dtypes.common import ( @@ -144,6 +143,7 @@ from pandas.core.construction import ( ensure_wrapped_if_datetimelike, extract_array, + sanitize_array, ) from pandas.core.indexers import deprecate_ndim_indexing from pandas.core.indexes.frozen import FrozenList @@ -399,18 +399,17 @@ def __new__( # index-like elif isinstance(data, (np.ndarray, Index, ABCSeries)): + if isinstance(data, ABCMultiIndex): + data = data._values + if dtype is not None: # we need to avoid having numpy coerce # things that look like ints/floats to ints unless # they are actually ints, e.g. '0' and 0.0 # should not be coerced # GH 11836 + data = sanitize_array(data, None, dtype=dtype, copy=copy) - # error: Argument 1 to "_maybe_cast_with_dtype" has incompatible type - # "Union[ndarray, Index, Series]"; expected "ndarray" - data = _maybe_cast_with_dtype( - data, dtype, copy # type: ignore[arg-type] - ) dtype = data.dtype if data.dtype.kind in ["i", "u", "f"]: @@ -6366,56 +6365,6 @@ def maybe_extract_name(name, obj, cls) -> Hashable: return name -def _maybe_cast_with_dtype(data: np.ndarray, dtype: np.dtype, copy: bool) -> np.ndarray: - """ - If a dtype is passed, cast to the closest matching dtype that is supported - by Index. - - Parameters - ---------- - data : np.ndarray - dtype : np.dtype - copy : bool - - Returns - ------- - np.ndarray - """ - # we need to avoid having numpy coerce - # things that look like ints/floats to ints unless - # they are actually ints, e.g. '0' and 0.0 - # should not be coerced - # GH 11836 - if is_integer_dtype(dtype): - inferred = lib.infer_dtype(data, skipna=False) - if inferred == "integer": - data = maybe_cast_to_integer_array(data, dtype, copy=copy) - elif inferred in ["floating", "mixed-integer-float"]: - if isna(data).any(): - raise ValueError("cannot convert float NaN to integer") - - if inferred == "mixed-integer-float": - data = maybe_cast_to_integer_array(data, dtype) - - # If we are actually all equal to integers, - # then coerce to integer. - try: - data = _try_convert_to_int_array(data, copy, dtype) - except ValueError: - data = np.array(data, dtype=np.float64, copy=copy) - - elif inferred != "string": - data = data.astype(dtype) - elif is_float_dtype(dtype): - inferred = lib.infer_dtype(data, skipna=False) - if inferred != "string": - data = data.astype(dtype) - else: - data = np.array(data, dtype=dtype, copy=copy) - - return data - - def _maybe_cast_data_without_dtype(subarr): """ If we have an arraylike input but no passed dtype, try to infer diff --git a/pandas/tests/indexes/base_class/test_constructors.py b/pandas/tests/indexes/base_class/test_constructors.py index 0c4f9c6d759b9..bc894579340ab 100644 --- a/pandas/tests/indexes/base_class/test_constructors.py +++ b/pandas/tests/indexes/base_class/test_constructors.py @@ -36,7 +36,6 @@ def test_constructor_wrong_kwargs(self): with tm.assert_produces_warning(FutureWarning): Index([], foo="bar") - @pytest.mark.xfail(reason="see GH#21311: Index doesn't enforce dtype argument") def test_constructor_cast(self): msg = "could not convert string to float" with pytest.raises(ValueError, match=msg):