diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index f0ba1250b7f8d..c9e2e7e133133 100755 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -1086,6 +1086,7 @@ Indexing - Bug when indexing with ``.loc`` where the index was a :class:`CategoricalIndex` with non-string categories didn't work (:issue:`17569`, :issue:`30225`) - :meth:`Index.get_indexer_non_unique` could fail with ``TypeError`` in some cases, such as when searching for ints in a string index (:issue:`28257`) - Bug in :meth:`Float64Index.get_loc` incorrectly raising ``TypeError`` instead of ``KeyError`` (:issue:`29189`) +- Bug in :meth:`DataFrame.loc` with incorrect dtype when setting Categorical value in 1-row DataFrame (:issue:`25495`) - :meth:`MultiIndex.get_loc` can't find missing values when input includes missing values (:issue:`19132`) - Bug in :meth:`Series.__setitem__` incorrectly assigning values with boolean indexer when the length of new data matches the number of ``True`` values and new data is not a ``Series`` or an ``np.array`` (:issue:`30567`) - Bug in indexing with a :class:`PeriodIndex` incorrectly accepting integers representing years, use e.g. ``ser.loc["2007"]`` instead of ``ser.loc[2007]`` (:issue:`30763`) diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index a93211edf162b..43edc246da6dd 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -876,7 +876,11 @@ def setitem(self, indexer, value): # length checking check_setitem_lengths(indexer, value, values) - + exact_match = ( + len(arr_value.shape) + and arr_value.shape[0] == values.shape[0] + and arr_value.size == values.size + ) if is_empty_indexer(indexer, arr_value): # GH#8669 empty indexers pass @@ -886,14 +890,21 @@ def setitem(self, indexer, value): # be e.g. a list; see GH#6043 values[indexer] = value - # if we are an exact match (ex-broadcasting), - # then use the resultant dtype elif ( - len(arr_value.shape) - and arr_value.shape[0] == values.shape[0] - and arr_value.size == values.size + exact_match + and is_categorical_dtype(arr_value.dtype) + and not is_categorical_dtype(values) ): + # GH25495 - If the current dtype is not categorical, + # we need to create a new categorical block values[indexer] = value + return self.make_block(Categorical(self.values, dtype=arr_value.dtype)) + + # if we are an exact match (ex-broadcasting), + # then use the resultant dtype + elif exact_match: + values[indexer] = value + try: values = values.astype(arr_value.dtype) except ValueError: diff --git a/pandas/tests/frame/indexing/test_categorical.py b/pandas/tests/frame/indexing/test_categorical.py index 5de38915f04c1..a29c193676db2 100644 --- a/pandas/tests/frame/indexing/test_categorical.py +++ b/pandas/tests/frame/indexing/test_categorical.py @@ -354,6 +354,16 @@ def test_functions_no_warnings(self): df.value, range(0, 105, 10), right=False, labels=labels ) + def test_setitem_single_row_categorical(self): + # GH 25495 + df = DataFrame({"Alpha": ["a"], "Numeric": [0]}) + categories = pd.Categorical(df["Alpha"], categories=["a", "b", "c"]) + df.loc[:, "Alpha"] = categories + + result = df["Alpha"] + expected = Series(categories, index=df.index, name="Alpha") + tm.assert_series_equal(result, expected) + def test_loc_indexing_preserves_index_category_dtype(self): # GH 15166 df = DataFrame(