Repository: pandas-dev/pandas

Issue:
BUG: Trying to use convert_dtypes with dtype_backend='pyarrow' specified on empty categorical series results in error or null[pyarrow] dtype
### Pandas version checks

- [X] I have checked that this issue has not already been reported.

- [X] I have confirmed this bug exists on the [latest version](https://pandas.pydata.org/docs/whatsnew/index.html) of pandas.

- [X] I have confirmed this bug exists on the [main branch](https://pandas.pydata.org/docs/dev/getting_started/install.html#installing-the-development-version-of-pandas) of pandas.


### Reproducible Example

```python
import pandas as pd

# example 1
ser1 = pd.Series(pd.Categorical([None] * 5))
converted1 = ser1.convert_dtypes(dtype_backend="pyarrow")

# example 2
ser2 = pd.Series(pd.Categorical([None] * 5, categories=["S1", "S2"]))
converted2 = ser2.convert_dtypes(dtype_backend="pyarrow")
```


### Issue Description

Trying to convert categorical series with empty categories results in `pyarrow.lib.ArrowNotImplementedError: NumPyConverter doesn't implement <null> conversion. `.
Trying to convert categorical series with non-empty categories returns null[pyarrow] dtype.

This is inconsistent with regular behavior when converting categoricals - if series is not empty, convert_dtypes call returns categorical dtype (essentially ignoring the requested pyarrow backend conversion).

I encountered this issue when trying to read files (like parquet, spss, ...) where some categorical variables are empty...

### Expected Behavior

Returning categorical dtype with propagated categories, and not raising errors.

### Installed Versions

<details>

INSTALLED VERSIONS
------------------
commit                : 00855f81bd84cc6ed9ae42c5f66916b2208dbe04
python                : 3.12.0
python-bits           : 64
OS                    : Windows
OS-release            : 11
Version               : 10.0.22631
machine               : AMD64
processor             : Intel64 Family 6 Model 165 Stepping 2, GenuineIntel
byteorder             : little
LC_ALL                : None
LANG                  : None
LOCALE                : English_United States.1252
pandas                : 3.0.0.dev0+1536.g00855f81bd
numpy                 : 1.26.4
dateutil              : 2.9.0.post0
pip                   : 24.2
Cython                : 3.0.11
sphinx                : 8.0.2
IPython               : 8.27.0
adbc-driver-postgresql: None
adbc-driver-sqlite    : None
bs4                   : 4.12.3
blosc                 : None
bottleneck            : 1.4.0
fastparquet           : 2024.5.0
fsspec                : 2024.9.0
html5lib              : 1.1
hypothesis            : 6.112.2
gcsfs                 : 2024.9.0post1
jinja2                : 3.1.4
lxml.etree            : 5.3.0
matplotlib            : 3.9.2
numba                 : 0.60.0
numexpr               : 2.10.1
odfpy                 : None
openpyxl              : 3.1.5
psycopg2              : 2.9.9
pymysql               : 1.4.6
pyarrow               : 17.0.0
pyreadstat            : 1.2.7
pytest                : 8.3.3
python-calamine       : None
pytz                  : 2024.2
pyxlsb                : 1.0.10
s3fs                  : 2024.9.0
scipy                 : 1.14.1
sqlalchemy            : 2.0.35
tables                : 3.10.1
tabulate              : 0.9.0
xarray                : 2024.9.0
xlrd                  : 2.0.1
xlsxwriter            : 3.2.0
zstandard             : 0.23.0
tzdata                : 2024.2
qtpy                  : None
pyqt5                 : None

</details>


Discussion:

Patch:
diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst
index e03a1154b0..2bb1e4af21 100644
--- a/doc/source/whatsnew/v3.0.0.rst
+++ b/doc/source/whatsnew/v3.0.0.rst
@@ -636,6 +636,7 @@ Bug fixes
 Categorical
 ^^^^^^^^^^^
 - Bug in :func:`Series.apply` where ``nan`` was ignored for :class:`CategoricalDtype` (:issue:`59938`)
+- Bug in :meth:`Series.convert_dtypes` with ``dtype_backend="pyarrow"`` where empty :class:`CategoricalDtype` :class:`Series` raised an error or got converted to ``null[pyarrow]`` (:issue:`59934`)
 -
 
 Datetimelike
diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py
index f11aefeeaa..dae04ba624 100644
--- a/pandas/core/dtypes/cast.py
+++ b/pandas/core/dtypes/cast.py
@@ -1127,6 +1127,7 @@ def convert_dtypes(
             or (
                 inferred_dtype.kind not in "iufcb"
                 and not isinstance(inferred_dtype, StringDtype)
+                and not isinstance(inferred_dtype, CategoricalDtype)
             )
         ):
             if isinstance(inferred_dtype, PandasExtensionDtype) and not isinstance(


Test patch:
diff --git a/pandas/tests/frame/methods/test_convert_dtypes.py b/pandas/tests/frame/methods/test_convert_dtypes.py
index d0f3020475..ab847e2f8e 100644
--- a/pandas/tests/frame/methods/test_convert_dtypes.py
+++ b/pandas/tests/frame/methods/test_convert_dtypes.py
@@ -3,6 +3,8 @@ import datetime
 import numpy as np
 import pytest
 
+import pandas.util._test_decorators as td
+
 import pandas as pd
 import pandas._testing as tm
 
@@ -35,6 +37,19 @@ class TestConvertDtypes:
         empty_df = pd.DataFrame()
         tm.assert_frame_equal(empty_df, empty_df.convert_dtypes())
 
+    @td.skip_if_no("pyarrow")
+    def test_convert_empty_categorical_to_pyarrow(self):
+        # GH#59934
+        df = pd.DataFrame(
+            {
+                "A": pd.Categorical([None] * 5),
+                "B": pd.Categorical([None] * 5, categories=["B1", "B2"]),
+            }
+        )
+        converted = df.convert_dtypes(dtype_backend="pyarrow")
+        expected = df
+        tm.assert_frame_equal(converted, expected)
+
     def test_convert_dtypes_retain_column_names(self):
         # GH#41435
         df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
diff --git a/pandas/tests/series/methods/test_convert_dtypes.py b/pandas/tests/series/methods/test_convert_dtypes.py
index d373386108..324e03894e 100644
--- a/pandas/tests/series/methods/test_convert_dtypes.py
+++ b/pandas/tests/series/methods/test_convert_dtypes.py
@@ -4,6 +4,7 @@ import numpy as np
 import pytest
 
 from pandas._libs import lib
+import pandas.util._test_decorators as td
 
 import pandas as pd
 import pandas._testing as tm
@@ -298,6 +299,15 @@ class TestSeriesConvertDtypes:
         expected = pd.Series([None, None], dtype=pd.ArrowDtype(pa.null()))
         tm.assert_series_equal(result, expected)
 
+    @td.skip_if_no("pyarrow")
+    @pytest.mark.parametrize("categories", [None, ["S1", "S2"]])
+    def test_convert_empty_categorical_to_pyarrow(self, categories):
+        # GH#59934
+        ser = pd.Series(pd.Categorical([None] * 5, categories=categories))
+        converted = ser.convert_dtypes(dtype_backend="pyarrow")
+        expected = ser
+        tm.assert_series_equal(converted, expected)
+
     def test_convert_dtype_pyarrow_timezone_preserve(self):
         # GH 60237
         pytest.importorskip("pyarrow")