Repository: pandas-dev/pandas

Issue:
Categorical.replace returns unexpected dimensions for length 1 Series
The shapes and types of the first two are unexpected. Should they be? Just upgraded to pandas 0.24.0.

```python
pd.Series(pd.Categorical('A', categories=['A', 'B'])).replace({'A': 1, 'B': 2})
# Out[30]:
# 0    [1]
# dtype: object

pd.Series(pd.Categorical(('A', ), categories=['A', 'B'])).replace({'A': 1, 'B': 2})
# Out[31]:
# 0    [1]
# dtype: object

pd.Series(pd.Categorical(('A', 'B'), categories=['A', 'B'])).replace({'A': 1, 'B': 2})
# Out[32]:
# 0    1
# 1    2
# dtype: int64
```

I expect the first to be an int64 dtype with a shape of (1,). They return a Series with shape (1, 1) and dtype object.

<details>
INSTALLED VERSIONS
------------------
commit: None
python: 3.7.0.final.0
python-bits: 64
OS: Linux
OS-release: 4.4.0-116-generic
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: en_US.UTF-8

pandas: 0.24.0
pytest: 4.1.1
pip: 18.1
setuptools: 40.2.0
Cython: None
numpy: 1.15.4
scipy: 1.2.0
pyarrow: None
xarray: None
IPython: 7.2.0
sphinx: None
patsy: None
dateutil: 2.6.1
pytz: 2018.7
blosc: None
bottleneck: None
tables: None
numexpr: None
feather: None
matplotlib: 3.0.2
openpyxl: None
xlrd: None
xlwt: None
xlsxwriter: None
lxml.etree: None
bs4: None
html5lib: None
sqlalchemy: 1.2.15
pymysql: None
psycopg2: 2.7.6.1 (dt dec pq3 ext lo64)
jinja2: 2.10
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None
gcsfs: None
</details>



Discussion:
@jseabold Thanks for the report!

What version were you using before upgrading? I see this buggy behaviour in 0.23.4 as well.
> What version were you using before upgrading?

I was using 0.23.4. I upgraded to see if another bug was fixed (it was), which revealed this one for me. Don't know anything about whether this code would have worked in an older version.
OK, just to know whether it was a regression or not. Of course it is still a clear bug, but if it was a regression compared to 0.23.4, we should have tried to give it priority.

Resolving patch:
diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py
--- a/pandas/core/internals/blocks.py
+++ b/pandas/core/internals/blocks.py
@@ -739,7 +739,7 @@ def copy(self, deep=True):
         values = self.values
         if deep:
             values = values.copy()
-        return self.make_block_same_class(values)
+        return self.make_block_same_class(values, ndim=self.ndim)
 
     def replace(self, to_replace, value, inplace=False, filter=None,
                 regex=False, convert=True):


Test patch:
diff --git a/pandas/tests/series/test_replace.py b/pandas/tests/series/test_replace.py
--- a/pandas/tests/series/test_replace.py
+++ b/pandas/tests/series/test_replace.py
@@ -281,6 +281,20 @@ def test_replace_mixed_types_with_string(self):
         expected = pd.Series([1, np.nan, 3, np.nan, 4, 5])
         tm.assert_series_equal(expected, result)
 
+    @pytest.mark.parametrize("categorical, numeric", [
+        (pd.Categorical('A', categories=['A', 'B']), [1]),
+        (pd.Categorical(('A', ), categories=['A', 'B']), [1]),
+        (pd.Categorical(('A', 'B'), categories=['A', 'B']), [1, 2]),
+    ])
+    def test_replace_categorical(self, categorical, numeric):
+        # GH 24971
+        # Do not check if dtypes are equal due to a known issue that
+        # Categorical.replace sometimes coerces to object (GH 23305)
+        s = pd.Series(categorical)
+        result = s.replace({'A': 1, 'B': 2})
+        expected = pd.Series(numeric)
+        tm.assert_series_equal(expected, result, check_dtype=False)
+
     def test_replace_with_no_overflowerror(self):
         # GH 25616
         # casts to object without Exception from OverflowError