Repository: geopandas/geopandas

Issue:
ENH: Support dissolve(by=None) as unary_union of all geoms
In order to have one unique geometry to deal with, I'm trying to dissolve all many edges that compose a route using the `.dissolve()` method on a GeoDataFrame instance but it throws me an error (while `by=None` is the default value);

```
gdf_unique_route = gdf_route_edges.dissolve() # or gdf_route_edges.dissolve(by=None) 

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-160-808abd5a70f0> in <module>
----> 1 gdf_unique_route = gdf_route_edges.dissolve(by=None)

/usr/local/lib/python3.6/dist-packages/geopandas/geodataframe.py in dissolve(self, by, aggfunc, as_index)
    684         # Process non-spatial component
    685         data = self.drop(labels=self.geometry.name, axis=1)
--> 686         aggregated_data = data.groupby(by=by).agg(aggfunc)
    687 
    688         # Process spatial component

/usr/local/lib/python3.6/dist-packages/pandas/core/frame.py in groupby(self, by, axis, level, as_index, sort, group_keys, squeeze, observed)
   5796 
   5797         if level is None and by is None:
-> 5798             raise TypeError("You have to supply one of 'by' and 'level'")
   5799         axis = self._get_axis_number(axis)
   5800 

TypeError: You have to supply one of 'by' and 'level'

```
If I use `by='attribute1` it works, but I end up with a few geometries instead of only one.      
I really want to dissolve them all, regardless of their attributes, so I used the default argument values.    

### Help on the `.dissolve()` method is as follows:

```
Help on method dissolve in module geopandas.geodataframe:

dissolve(by=None, aggfunc='first', as_index=True) method of geopandas.geodataframe.GeoDataFrame instance
    Dissolve geometries within `groupby` into single observation.
    This is accomplished by applying the `unary_union` method
    to all geometries within a groupself.
    
    Observations associated with each `groupby` group will be aggregated
    using the `aggfunc`.
    
    Parameters
    ----------
    by : string, default None
        Column whose values define groups to be dissolved
    aggfunc : function or string, default "first"
        Aggregation function for manipulation of data associated
        with each group. Passed to pandas `groupby.agg` method.
    as_index : boolean, default True
        If true, groupby columns become index of result.
    
    Returns
    -------
    GeoDataFrame

```
There is no such `level` arg in there, it's something used by pandas DataFrame [`.groupy()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.groupby.html) method actually (which you probably rely on).    

As I was facing this error using GeoPandas so I'm reporting it here, but let me know if it should be reported to Pandas developers. 

Thanks a lot!

### Environment

Ubuntu 18.04    
Python  3.6.9 (default, Nov  7 2019, 10:44:02)    
Numpy 1.18.2    
Pandas 1.0.3    
GeoPandas 0.7.0    
Anything useful I missed?



Discussion:
You have to specify column in `by`. It cannot be None. We should probably catch this error and raise something more useful on our side before it gets to pandas.groupby. If you want to dissolve all geometries into one, use `df.geometry.unary_union` instead of dissolve.
Another solution on our side would be to do `unary_union` automatically under the hood of no `by` is specified. That makes more sense.
Yes, indeed, `unary_union`, is working great, but it is directly returning a shapely geometry instead of a GeoDataFrame, like it is actually the case when using `dissolve()`.     
Because of that, if `unary_union` is used under the hood of `dissolve()`, depending on the `by` value, the output type will change.

For people still wanting a GeoDataFrame on the ouput, a workaround could be to generate a new column with a unique value for every records and then dissolve on this column. It probably doesn't make much sense but it works; in my situation I have data from OSM which have a field named `key` which is always 0 and the `dissolve(by='key')` is working:

![Untitled](https://user-images.githubusercontent.com/19967599/79472880-a97d3100-8004-11ea-9490-8e4660abed2d.png)





Resolving patch:
diff --git a/geopandas/geodataframe.py b/geopandas/geodataframe.py
--- a/geopandas/geodataframe.py
+++ b/geopandas/geodataframe.py
@@ -934,7 +934,8 @@ def dissolve(self, by=None, aggfunc="first", as_index=True):
         Parameters
         ----------
         by : string, default None
-            Column whose values define groups to be dissolved
+            Column whose values define groups to be dissolved. If None,
+            whole GeoDataFrame is considered a single group.
         aggfunc : function or string, default "first"
             Aggregation function for manipulation of data associated
             with each group. Passed to pandas `groupby.agg` method.
@@ -946,6 +947,9 @@ def dissolve(self, by=None, aggfunc="first", as_index=True):
         GeoDataFrame
         """
 
+        if by is None:
+            by = np.zeros(len(self), dtype="int64")
+
         # Process non-spatial component
         data = self.drop(labels=self.geometry.name, axis=1)
         aggregated_data = data.groupby(by=by).agg(aggfunc)


Test patch:
diff --git a/geopandas/tests/test_dissolve.py b/geopandas/tests/test_dissolve.py
--- a/geopandas/tests/test_dissolve.py
+++ b/geopandas/tests/test_dissolve.py
@@ -99,3 +99,32 @@ def test_reset_index(nybb_polydf, first):
     test = nybb_polydf.dissolve("manhattan_bronx", as_index=False)
     comparison = first.reset_index()
     assert_frame_equal(comparison, test, check_column_type=False)
+
+
+def test_dissolve_none(nybb_polydf):
+    test = nybb_polydf.dissolve(by=None)
+    expected = GeoDataFrame(
+        {
+            nybb_polydf.geometry.name: [nybb_polydf.geometry.unary_union],
+            "BoroName": ["Staten Island"],
+            "BoroCode": [5],
+            "manhattan_bronx": [5],
+        },
+        geometry=nybb_polydf.geometry.name,
+        crs=nybb_polydf.crs,
+    )
+    assert_frame_equal(expected, test, check_column_type=False)
+
+
+def test_dissolve_none_mean(nybb_polydf):
+    test = nybb_polydf.dissolve(aggfunc="mean")
+    expected = GeoDataFrame(
+        {
+            nybb_polydf.geometry.name: [nybb_polydf.geometry.unary_union],
+            "BoroCode": [3.0],
+            "manhattan_bronx": [5.4],
+        },
+        geometry=nybb_polydf.geometry.name,
+        crs=nybb_polydf.crs,
+    )
+    assert_frame_equal(expected, test, check_column_type=False)