{"record":{"id":"cf37c4a1c9256b6b","repo":"pandas-dev/pandas","slug":"cannot-safely-cast-non-equivalent-values-dtype-t","errorCode":null,"errorMessage":"cannot safely cast non-equivalent {values.dtype} to {np.dtype(dtype)}","messagePattern":"cannot safely cast non-equivalent (.+?) to (.+?)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"pandas/core/arrays/integer.py","lineNumber":69,"sourceCode":"    def _get_dtype_mapping(cls) -> dict[np.dtype, IntegerDtype]:\n        return NUMPY_INT_TO_DTYPE\n\n    @classmethod\n    def _safe_cast(cls, values: np.ndarray, dtype: np.dtype, copy: bool) -> np.ndarray:\n        \"\"\"\n        Safely cast the values to the given dtype.\n\n        \"safe\" in this context means the casting is lossless. e.g. if 'values'\n        has a floating dtype, each value must be an integer.\n        \"\"\"\n        try:\n            return values.astype(dtype, casting=\"safe\", copy=copy)\n        except TypeError as err:\n            casted = values.astype(dtype, copy=copy)\n            if (casted == values).all():\n                return casted\n\n            raise TypeError(\n                f\"cannot safely cast non-equivalent {values.dtype} to {np.dtype(dtype)}\"\n            ) from err\n\n\n@set_module(\"pandas.arrays\")\nclass IntegerArray(NumericArray):\n    \"\"\"\n    Array of integer (optional missing) values.\n\n    Uses :attr:`pandas.NA` as the missing value.\n\n    .. warning::\n\n       IntegerArray is currently experimental, and its API or internal\n       implementation may change without warning.\n\n    We represent an IntegerArray with 2 numpy arrays:\n","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/pandas-dev/pandas/blob/3b7651241d4da534b3559b60ef128e1c34f54116/pandas/core/arrays/integer.py#L51-L87","documentation":"Raised by `IntegerArray._safe_cast` (NumericArray path) when lossless casting is required but impossible. It first tries numpy `astype(..., casting='safe')`; on failure it does an unsafe cast and compares element-wise — if any value changed, the cast is not equivalent and the operation is rejected. This protects integer ExtensionArrays from silently truncating floats or narrowing integers.","triggerScenarios":"Assigning a float array containing non-integer values (e.g. 1.5) into an IntegerArray/Int64 column; constructing `pd.array([1.5, 2.0], dtype='Int64')`; `astype('Int64')` on a float Series with fractional values; read_csv inferring float then coercing to nullable Int with non-integer data.","commonSituations":"Loading data with NaN-imputed floats into an 'Int64' column where imputation produced fractional values; merging/joining that upcasts to float64 then casting back to Int32; user passes `dtype='Int8'` to a column whose values exceed the range; arithmetic producing floats assigned back to integer nullable dtype.","solutions":["Round or floor the floats first if truncation is intended: `s.round().astype('Int64')` or `np.floor(s).astype('Int64')`.","Use a nullable float dtype (`'Float64'`) instead of integer if fractional values are valid.","Drop or correct the non-integer rows before casting: `s[s == s.floor()].astype('Int64')`.","Pass `copy=False, casting='unsafe'` only via the underlying numpy array if you accept the loss; do not rely on the ExtensionArray path to do this."],"exampleFix":"# before\npd.array([1.5, 2.0, 3.0], dtype='Int64')  # 1.5 is non-equivalent\n\n# after - decide semantics explicitly\npd.array([1.5, 2.0, 3.0], dtype='Float64')        # keep precision\npd.array(np.floor([1.5, 2.0, 3.0]).astype(int), dtype='Int64')  # truncate","handlingStrategy":"validation","validationCode":"import numpy as np\ndef safe_to_int(s, dtype='Int64'):\n    s = pd.Series(s)\n    if np.issubdtype(s.dtype, np.floating):\n        if not (s.dropna() == s.dropna().floor()).all():\n            raise ValueError('non-integer floats present; cannot safely cast to Int')\n    return s.astype(dtype)","typeGuard":"def is_lossless_int_castable(values: np.ndarray, dtype) -> bool:\n    try:\n        casted = values.astype(dtype, casting='safe')\n        return True\n    except TypeError:\n        casted = values.astype(dtype)\n        return (casted == values).all()","tryCatchPattern":"try:\n    arr = pd.array(values, dtype='Int64')\nexcept TypeError as e:\n    if 'cannot safely cast non-equivalent' in str(e):\n        arr = pd.array(values, dtype='Float64')  # or floor first\n    else:\n        raise","preventionTips":["Validate `s == s.floor()` for float Series before casting to nullable Int.","Prefer 'Float64' for any column that may legitimately hold fractions.","After read_csv, inspect dtype and fractional-ness before coerce-to-Int."],"tags":["casting","integer-array","nullable-integer","type-coercion"],"backgroundTag":null,"analyzedSha":"3b7651241d4da534b3559b60ef128e1c34f54116","analyzedAt":"2026-08-11T22:10:44.015Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}