Repository: geopandas/geopandas

Issue:
BUG: explode() fails if 'level_1' is in columns
If there is a column `'level_1'` present in GeoDataFrame, it will fail with `NotImplementedError`.

```py
gdf = gpd.read_file(gpd.datasets.get_path('nybb'))
gdf['level_1'] = 0
gdf.explode()
```

<details>

```py

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~/anaconda3/envs/uap/lib/python3.8/site-packages/pandas/core/arrays/categorical.py in __init__(self, values, categories, ordered, dtype, fastpath)
    354             try:
--> 355                 codes, categories = factorize(values, sort=True)
    356             except TypeError:

~/anaconda3/envs/uap/lib/python3.8/site-packages/pandas/core/algorithms.py in factorize(values, sort, na_sentinel, size_hint)
    634 
--> 635         codes, uniques = _factorize_array(
    636             values, na_sentinel=na_sentinel, size_hint=size_hint, na_value=na_value

~/anaconda3/envs/uap/lib/python3.8/site-packages/pandas/core/algorithms.py in _factorize_array(values, na_sentinel, size_hint, na_value)
    483     table = hash_klass(size_hint or len(values))
--> 484     uniques, codes = table.factorize(values, na_sentinel=na_sentinel, na_value=na_value)
    485 

pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.Int64HashTable.factorize()

ValueError: Buffer has wrong number of dimensions (expected 1, got 2)

During handling of the above exception, another exception occurred:

NotImplementedError                       Traceback (most recent call last)
<ipython-input-66-973d8edd5e42> in <module>
----> 1 gdf.explode()

~/Dropbox/Python/geopandas/geopandas/geopandas/geodataframe.py in explode(self)
    844         # reset to MultiIndex, otherwise df index is only first level of
    845         # exploded GeoSeries index.
--> 846         df.set_index(exploded_index, append=True, inplace=True)
    847         df.index.names = list(self.index.names) + [None]
    848         geo_df = df.set_geometry(self._geometry_column_name)

~/anaconda3/envs/uap/lib/python3.8/site-packages/pandas/core/frame.py in set_index(self, keys, drop, append, inplace, verify_integrity)
   4349                 )
   4350 
-> 4351         index = ensure_index_from_sequences(arrays, names)
   4352 
   4353         if verify_integrity and not index.is_unique:

~/anaconda3/envs/uap/lib/python3.8/site-packages/pandas/core/indexes/base.py in ensure_index_from_sequences(sequences, names)
   5288         return Index(sequences[0], name=names)
   5289     else:
-> 5290         return MultiIndex.from_arrays(sequences, names=names)
   5291 
   5292 

~/anaconda3/envs/uap/lib/python3.8/site-packages/pandas/core/indexes/multi.py in from_arrays(cls, arrays, sortorder, names)
    425                 raise ValueError("all arrays must be same length")
    426 
--> 427         codes, levels = factorize_from_iterables(arrays)
    428         if names is lib.no_default:
    429             names = [getattr(arr, "name", None) for arr in arrays]

~/anaconda3/envs/uap/lib/python3.8/site-packages/pandas/core/arrays/categorical.py in factorize_from_iterables(iterables)
   2706         # For consistency, it should return a list of 2 lists.
   2707         return [[], []]
-> 2708     return map(list, zip(*(factorize_from_iterable(it) for it in iterables)))

~/anaconda3/envs/uap/lib/python3.8/site-packages/pandas/core/arrays/categorical.py in <genexpr>(.0)
   2706         # For consistency, it should return a list of 2 lists.
   2707         return [[], []]
-> 2708     return map(list, zip(*(factorize_from_iterable(it) for it in iterables)))

~/anaconda3/envs/uap/lib/python3.8/site-packages/pandas/core/arrays/categorical.py in factorize_from_iterable(values)
   2678         # but only the resulting categories, the order of which is independent
   2679         # from ordered. Set ordered to False as default. See GH #15457
-> 2680         cat = Categorical(values, ordered=False)
   2681         categories = cat.categories
   2682         codes = cat.codes

~/anaconda3/envs/uap/lib/python3.8/site-packages/pandas/core/arrays/categorical.py in __init__(self, values, categories, ordered, dtype, fastpath)
    367 
    368                 # FIXME
--> 369                 raise NotImplementedError(
    370                     "> 1 ndim Categorical are not supported at this time"
    371                 )

NotImplementedError: > 1 ndim Categorical are not supported at this time
```
</details>

We should ideally fix it, or at least catch it and provide meaningful Error message.


Discussion:

Resolving patch:
diff --git a/geopandas/geodataframe.py b/geopandas/geodataframe.py
--- a/geopandas/geodataframe.py
+++ b/geopandas/geodataframe.py
@@ -877,6 +877,9 @@ def explode(self):
         """
         df_copy = self.copy()
 
+        if "level_1" in df_copy.columns:  # GH1393
+            df_copy = df_copy.rename(columns={"level_1": "__level_1"})
+
         exploded_geom = df_copy.geometry.explode().reset_index(level=-1)
         exploded_index = exploded_geom.columns[0]
 
@@ -887,6 +890,10 @@ def explode(self):
         # exploded GeoSeries index.
         df.set_index(exploded_index, append=True, inplace=True)
         df.index.names = list(self.index.names) + [None]
+
+        if "__level_1" in df.columns:
+            df = df.rename(columns={"__level_1": "level_1"})
+
         geo_df = df.set_geometry(self._geometry_column_name)
         return geo_df
 


Test patch:
diff --git a/geopandas/tests/test_geom_methods.py b/geopandas/tests/test_geom_methods.py
--- a/geopandas/tests/test_geom_methods.py
+++ b/geopandas/tests/test_geom_methods.py
@@ -12,6 +12,7 @@
 from geopandas.base import GeoPandasBase
 
 from geopandas.tests.util import assert_geoseries_equal, geom_almost_equals, geom_equals
+from geopandas._compat import PANDAS_GE_024
 from pandas.testing import assert_frame_equal, assert_series_equal
 import pytest
 
@@ -658,6 +659,27 @@ def test_explode_geodataframe(self, index_name):
         expected_df = expected_df.set_index(expected_index)
         assert_frame_equal(test_df, expected_df)
 
+    @pytest.mark.parametrize("index_name", [None, "test"])
+    def test_explode_geodataframe_level_1(self, index_name):
+        # GH1393
+        s = GeoSeries([MultiPoint([Point(1, 2), Point(2, 3)]), Point(5, 5)])
+        df = GeoDataFrame({"level_1": [1, 2], "geometry": s})
+        df.index.name = index_name
+
+        test_df = df.explode()
+
+        expected_s = GeoSeries([Point(1, 2), Point(2, 3), Point(5, 5)])
+        expected_df = GeoDataFrame({"level_1": [1, 1, 2], "geometry": expected_s})
+        expected_index = MultiIndex(
+            [[0, 1], [0, 1]],  # levels
+            [[0, 0, 1], [0, 1, 0]],  # labels/codes
+            names=[index_name, None],
+        )
+        expected_df = expected_df.set_index(expected_index)
+        if not PANDAS_GE_024:
+            expected_df = expected_df[["level_1", "geometry"]]
+        assert_frame_equal(test_df, expected_df)
+
     #
     # Test '&', '|', '^', and '-'
     #