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/rule_based_profiler/helpers/util.py b/great_expectations/rule_based_profiler/helpers/util.py
--- a/great_expectations/rule_based_profiler/helpers/util.py
+++ b/great_expectations/rule_based_profiler/helpers/util.py
@@ -462,7 +462,7 @@ def integer_semantic_domain_type(domain: Domain) -> bool:
                 SemanticDomainTypes.LOGIC,
                 SemanticDomainTypes.IDENTIFIER,
             ]
-            for semantic_domain_type in (inferred_semantic_domain_type.values())
+            for semantic_domain_type in inferred_semantic_domain_type.values()
         ]
     )
 
diff --git a/great_expectations/rule_based_profiler/parameter_builder/numeric_metric_range_multi_batch_parameter_builder.py b/great_expectations/rule_based_profiler/parameter_builder/numeric_metric_range_multi_batch_parameter_builder.py
--- a/great_expectations/rule_based_profiler/parameter_builder/numeric_metric_range_multi_batch_parameter_builder.py
+++ b/great_expectations/rule_based_profiler/parameter_builder/numeric_metric_range_multi_batch_parameter_builder.py
@@ -54,9 +54,10 @@ class NumericMetricRangeMultiBatchParameterBuilder(MetricMultiBatchParameterBuil
     """
 
     RECOGNIZED_SAMPLING_METHOD_NAMES: set = {
-        "oneshot",
         "bootstrap",
+        "exact",
         "kde",
+        "oneshot",
     }
 
     RECOGNIZED_N_RESAMPLES_SAMPLING_METHOD_NAMES: set = {
@@ -92,7 +93,7 @@ def __init__(
         enforce_numeric_metric: Union[str, bool] = True,
         replace_nan_with_zero: Union[str, bool] = True,
         reduce_scalar_metric: Union[str, bool] = True,
-        false_positive_rate: Union[str, float] = 5.0e-2,
+        false_positive_rate: Optional[Union[str, float]] = None,
         estimator: str = "bootstrap",
         n_resamples: Optional[Union[str, int]] = None,
         random_seed: Optional[Union[str, int]] = None,
@@ -124,8 +125,8 @@ def __init__(
             reduce_scalar_metric: if True (default), then reduces computation of 1-dimensional metric to scalar value.
             false_positive_rate: user-configured fraction between 0 and 1 expressing desired false positive rate for
                 identifying unexpected values as judged by the upper- and lower- quantiles of the observed metric data.
-            estimator: choice of the estimation algorithm: "oneshot" (one observation), "bootstrap" (default),
-                or "kde" (kernel density estimation).
+            estimator: choice of the estimation algorithm: "oneshot" (one observation), "bootstrap" (default), "exact"
+                (deterministic, incorporating entire observed value range), or "kde" (kernel density estimation).
             n_resamples: Applicable only for the "bootstrap" and "kde" sampling methods -- if omitted (default), then
                 9999 is used (default in
                 "https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.bootstrap.html").
@@ -138,8 +139,8 @@ def __init__(
             bw_method: Applicable only for the "kde" sampling method -- if omitted (default), then "scott" is used.
                 Possible values for the estimator bandwidth method are described at:
                 https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.gaussian_kde.html
-            include_estimator_samples_histogram_in_details: Applicable only for the "bootstrap" sampling method -- if
-                True, then add 10-bin histogram of bootstraps to "details"; otherwise, omit this information (default).
+            include_estimator_samples_histogram_in_details: Applicable only for the "bootstrap" and "kde" sampling
+                methods -- if True, then add 10-bin histogram of metric samples to "details"; otherwise, omit (default).
             truncate_values: user-configured directive for whether or not to allow the computed parameter values
                 (i.e., lower_bound, upper_bound) to take on values outside the specified bounds when packaged on output.
             round_decimals: user-configured non-negative integer indicating the number of decimals of the
@@ -166,6 +167,9 @@ def __init__(
             metric_multi_batch_parameter_builder_name
         )
 
+        if false_positive_rate is None:
+            false_positive_rate = 5.0e-2
+
         self._false_positive_rate = false_positive_rate
 
         self._estimator = estimator
@@ -407,7 +411,10 @@ def _build_parameters(
 
         estimator_func: Callable
         estimator_kwargs: dict
-        if estimator == "bootstrap":
+        if estimator == "exact":
+            estimator_func = self._get_exact_estimate
+            estimator_kwargs = {}
+        elif estimator == "bootstrap":
             estimator_func = self._get_bootstrap_estimate
             estimator_kwargs = {
                 "false_positive_rate": false_positive_rate,
@@ -426,7 +433,8 @@ def _build_parameters(
                 "bw_method": self.bw_method,
             }
         else:
-            estimator_func = self._get_deterministic_estimate
+            # Apply "oneshot" estimator (should be used for testing purposes only).
+            estimator_func = self._get_oneshot_estimate
             estimator_kwargs = {
                 "false_positive_rate": false_positive_rate,
                 "quantile_statistic_interpolation_method": quantile_statistic_interpolation_method,
@@ -805,7 +813,7 @@ def _get_kde_estimate(
         )
 
     @staticmethod
-    def _get_deterministic_estimate(
+    def _get_oneshot_estimate(
         metric_values: np.ndarray,
         **kwargs,
     ) -> NumericRangeEstimationResult:
@@ -819,3 +827,17 @@ def _get_deterministic_estimate(
             false_positive_rate=false_positive_rate,
             quantile_statistic_interpolation_method=quantile_statistic_interpolation_method,
         )
+
+    @staticmethod
+    def _get_exact_estimate(
+        metric_values: np.ndarray,
+        **kwargs,
+    ) -> NumericRangeEstimationResult:
+        return NumericRangeEstimationResult(
+            estimation_histogram=np.histogram(a=metric_values, bins=NUM_HISTOGRAM_BINS)[
+                0
+            ],
+            value_range=np.asarray(
+                [np.amin(a=metric_values), np.amax(a=metric_values)]
+            ),
+        )
diff --git a/great_expectations/rule_based_profiler/parameter_builder/partition_parameter_builder.py b/great_expectations/rule_based_profiler/parameter_builder/partition_parameter_builder.py
--- a/great_expectations/rule_based_profiler/parameter_builder/partition_parameter_builder.py
+++ b/great_expectations/rule_based_profiler/parameter_builder/partition_parameter_builder.py
@@ -24,7 +24,7 @@
 
 class PartitionParameterBuilder(MetricSingleBatchParameterBuilder):
     """
-    Compute histogram/partition using specified metric (depending on bucketizaiton directive) for one Batch od data.
+    Compute histogram/partition using specified metric (depending on bucketizaiton directive) for one Batch of data.
     """
 
     exclude_field_names: Set[


Test patch:
diff --git a/tests/rule_based_profiler/parameter_builder/test_numeric_metric_range_multi_batch_parameter_builder.py b/tests/rule_based_profiler/parameter_builder/test_numeric_metric_range_multi_batch_parameter_builder.py
--- a/tests/rule_based_profiler/parameter_builder/test_numeric_metric_range_multi_batch_parameter_builder.py
+++ b/tests/rule_based_profiler/parameter_builder/test_numeric_metric_range_multi_batch_parameter_builder.py
@@ -343,6 +343,126 @@ def test_oneshot_numeric_metric_range_multi_batch_parameter_builder_bobby(
     assert p_value > 9.5e-1
 
 
+def test_exact_numeric_metric_range_multi_batch_parameter_builder_bobby(
+    bobby_columnar_table_multi_batch_deterministic_data_context,
+):
+    data_context: DataContext = (
+        bobby_columnar_table_multi_batch_deterministic_data_context
+    )
+
+    batch_request: dict = {
+        "datasource_name": "taxi_pandas",
+        "data_connector_name": "monthly",
+        "data_asset_name": "my_reports",
+    }
+
+    metric_domain_kwargs: dict = {"column": "fare_amount"}
+
+    fully_qualified_parameter_name_for_value: str = "$parameter.column_min_range"
+
+    numeric_metric_range_parameter_builder: ParameterBuilder = (
+        NumericMetricRangeMultiBatchParameterBuilder(
+            name="column_min_range",
+            metric_name="column.min",
+            metric_multi_batch_parameter_builder_name=None,
+            metric_domain_kwargs=DOMAIN_KWARGS_PARAMETER_FULLY_QUALIFIED_NAME,
+            metric_value_kwargs=None,
+            estimator="exact",
+            include_estimator_samples_histogram_in_details=True,
+            false_positive_rate=None,
+            round_decimals=1,
+            evaluation_parameter_builder_configs=None,
+            data_context=data_context,
+        )
+    )
+
+    variables: Optional[ParameterContainer] = None
+
+    domain: Domain = Domain(
+        domain_type=MetricDomainTypes.COLUMN,
+        domain_kwargs=metric_domain_kwargs,
+        rule_name="my_rule",
+    )
+    parameter_container: ParameterContainer = ParameterContainer(parameter_nodes=None)
+    parameters: Dict[str, ParameterContainer] = {
+        domain.id: parameter_container,
+    }
+
+    assert parameter_container.parameter_nodes is None
+
+    numeric_metric_range_parameter_builder.build_parameters(
+        domain=domain,
+        variables=variables,
+        parameters=parameters,
+        batch_request=batch_request,
+    )
+
+    parameter_nodes: Optional[Dict[str, ParameterNode]] = (
+        parameter_container.parameter_nodes or {}
+    )
+    assert len(parameter_nodes) == 1
+
+    expected_value_dict: Dict[str, Optional[str]] = {
+        "value": None,
+        "details": {
+            "metric_configuration": {
+                "domain_kwargs": {"column": "fare_amount"},
+                "metric_name": "column.min",
+                "metric_value_kwargs": None,
+                "metric_dependencies": None,
+            },
+            "num_batches": 3,
+        },
+    }
+
+    parameter_node: ParameterNode = (
+        get_parameter_value_by_fully_qualified_parameter_name(
+            fully_qualified_parameter_name=fully_qualified_parameter_name_for_value,
+            domain=domain,
+            parameters=parameters,
+        )
+    )
+
+    actual_values_01: np.ndarray = parameter_node.pop("value")
+    parameter_node["value"] = None
+
+    actual_estimation_histogram: np.ndarray = parameter_node.details.pop(
+        "estimation_histogram"
+    )
+
+    assert parameter_node == expected_value_dict
+
+    actual_value_01_lower: float = actual_values_01[0]
+    actual_value_01_upper: float = actual_values_01[1]
+    expected_value_01_lower: float = -52.0
+    expected_value_01_upper: float = -21.0
+
+    assert actual_value_01_lower == expected_value_01_lower
+    assert actual_value_01_upper == expected_value_01_upper
+
+    expected_estimation_histogram: np.ndarray = np.array(
+        [
+            1.0,
+            0.0,
+            0.0,
+            0.0,
+            0.0,
+            0.0,
+            0.0,
+            0.0,
+            0.0,
+            2.0,
+        ]
+    )
+
+    # Assert no significant difference between expected (null hypothesis) and actual estimation histograms.
+    ks_result: tuple = stats.ks_2samp(
+        data1=actual_estimation_histogram, data2=expected_estimation_histogram
+    )
+    p_value: float = ks_result[1]
+    assert p_value > 9.5e-1
+
+
 def test_oneshot_numeric_metric_range_multi_batch_parameter_builder_with_evaluation_dependency_bobby(
     bobby_columnar_table_multi_batch_deterministic_data_context,
 ):