Repository: geopandas/geopandas

Issue:
REGR: indexing doesn't return geometry but Series if geometry is only column
One of the recent PRs caused a regression in indexing. If there's a single column in a GeoDataFrame, indexing using `df.loc[0].geometry` returns a Series instead of a shapely geometry (as it did before).

```py
import geopandas
df = geopandas.read_file(geopandas.datasets.get_path("nybb"))
df[["geometry"]].loc[0].geometry

geometry    MULTIPOLYGON (((970217.022 145643.332, 970227....
Name: 0, dtype: geometry
```

On 0.10.2:

```py
import geopandas
df = geopandas.read_file(geopandas.datasets.get_path("nybb"))
df[["geometry"]].loc[0].geometry

<shapely.geometry.multipolygon.MultiPolygon at 0x107377e50>
```

If there are other columns apart from geometry, it correctly returns the shapely geometry.

@m-richards I haven't looked in detail but I think it is caused by #2060, there was no other commit relevant to this.

Discussion:
jorisvandenbossche: I would maybe try the suggestion I made here: https://github.com/geopandas/geopandas/issues/2282#issuecomment-1004114394, requiring an actual geometry dtype in `_constructor_sliced`, and not just geometry objects
jorisvandenbossche: Whoops, I see you edited your comment there to say that this approach might not work. Will comment there.
m-richards: Alright, take three of this, this has been rather complicated to work through. Still don't love this approach as we're relying on stability of the inputs to _constructor_sliced, which used in almost 40 places (including in _ixs). Fulling expecting some comments to the effect of "this is too verbose", was trying to be explicit as I went, happy to cut back.
m-richards: I've been looking into the one failing test on pandas 1.0.5
```python
def test_apply_axis1_secondary_geo_cols(df):
    def identity(x):
        return x

    assert_object(df[["geometry2"]].apply(identity, axis=1), GeoDataFrame, None, None)
```

I don't really have a good way forward on this. Apply axis=1 is essentially a wrapped up for loop, so the geometry column information is lost - any because _constructor_sliced is now defined, the default assumption is that a slice is a pd.Series and not a GeoSeries.

So the only way I can see to fix this would be in `.apply`, to call `ensure_geometry` on every column in the input to apply that appears in the result of apply, and if any are successful, then promote the result to a geodataframe with no geometry column.
This doesn't seem ideal to introduce given the checks around this would run on all apply calls.

Given that the issue is only on the minimal environment with pandas 1.0.5, I was wondering if it would be okay to xfail or skip this case - the failure case has identical behaviour on 0.10.2, so the regression introduced here is only a regression in 0.11 master after #2060 up until now, so wouldn't actually be a regression for users of release versions only.

martinfleis: > Given that the issue is only on the minimal environment with pandas 1.0.5, I was wondering if it would be okay to xfail or skip this case

That is fine with me
jorisvandenbossche: Moving my last inline comment to the main thread:

> OK, so the question is then: what do we do with selecting a single row from a geometry-only GeoDataFrames? Are we fine with making that return a GeoSeries (with the possible breaking behaviour as reported in #2282)? Or do we have some other (more robust) way to find out that we are selecting a row? (overriding iloc, ...)

Personally, I think it is _maybe_ fine to return a GeoSeries for the row of a geometry-only GeoDataFrame. That indeed breaks some behaviour, but it seems solvable for downstream projects (use `["geometry"]` instead of `.geometry`).

If we do want to fix this to not return a GeoSeries, some possible options:

- Override `loc`/`iloc` to catch this. We really want to avoid this, so this is only a last resort.
- Checking the dtype in `_constructor_sliced` is not sufficient, as pandas will return a proper GeometryArray (or wrapped in manager) if all columns are geometry dtype.
- Inside `_constructor_sliced`, we still have access to the parent `self`, so for a case of `df.(i)loc[0, :]`, we actually could compare the length of the passed data with the shape of the dataframe, and infer whether we are selecting a row. Big downside: this breaks down for square GeoDataFrames (if n_rows == n_columns).
- ...?
m-richards: > Moving my last inline comment to the main thread:
> 
> > OK, so the question is then: what do we do with selecting a single row from a geometry-only GeoDataFrames? Are we fine with making that return a GeoSeries (with the possible breaking behaviour as reported in #2282)? Or do we have some other (more robust) way to find out that we are selecting a row? (overriding iloc, ...)
> 
> Personally, I think it is _maybe_ fine to return a GeoSeries for the row of a geometry-only GeoDataFrame. That indeed breaks some behaviour, but it seems solvable for downstream projects (use `["geometry"]` instead of `.geometry`).
> 
> If we do want to fix this to not return a GeoSeries, some possible options:
> 
> - Override `loc`/`iloc` to catch this. We really want to avoid this, so this is only a last resort.
> - Checking the dtype in `_constructor_sliced` is not sufficient, as pandas will return a proper GeometryArray (or wrapped in manager) if all columns are geometry dtype.
> - Inside `_constructor_sliced`, we still have access to the parent `self`, so for a case of `df.(i)loc[0, :]`, we actually could compare the length of the passed data with the shape of the dataframe, and infer whether we are selecting a row. Big downside: this breaks down for square GeoDataFrames (if n_rows == n_columns).
> - ...?

I think that returning a Geoseries as a row is technically correct (although it is not useful). In terms of other options to avoid breaking the .geometry case, I'm brought back to my comment at the start of this PR, what if we move the GeoPandaBase.geometry to GeoPandaBase._geometry and thereby move the name collision in the regression to a more unlikely case of having a dataframe with column called _geometry?

This would be a little messy as Geodataframe.geometry is obviously useful, so we need to add that back as an alias. 
jorisvandenbossche: >  I'm brought back to my comment at the start of this PR, what if we move the GeoPandaBase.geometry to GeoPandaBase._geometry and thereby move the name collision in the regression to a more unlikely case of having a dataframe with column called _geometry?

That could be a long term solution if we would like that, but not on the short term for fixing the regression. Removing the `.geometry` attribute for GeoSeries would be a bigger breaking change, and if we want to do that, it's something we should certainly first deprecate before actually changing. 
So we can have that discussion, but IMO it wouldn't help for this specific PR / regression.
m-richards: 
> - Inside `_constructor_sliced`, we still have access to the parent `self`, so for a case of `df.(i)loc[0, :]`, we actually could compare the length of the passed data with the shape of the dataframe, and infer whether we are selecting a row. Big downside: this breaks down for square GeoDataFrames (if n_rows == n_columns).
> - ...?

Another thought, extending what you have above with length checks, what about checking that srs.index = self.index to identify column slices? Or perhaps srs.index = self.columns to identify row slices is slightly better presuming there are generally more rows than columns. 
jorisvandenbossche: Ah, that's indeed a good idea. And I suppose we might even be able to check for `is` instead of `==` (would need to check that with pandas). That's more robust than the length check.
m-richards: > Ah, that's indeed a good idea. And I suppose we might even be able to check for `is` instead of `==` (would need to check that with pandas). That's more robust than the length check.

With this now included (in the commits I've just pushed) this finally now seems like it is in an okay state ( perhaps pending https://github.com/geopandas/geopandas/pull/2436 (which is not essential, could instead put a test skip in for pandas 1.0.5) for a cleaner diff)

Patch:
diff --git a/geopandas/geodataframe.py b/geopandas/geodataframe.py
index 0f3f780d..e8a1e51a 100644
--- a/geopandas/geodataframe.py
+++ b/geopandas/geodataframe.py
@@ -13,7 +13,7 @@ from pyproj import CRS
 
 from geopandas.array import GeometryArray, GeometryDtype, from_shapely, to_wkb, to_wkt
 from geopandas.base import GeoPandasBase, is_geometry_type
-from geopandas.geoseries import GeoSeries, _geoseries_constructor_with_fallback
+from geopandas.geoseries import GeoSeries
 import geopandas.io
 from geopandas.explore import _explore
 from . import _compat as compat
@@ -1450,6 +1450,14 @@ individually so that features may have different properties
             else:
                 if self.crs is not None and result.crs is None:
                     result.set_crs(self.crs, inplace=True)
+        elif isinstance(result, Series):
+            # Reconstruct series GeometryDtype if lost by apply
+            try:
+                # Note CRS cannot be preserved in this case as func could refer
+                # to any column
+                result = _ensure_geometry(result)
+            except TypeError:
+                pass
 
         return result
 
@@ -1459,7 +1467,29 @@ individually so that features may have different properties
 
     @property
     def _constructor_sliced(self):
-        return _geoseries_constructor_with_fallback
+        def _geodataframe_constructor_sliced(data=None, index=None, crs=None, **kwargs):
+            """
+            A specialized (Geo)Series constructor which can fall back to a
+            Series if a certain operation does not produce geometries:
+
+            - We only return a GeoSeries if the data is actually of geometry
+              dtype (and so we don't try to convert geometry objects such as
+              the normal GeoSeries(..) constructor does with `_ensure_geometry`).
+            - When we get here from obtaining a row or column from a
+              GeoDataFrame, the goal is to only return a GeoSeries for a
+              geometry column, and not return a GeoSeries for a row that happened
+              to come from a DataFrame with only geometry dtype columns (and
+              thus could have a geometry dtype). Therefore, we don't return a
+              GeoSeries if we are sure we are in a row selection case (by
+              checking the identity of the index)
+            """
+            srs = pd.Series(data, index, **kwargs)
+            is_row_proxy = srs.index is self.columns
+            if is_geometry_type(srs) and not is_row_proxy:
+                srs = GeoSeries(srs, crs=crs)
+            return srs
+
+        return _geodataframe_constructor_sliced
 
     def __finalize__(self, other, method=None, **kwargs):
         """propagate metadata from other to self"""
@@ -1864,7 +1894,7 @@ individually so that features may have different properties
         warnings.warn(
             "'^' operator will be deprecated. Use the 'symmetric_difference' "
             "method instead.",
-            DeprecationWarning,
+            FutureWarning,
             stacklevel=2,
         )
         return self.geometry.symmetric_difference(other)
@@ -1873,7 +1903,7 @@ individually so that features may have different properties
         """Implement | operator as for builtin set type"""
         warnings.warn(
             "'|' operator will be deprecated. Use the 'union' method instead.",
-            DeprecationWarning,
+            FutureWarning,
             stacklevel=2,
         )
         return self.geometry.union(other)
@@ -1882,7 +1912,7 @@ individually so that features may have different properties
         """Implement & operator as for builtin set type"""
         warnings.warn(
             "'&' operator will be deprecated. Use the 'intersection' method instead.",
-            DeprecationWarning,
+            FutureWarning,
             stacklevel=2,
         )
         return self.geometry.intersection(other)
@@ -1891,7 +1921,7 @@ individually so that features may have different properties
         """Implement - operator as for builtin set type"""
         warnings.warn(
             "'-' operator will be deprecated. Use the 'difference' method instead.",
-            DeprecationWarning,
+            FutureWarning,
             stacklevel=2,
         )
         return self.geometry.difference(other)
diff --git a/geopandas/geoseries.py b/geopandas/geoseries.py
index eb9bd8e7..cc9b5fad 100644
--- a/geopandas/geoseries.py
+++ b/geopandas/geoseries.py
@@ -1245,7 +1245,7 @@ e": "Feature", "properties": {}, "geometry": {"type": "Point", "coordinates": [3
         warnings.warn(
             "'^' operator will be deprecated. Use the 'symmetric_difference' "
             "method instead.",
-            DeprecationWarning,
+            FutureWarning,
             stacklevel=2,
         )
         return self.symmetric_difference(other)
@@ -1254,7 +1254,7 @@ e": "Feature", "properties": {}, "geometry": {"type": "Point", "coordinates": [3
         """Implement | operator as for builtin set type"""
         warnings.warn(
             "'|' operator will be deprecated. Use the 'union' method instead.",
-            DeprecationWarning,
+            FutureWarning,
             stacklevel=2,
         )
         return self.union(other)
@@ -1263,7 +1263,7 @@ e": "Feature", "properties": {}, "geometry": {"type": "Point", "coordinates": [3
         """Implement & operator as for builtin set type"""
         warnings.warn(
             "'&' operator will be deprecated. Use the 'intersection' method instead.",
-            DeprecationWarning,
+            FutureWarning,
             stacklevel=2,
         )
         return self.intersection(other)
@@ -1272,7 +1272,7 @@ e": "Feature", "properties": {}, "geometry": {"type": "Point", "coordinates": [3
         """Implement - operator as for builtin set type"""
         warnings.warn(
             "'-' operator will be deprecated. Use the 'difference' method instead.",
-            DeprecationWarning,
+            FutureWarning,
             stacklevel=2,
         )
         return self.difference(other)
diff --git a/geopandas/io/file.py b/geopandas/io/file.py
index 0b55373c..9e36641b 100644
--- a/geopandas/io/file.py
+++ b/geopandas/io/file.py
@@ -269,7 +269,7 @@ def read_file(*args, **kwargs):
     warnings.warn(
         "geopandas.io.file.read_file() is intended for internal "
         "use only, and will be deprecated. Use geopandas.read_file() instead.",
-        DeprecationWarning,
+        FutureWarning,
         stacklevel=2,
     )
 
@@ -281,7 +281,7 @@ def to_file(*args, **kwargs):
         "geopandas.io.file.to_file() is intended for internal "
         "use only, and will be deprecated. Use GeoDataFrame.to_file() "
         "or GeoSeries.to_file() instead.",
-        DeprecationWarning,
+        FutureWarning,
         stacklevel=2,
     )
 
diff --git a/geopandas/io/sql.py b/geopandas/io/sql.py
index 06c00b95..93b8c188 100644
--- a/geopandas/io/sql.py
+++ b/geopandas/io/sql.py
@@ -182,7 +182,7 @@ def read_postgis(*args, **kwargs):
     warnings.warn(
         "geopandas.io.sql.read_postgis() is intended for internal "
         "use only, and will be deprecated. Use geopandas.read_postgis() instead.",
-        DeprecationWarning,
+        FutureWarning,
         stacklevel=2,
     )
 
diff --git a/geopandas/plotting.py b/geopandas/plotting.py
index 8555dccc..aa198d5c 100644
--- a/geopandas/plotting.py
+++ b/geopandas/plotting.py
@@ -11,14 +11,14 @@ from packaging.version import Version
 from ._decorator import doc
 
 
-def deprecated(new):
+def deprecated(new, warning_type=FutureWarning):
     """Helper to provide deprecation warning."""
 
     def old(*args, **kwargs):
         warnings.warn(
             "{} is intended for internal ".format(new.__name__[1:])
             + "use only, and will be deprecated.",
-            DeprecationWarning,
+            warning_type,
             stacklevel=2,
         )
         new(*args, **kwargs)
diff --git a/setup.py b/setup.py
index d12ea2f6..e5c40bda 100644
--- a/setup.py
+++ b/setup.py
@@ -34,7 +34,7 @@ if os.environ.get("READTHEDOCS", False) == "True":
 else:
     INSTALL_REQUIRES = [
         "pandas >= 1.0.0",
-        "shapely >= 1.7",
+        "shapely >= 1.7, < 2",
         "fiona >= 1.8",
         "pyproj >= 2.6.1.post1",
         "packaging",


Test patch:
diff --git a/geopandas/io/tests/test_file.py b/geopandas/io/tests/test_file.py
index 2e6314e2..ec631807 100644
--- a/geopandas/io/tests/test_file.py
+++ b/geopandas/io/tests/test_file.py
@@ -261,7 +261,7 @@ def test_to_file_empty(tmpdir):
 
 def test_to_file_privacy(tmpdir, df_nybb):
     tempfilename = os.path.join(str(tmpdir), "test.shp")
-    with pytest.warns(DeprecationWarning):
+    with pytest.warns(FutureWarning):
         geopandas.io.file.to_file(df_nybb, tempfilename)
 
 
@@ -679,7 +679,7 @@ def test_read_file_empty_shapefile(tmpdir):
 
 
 def test_read_file_privacy(tmpdir, df_nybb):
-    with pytest.warns(DeprecationWarning):
+    with pytest.warns(FutureWarning):
         geopandas.io.file.read_file(geopandas.datasets.get_path("nybb"))
 
 
diff --git a/geopandas/io/tests/test_sql.py b/geopandas/io/tests/test_sql.py
index 62d832fd..360fefa6 100644
--- a/geopandas/io/tests/test_sql.py
+++ b/geopandas/io/tests/test_sql.py
@@ -326,7 +326,7 @@ class TestIO:
         create_postgis(con, df_nybb)
 
         sql = "SELECT * FROM nybb;"
-        with pytest.warns(DeprecationWarning):
+        with pytest.warns(FutureWarning):
             geopandas.io.sql.read_postgis(sql, con)
 
     def test_write_postgis_default(self, engine_postgis, df_nybb):
diff --git a/geopandas/tests/test_geom_methods.py b/geopandas/tests/test_geom_methods.py
index 705b5eb1..013dc45e 100644
--- a/geopandas/tests/test_geom_methods.py
+++ b/geopandas/tests/test_geom_methods.py
@@ -1224,39 +1224,39 @@ class TestGeomMethods:
     # Test '&', '|', '^', and '-'
     #
     def test_intersection_operator(self):
-        with pytest.warns(DeprecationWarning):
+        with pytest.warns(FutureWarning):
             self._test_binary_operator("__and__", self.t1, self.g1, self.g2)
-        with pytest.warns(DeprecationWarning):
+        with pytest.warns(FutureWarning):
             self._test_binary_operator("__and__", self.t1, self.gdf1, self.g2)
 
     def test_union_operator(self):
-        with pytest.warns(DeprecationWarning):
+        with pytest.warns(FutureWarning):
             self._test_binary_operator("__or__", self.sq, self.g1, self.g2)
-        with pytest.warns(DeprecationWarning):
+        with pytest.warns(FutureWarning):
             self._test_binary_operator("__or__", self.sq, self.gdf1, self.g2)
 
     def test_union_operator_polygon(self):
-        with pytest.warns(DeprecationWarning):
+        with pytest.warns(FutureWarning):
             self._test_binary_operator("__or__", self.sq, self.g1, self.t2)
-        with pytest.warns(DeprecationWarning):
+        with pytest.warns(FutureWarning):
             self._test_binary_operator("__or__", self.sq, self.gdf1, self.t2)
 
     def test_symmetric_difference_operator(self):
-        with pytest.warns(DeprecationWarning):
+        with pytest.warns(FutureWarning):
             self._test_binary_operator("__xor__", self.sq, self.g3, self.g4)
-        with pytest.warns(DeprecationWarning):
+        with pytest.warns(FutureWarning):
             self._test_binary_operator("__xor__", self.sq, self.gdf3, self.g4)
 
     def test_difference_series2(self):
         expected = GeoSeries([GeometryCollection(), self.t2])
-        with pytest.warns(DeprecationWarning):
+        with pytest.warns(FutureWarning):
             self._test_binary_operator("__sub__", expected, self.g1, self.g2)
-        with pytest.warns(DeprecationWarning):
+        with pytest.warns(FutureWarning):
             self._test_binary_operator("__sub__", expected, self.gdf1, self.g2)
 
     def test_difference_poly2(self):
         expected = GeoSeries([self.t1, self.t1])
-        with pytest.warns(DeprecationWarning):
+        with pytest.warns(FutureWarning):
             self._test_binary_operator("__sub__", expected, self.g1, self.t2)
-        with pytest.warns(DeprecationWarning):
+        with pytest.warns(FutureWarning):
             self._test_binary_operator("__sub__", expected, self.gdf1, self.t2)
diff --git a/geopandas/tests/test_op_output_types.py b/geopandas/tests/test_op_output_types.py
index dd25ff91..3a4155c7 100644
--- a/geopandas/tests/test_op_output_types.py
+++ b/geopandas/tests/test_op_output_types.py
@@ -4,6 +4,7 @@ import pytest
 import geopandas._compat as compat
 
 from shapely.geometry import Point
+import numpy as np
 
 from geopandas import GeoDataFrame, GeoSeries
 
@@ -36,6 +37,20 @@ def df(request):
     return df
 
 
+@pytest.fixture
+def df2():
+    """For constructor_sliced tests"""
+    return GeoDataFrame(
+        {
+            "geometry": GeoSeries([Point(x, x) for x in range(3)]),
+            "geometry2": GeoSeries([Point(x, x) for x in range(3)]),
+            "geometry3": GeoSeries([Point(x, x) for x in range(3)]),
+            "value": [1, 2, 1],
+            "value_nan": np.nan,
+        }
+    )
+
+
 def _check_metadata_gdf(gdf, geo_name="geometry", crs=crs_wgs):
     assert gdf._geometry_column_name == geo_name
     assert gdf.geometry.name == geo_name
@@ -212,7 +227,9 @@ def test_apply(df):
     assert_object(df[["value1"]].apply(identity, axis=1), pd.DataFrame)
 
 
+@pytest.mark.xfail(not compat.PANDAS_GE_11, reason="apply is different in pandas 1.0.5")
 def test_apply_axis1_secondary_geo_cols(df):
+    # note #GH2436 would also fix this
     def identity(x):
         return x
 
@@ -271,3 +288,60 @@ def test_expanddim_in_unstack():
         assert unstack._geometry_column_name is None
     else:  # pandas GH37369, unstack doesn't call finalize
         assert unstack._geometry_column_name == "geometry"
+
+
+# indexing /  constructor_sliced tests
+
+test_case_column_sets = [
+    ["geometry"],
+    ["geometry2"],
+    ["geometry", "geometry2"],
+    # non active geo col case
+    ["geometry", "value"],
+    ["geometry", "value_nan"],
+    ["geometry2", "value"],
+    ["geometry2", "value_nan"],
+]
+
+
+@pytest.mark.parametrize(
+    "column_set",
+    test_case_column_sets,
+    ids=[", ".join(i) for i in test_case_column_sets],
+)
+def test_constructor_sliced_row_slices(df2, column_set):
+    # https://github.com/geopandas/geopandas/issues/2282
+    df_subset = df2[column_set]
+    assert isinstance(df_subset, GeoDataFrame)
+    res = df_subset.loc[0]
+    # row slices shouldn't be GeoSeries, even if they have a geometry col
+    assert type(res) == pd.Series
+    if "geometry" in column_set:
+        assert not isinstance(res.geometry, pd.Series)
+        assert res.geometry == Point(0, 0)
+
+
+def test_constructor_sliced_column_slices(df2):
+    # Note loc doesn't use _constructor_sliced so it's not tested here
+    geo_idx = df2.columns.get_loc("geometry")
+    sub = df2.head(1)
+    # column slices should be GeoSeries if of geometry type
+    assert type(sub.iloc[:, geo_idx]) == GeoSeries
+    assert type(sub.iloc[[0], geo_idx]) == GeoSeries
+    sub = df2.head(2)
+    assert type(sub.iloc[:, geo_idx]) == GeoSeries
+    assert type(sub.iloc[[0, 1], geo_idx]) == GeoSeries
+
+    # check iloc row slices are pd.Series instead
+    assert type(df2.iloc[0, :]) == pd.Series
+
+
+def test_constructor_sliced_in_pandas_methods(df2):
+    # constructor sliced is used in many places, checking a sample of non
+    # geometry cases are sensible
+    assert type(df2.count()) == pd.Series
+    # drop the secondary geometry columns as not hashable
+    hashable_test_df = df2.drop(columns=["geometry2", "geometry3"])
+    assert type(hashable_test_df.duplicated()) == pd.Series
+    assert type(df2.quantile()) == pd.Series
+    assert type(df2.memory_usage()) == pd.Series
diff --git a/geopandas/tests/test_plotting.py b/geopandas/tests/test_plotting.py
index 368a0440..26291ced 100644
--- a/geopandas/tests/test_plotting.py
+++ b/geopandas/tests/test_plotting.py
@@ -1438,8 +1438,8 @@ class TestPlotCollections:
         with pytest.raises((TypeError, ValueError)):
             _plot_point_collection(ax, self.points, color="not color")
 
-        # check DeprecationWarning
-        with pytest.warns(DeprecationWarning):
+        # check FutureWarning
+        with pytest.warns(FutureWarning):
             plot_point_collection(ax, self.points)
 
     def test_points_values(self):
@@ -1511,8 +1511,8 @@ class TestPlotCollections:
         # not a color
         with pytest.raises((TypeError, ValueError)):
             _plot_linestring_collection(ax, self.lines, color="not color")
-        # check DeprecationWarning
-        with pytest.warns(DeprecationWarning):
+        # check FutureWarning
+        with pytest.warns(FutureWarning):
             plot_linestring_collection(ax, self.lines)
 
     def test_linestrings_values(self):
@@ -1603,8 +1603,8 @@ class TestPlotCollections:
         # not a color
         with pytest.raises((TypeError, ValueError)):
             _plot_polygon_collection(ax, self.polygons, color="not color")
-        # check DeprecationWarning
-        with pytest.warns(DeprecationWarning):
+        # check FutureWarning
+        with pytest.warns(FutureWarning):
             plot_polygon_collection(ax, self.polygons)
 
     def test_polygons_values(self):