Repository: pandas-dev/pandas

Issue:
BUG: Merge_asof error message unclear when attempting to join on missing values
The merge_asof function gives confusing error message when trying to join on columns which contain null values based on it failing the is_monotonic check

#### See example:

```python

left = pd.DataFrame({'a': [10.0, np.nan, 3.0, 12.0, 15.0],
                     'left_val': ['a', 'b', 'c', 'd', 'e']})
right = pd.DataFrame({'a': [1.0, 5.0, 10.0, 12.0],
                      'right_val': [1, 6, 11, 15]})

# As a responsible pandas user I then sort my left DataFrame
left_sorted = left.sort_values(by="a")


# and try using merge_asof
merge_asof(left_sorted, right, on='a')
```
```
ValueError: left keys must be sorted
```

This can lead to scratching of heads, because hey, the DataFrame was just sorted, and it kind of looks sorted, (also doesn't appear in docs that this isn't allowed).

The proposed change will raise a more meaningful error to lead the user to the cause of the problem.

```
ValueError: Merge keys contain null values on left side
```

Working on a pull request with @tolaa06 which should resolve this issue


Discussion:

Resolving patch:
diff --git a/pandas/core/reshape/merge.py b/pandas/core/reshape/merge.py
--- a/pandas/core/reshape/merge.py
+++ b/pandas/core/reshape/merge.py
@@ -36,7 +36,7 @@
     ensure_float64,
     ensure_object,
     _get_dtype)
-from pandas.core.dtypes.missing import na_value_for_dtype
+from pandas.core.dtypes.missing import na_value_for_dtype, isnull
 from pandas.core.internals import (items_overlap_with_suffix,
                                    concatenate_block_managers)
 from pandas.util._decorators import Appender, Substitution
@@ -1390,12 +1390,21 @@ def flip(xs):
                         self.right_join_keys[-1])
         tolerance = self.tolerance
 
-        # we required sortedness in the join keys
-        msg = "{side} keys must be sorted"
+        # we required sortedness and non-missingness in the join keys
+        msg_sorted = "{side} keys must be sorted"
+        msg_missings = "Merge keys contain null values on {side} side"
+
         if not Index(left_values).is_monotonic:
-            raise ValueError(msg.format(side='left'))
+            if isnull(left_values).sum() > 0:
+                raise ValueError(msg_missings.format(side='left'))
+            else:
+                raise ValueError(msg_sorted.format(side='left'))
+
         if not Index(right_values).is_monotonic:
-            raise ValueError(msg.format(side='right'))
+            if isnull(right_values).sum() > 0:
+                raise ValueError(msg_missings.format(side='right'))
+            else:
+                raise ValueError(msg_sorted.format(side='right'))
 
         # initial type conversion as needed
         if needs_i8_conversion(left_values):


Test patch:
diff --git a/pandas/tests/reshape/merge/test_merge_asof.py b/pandas/tests/reshape/merge/test_merge_asof.py
--- a/pandas/tests/reshape/merge/test_merge_asof.py
+++ b/pandas/tests/reshape/merge/test_merge_asof.py
@@ -1007,3 +1007,25 @@ def test_merge_datatype_error(self):
 
         with tm.assert_raises_regex(MergeError, msg):
             merge_asof(left, right, on='a')
+
+    def test_merge_on_nans_int(self):
+        # 23189
+        msg = "Merge keys contain null values on left side"
+        left = pd.DataFrame({'a': [1.0, 5.0, 10.0, 12.0, np.nan],
+                             'left_val': ['a', 'b', 'c', 'd', 'e']})
+        right = pd.DataFrame({'a': [1.0, 5.0, 10.0, 12.0],
+                              'right_val': [1, 6, 11, 15]})
+
+        with tm.assert_raises_regex(ValueError, msg):
+            merge_asof(left, right, on='a')
+
+    def test_merge_on_nans_datetime(self):
+        # 23189
+        msg = "Merge keys contain null values on right side"
+        left = pd.DataFrame({"a": pd.date_range('20130101', periods=5)})
+        date_vals = pd.date_range('20130102', periods=5)\
+            .append(pd.Index([None]))
+        right = pd.DataFrame({"a": date_vals})
+
+        with tm.assert_raises_regex(ValueError, msg):
+            merge_asof(left, right, on='a')