diff --git a/doc/source/whatsnew/v0.24.0.rst b/doc/source/whatsnew/v0.24.0.rst index d8a204abcd93e..1eae6d43677f1 100644 --- a/doc/source/whatsnew/v0.24.0.rst +++ b/doc/source/whatsnew/v0.24.0.rst @@ -1405,6 +1405,7 @@ Conversion ^^^^^^^^^^ - Bug in :meth:`DataFrame.combine_first` in which column types were unexpectedly converted to float (:issue:`20699`) +- Bug in :meth:`DataFrame.clip` in which column types are not preserved and casted to float (:issue:`24162`) Strings ^^^^^^^ diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 65b219cc57f3a..efb3f20202c42 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -7132,19 +7132,13 @@ def _clip_with_scalar(self, lower, upper, inplace=False): (upper is not None and np.any(isna(upper)))): raise ValueError("Cannot use an NA value as a clip threshold") - result = self.values - mask = isna(result) - - with np.errstate(all='ignore'): - if upper is not None: - result = np.where(result >= upper, upper, result) - if lower is not None: - result = np.where(result <= lower, lower, result) - if np.any(mask): - result[mask] = np.nan - - axes_dict = self._construct_axes_dict() - result = self._constructor(result, **axes_dict).__finalize__(self) + result = self + if upper is not None: + subset = self.le(upper, axis=None) | isna(result) + result = result.where(subset, upper, axis=None, inplace=False) + if lower is not None: + subset = self.ge(lower, axis=None) | isna(result) + result = result.where(subset, lower, axis=None, inplace=False) if inplace: self._update_inplace(result) @@ -7153,7 +7147,6 @@ def _clip_with_scalar(self, lower, upper, inplace=False): def _clip_with_one_bound(self, threshold, method, axis, inplace): - inplace = validate_bool_kwarg(inplace, 'inplace') if axis is not None: axis = self._get_axis_number(axis) diff --git a/pandas/tests/frame/test_analytics.py b/pandas/tests/frame/test_analytics.py index 88262220015c7..baf763d7b1d03 100644 --- a/pandas/tests/frame/test_analytics.py +++ b/pandas/tests/frame/test_analytics.py @@ -1836,7 +1836,6 @@ def test_pct_change(self): tm.assert_frame_equal(result, expected) # Clip - def test_clip(self, float_frame): median = float_frame.median().median() original = float_frame.copy() @@ -1895,10 +1894,17 @@ def test_clip_mixed_numeric(self): df = DataFrame({'A': [1, 2, 3], 'B': [1., np.nan, 3.]}) result = df.clip(1, 2) - expected = DataFrame({'A': [1, 2, 2.], + expected = DataFrame({'A': [1, 2, 2], 'B': [1., np.nan, 2.]}) tm.assert_frame_equal(result, expected, check_like=True) + # GH 24162, clipping now preserves numeric types per column + df = DataFrame([[1, 2, 3.4], [3, 4, 5.6]], + columns=['foo', 'bar', 'baz']) + expected = df.dtypes + result = df.clip(upper=3).dtypes + tm.assert_series_equal(result, expected) + @pytest.mark.parametrize("inplace", [True, False]) def test_clip_against_series(self, inplace): # GH 6966