Repository: geopandas/geopandas

Issue:
Value error using sjoin with pandas v0.23
I use the `sjoin` function to add the region name (polygons) to every point within the region. Some points are not in any region, therefore I filter these points and buffer them step by step. So the points layer without intersection becomes smaller and smaller. If there is only one row left I get the following error in pandas v0.23 which I did not get before (pandas < v0.23). Using geopandas v0.3.0.

My call:
```python
new = gpd.sjoin(rest_points, polygons, how='left', op='intersects')
```

Error message:
```python
ValueError: You are trying to merge on object and int64 columns.
If you wish to proceed you should use pd.concat
```
class: `GeoDataFrame`
method: `merge(self, *args, **kwargs)`
line: `result = DataFrame.merge(self, *args, **kwargs)`

I do not understand the error and why it happens only with the last point (last row) and only with the newest pandas version. I had a look at "What's New" but could not find anything.

Full message:
```python
  File "virtualenv/lib/python3.5/site-packages/geopandas/tools/sjoin.py", line 140,
    in sjoin suffixes=('_%s' % lsuffix, '_%s' % rsuffix))
  File "virtualenv/lib/python3.5/site-packages/geopandas/geodataframe.py", line 418,
     in merge result = DataFrame.merge(self, *args, **kwargs)
  File "virtualenv/lib/python3.5/site-packages/pandas/core/frame.py", line 6379,
     in merge copy=copy, indicator=indicator, validate=validate)
  File "virtualenv/lib/python3.5/site-packages/pandas/core/reshape/merge.py", line 60,
     in mergevalidate=validate)
  File "virtualenv/lib/python3.5/site-packages/pandas/core/reshape/merge.py", line 554,
     in __init__self._maybe_coerce_merge_keys()
  File "virtualenv/lib/python3.5/site-packages/pandas/core/reshape/merge.py", line 980,
        in _maybe_coerce_merge_keys
    raise ValueError(msg)
ValueError: You are trying to merge on object and int64 columns.
If you wish to proceed you should use pd.concat
```


Discussion:
@uvchik Thanks for the report!

This is related to a change in pandas to prevent merging on in compatible columns: https://github.com/pandas-dev/pandas/pull/18352 (but the detection of those cases seems a bit too eager). 

Would it be possible to make a small reproducible example for this case? (when you only have a single row). Does the row actually fall within any of the polygons?
OK, it seems that it gives this error if there is no matching row. Eg:

```python
In [67]: from shapely.geometry import Point, Polygon

In [68]: import geopandas

In [74]: polygons = geopandas.GeoDataFrame({'col2': [1, 2], 'geometry': [Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]), Polygon([(1, 0), (2, 0), (2, 1), (1, 1)])]})

In [75]: rest_points = geopandas.GeoDataFrame({'col1': [1], 'geometry': [Point(0.5, 0.5)]})

In [76]: geopandas.sjoin(rest_points, polygons, how='left', op='intersects')
Out[76]: 
   col1         geometry  index_right  col2
0     1  POINT (0.5 0.5)            0     1

In [77]: rest_points = geopandas.GeoDataFrame({'col1': [1], 'geometry': [Point(-0.5, 0.5)]})

In [78]: geopandas.sjoin(rest_points, polygons, how='left', op='intersects')
...
ValueError: You are trying to merge on object and int64 columns. If you wish to proceed you should use pd.concat
```

The underlying reason is that the "key column" that gets created under the hood is of object dtype if it is empty. So we would need to ensure it is of float dtype.
In case someone else encounters this error: I got the same error when my longitude/latitude coordinates where in the wrong order. 
```python
point_df = geopandas.GeoDataFrame({'geometry': [Point(69.905930,17.169982)]})
poly_df = geopandas.GeoDataFrame({'geometry': shapes})
pointInPolys = sjoin(point_df, poly_df, how='right', op='intersects')
```
only worked after changing the order of the Point coordinates to `Point(17.169982,69.905930)`. Here, `shapes` is a list of `Polygon` objects. A correct output for the point `Point(69.905930,17.169982)` should have been "out of range" or something similar, since it ends up far from all the Polygons in `shapes`

Resolving patch:
diff --git a/geopandas/tools/sjoin.py b/geopandas/tools/sjoin.py
--- a/geopandas/tools/sjoin.py
+++ b/geopandas/tools/sjoin.py
@@ -114,7 +114,7 @@ def find_contains(a1, a2):
 
     else:
         # when output from the join has no overlapping geometries
-        result = pd.DataFrame(columns=['_key_left', '_key_right'])
+        result = pd.DataFrame(columns=['_key_left', '_key_right'], dtype=float)
 
     if op == "within":
         # within implemented as the inverse of contains; swap names


Test patch:
diff --git a/geopandas/tools/tests/test_sjoin.py b/geopandas/tools/tests/test_sjoin.py
--- a/geopandas/tools/tests/test_sjoin.py
+++ b/geopandas/tools/tests/test_sjoin.py
@@ -117,6 +117,23 @@ def test_left(self, op, dfs):
 
         assert_frame_equal(res, exp)
 
+    def test_empty_join(self):
+        # Check empty joins
+        polygons = geopandas.GeoDataFrame({'col2': [1, 2], 
+                                           'geometry':  [Polygon([(0, 0), (1, 0), 
+                                                                  (1, 1), (0, 1)]), 
+                                                         Polygon([(1, 0), (2, 0), 
+                                                                  (2, 1), (1, 1)])
+                                                        ]})
+        not_in = geopandas.GeoDataFrame({'col1': [1], 
+                             'geometry': [Point(-0.5, 0.5)]})
+        empty = sjoin(not_in, polygons, how='left', op='intersects')
+        assert empty.index_right.isnull().all()
+        empty = sjoin(not_in, polygons, how='right', op='intersects')
+        assert empty.index_left.isnull().all()
+        empty = sjoin(not_in, polygons, how='inner', op='intersects')
+        assert empty.empty
+
     @pytest.mark.parametrize('dfs', ['default-index', 'string-index'],
                              indirect=True)
     @pytest.mark.parametrize('op', ['intersects', 'contains', 'within'])