Repository: great-expectations/great_expectations

Issue:
Use cleaner solution for non-truncating division in python 2
Prefer `from __future__ import division` to `1.*x/y`


Discussion:
Done inline during improve_partitions push.

Resolving patch:
diff --git a/great_expectations/data_asset/data_asset.py b/great_expectations/data_asset/data_asset.py
--- a/great_expectations/data_asset/data_asset.py
+++ b/great_expectations/data_asset/data_asset.py
@@ -10,7 +10,7 @@
 from collections import Counter, defaultdict, namedtuple
 from collections.abc import Hashable
 from functools import wraps
-from typing import Dict, List, Optional, Union
+from typing import Any, Dict, List, Optional, Union
 
 from dateutil.parser import parse
 from marshmallow import ValidationError
@@ -1111,7 +1111,7 @@ def expectation_suite_name(self, expectation_suite_name) -> None:
     #
     ###
 
-    def _format_map_output(
+    def _format_map_output(  # noqa: C901 - complexity 21
         self,
         result_format,
         success,
@@ -1173,16 +1173,32 @@ def _format_map_output(
         if result_format["result_format"] == "BASIC":
             return return_obj
 
+        if unexpected_list is not None:
+            if len(unexpected_list) and isinstance(unexpected_list[0], dict):
+                # in the case of multicolumn map expectations `unexpected_list` contains dicts,
+                # which will throw an exception when we hash it to count unique members.
+                # As a workaround, we flatten the values out to tuples.
+                immutable_unexpected_list = [
+                    tuple([val for val in item.values()]) for item in unexpected_list
+                ]
+            else:
+                immutable_unexpected_list = unexpected_list
+
         # Try to return the most common values, if possible.
-        if 0 < result_format.get("partial_unexpected_count"):
+        partial_unexpected_count: Optional[int] = result_format.get(
+            "partial_unexpected_count"
+        )
+        partial_unexpected_counts: Optional[List[Dict[str, Any]]] = None
+
+        if partial_unexpected_count is not None and 0 < partial_unexpected_count:
             try:
                 partial_unexpected_counts = [
                     {"value": key, "count": value}
                     for key, value in sorted(
-                        Counter(unexpected_list).most_common(
+                        Counter(immutable_unexpected_list).most_common(
                             result_format["partial_unexpected_count"]
                         ),
-                        key=lambda x: (-x[1], str(x[0])),
+                        key=lambda x: (-x[1], x[0]),
                     )
                 ]
             except TypeError:


Test patch:
diff --git a/tests/dataset/test_pandas_dataset.py b/tests/dataset/test_pandas_dataset.py
--- a/tests/dataset/test_pandas_dataset.py
+++ b/tests/dataset/test_pandas_dataset.py
@@ -379,17 +379,18 @@ def test_result_format_argument_in_decorators():
                 "element_count": 5,
                 "missing_count": 0,
                 "missing_percent": 0.0,
+                "unexpected_count": 2,
+                "unexpected_percent": 40.0,
+                "unexpected_percent_total": 40.0,
+                "unexpected_percent_nonmissing": 40.0,
+                "partial_unexpected_list": [8, 10],
+                "partial_unexpected_index_list": [3, 4],
                 "partial_unexpected_counts": [
-                    {"count": 1, "value": 10},
                     {"count": 1, "value": 8},
+                    {"count": 1, "value": 10},
                 ],
-                "partial_unexpected_index_list": [3, 4],
-                "partial_unexpected_list": [8, 10],
-                "unexpected_count": 2,
-                "unexpected_index_list": [3, 4],
                 "unexpected_list": [8, 10],
-                "unexpected_percent": 40.0,
-                "unexpected_percent_nonmissing": 40.0,
+                "unexpected_index_list": [3, 4],
             },
             "success": False,
         }
diff --git a/tests/dataset/test_pandas_dataset_conditionals.py b/tests/dataset/test_pandas_dataset_conditionals.py
--- a/tests/dataset/test_pandas_dataset_conditionals.py
+++ b/tests/dataset/test_pandas_dataset_conditionals.py
@@ -277,17 +277,18 @@ def test_result_format_argument_in_decorators():
                 "element_count": 5,
                 "missing_count": 0,
                 "missing_percent": 0.0,
+                "unexpected_count": 2,
+                "unexpected_percent": 40.0,
+                "unexpected_percent_total": 40.0,
+                "unexpected_percent_nonmissing": 40.0,
+                "partial_unexpected_list": [8, 10],
+                "partial_unexpected_index_list": [3, 4],
                 "partial_unexpected_counts": [
-                    {"count": 1, "value": 10},
                     {"count": 1, "value": 8},
+                    {"count": 1, "value": 10},
                 ],
-                "partial_unexpected_index_list": [3, 4],
-                "partial_unexpected_list": [8, 10],
-                "unexpected_count": 2,
-                "unexpected_index_list": [3, 4],
                 "unexpected_list": [8, 10],
-                "unexpected_percent": 40.0,
-                "unexpected_percent_nonmissing": 40.0,
+                "unexpected_index_list": [3, 4],
             },
             "success": False,
         }