Repository: great-expectations/great_expectations

Issue:
Support to include ID/PK in validation result for each row that failed an expectations
**Is your feature request related to a problem? Please describe.**
When an expectation is run the output includes a “partial_unexpected_list” property of values that were unexpected. While this is useful, in most cases, it doesn't allow teams to identify, resolve, or divert data with poor quality.

It would be great if a sample list or a complete list (result_format SUMMARY, COMPLETE) of ID's for each row that failed an expectation was included in the validation result.

**Describe the solution you'd like**
One could include a column name as an argument in the expectation that they would like to be included in the "partial_unexpected_list" (or a new property).

**Describe alternatives you've considered**
One could try infer the PK for a table but this is not possible for all engine types E.g. Spark. 

**Additional context**
Enabling teams to not only bring light to data quality issues but identify all rows allows them to address poor data quality in real-time instead of requiring manual intervention. 



Discussion:
This is a very important feature in my opinion, as Kenton mentioned, we will be able to not only identify the data quality and correctness on the data, but take action as well and help isolate erroneous rows rather than failing the entire dataset.
Thanks for submitting this issue, @KentonParton, and the follow-up, @OmarSultan85 ! We will discuss internally, and I will get back to you in the next few days.
Hi @KentonParton and @OmarSultan85 - apologies for delays! A part of the V3 API was making sure that this functionality was available, but @jcampbell and I worked on this draft PR (#3346) to link up all the pieces and get it working. When you have a chance, we'd love your thoughts on the interface of it, concerns, etc.

Thanks!
Hi @talagluck ,

Thanks a lot for the update, I checked the PR but I am not entirely sure if I understood correctly. What I understand is that now the list of unexpected rows will be returned completely as part of the validation result when its of type COMPLETE.

If that is the case then I believe this would be of great use and would allows us to use great expectations in a pipeline that could split the incoming data into two, process correct rows and insert them and for the unexpected ones perform some kind of action  to clean the data, notify business users, etc.

Anyway I can try this new feature before it is released? I can't wait to integrate it into our product and pipelines :) 
Thanks so much for the feedback, @OmarSultan85 ! That is indeed the purpose of the feature, and I'm glad to hear that that will work for you!

We are still figuring out the correct user-interface for this, as it is possible that it will wind up returning far too much data, and so we would want to be explicit and careful so that we don't wind up including an entire table in validation, results for instance :) This is something that @KentonParton had called out, and is important to keep in mind.

What we're thinking right now is that we will add another `result_format` option (e.g. something like `INCLUDE_ALL_UNEXPECTED_ROWS`) that would return the entire contents of this. This would make sure that only users who definitely want this and know what they are doing would use this.

We are also still looking into specifying a list of columns that would be returned for each `unexpected_rows`. This is more along the lines of the original request for this issue. In this case, for instance, you would be able to say that where `column_a` has unexpected rows, return the contents of `column_b` and `column_c`. This seems achievable, but it goes pretty deep into the code (since it would affect all column_map expectations), and so we're working on it, but it will take some more time.
any updates on this?
Hi @talagluck I noticed just now that in #3764 that the GE team was planning on taking a look at this back in January.

Is progress being made against this issue on the GE jira board or has this been de-prioritized? If it has been de-prioritized (as tends to happen in our world) is this the kind of feature that the GE team would be able to offer guidance on?

Our dependence on pandas for the unexpected_index_list is fast becoming a constraint. We need this feature to be able to move to validating our data on a DB in order to allow us to continue to use GE as the size of our data scales.
Hi @abekfenn - thanks so much for the ping, and thanks for calling this out. This is something that I've been picking up as I have time (and I don't have a lot of time). I don't think this is a huge amount of work to implement, [especially in light of the work done in this PR for `unexpected_rows` ](https://github.com/great-expectations/great_expectations/pull/3346), it's just a matter of resources. If this is something on which you have the capacity to work, I'd be happy to offer guidance and support for design and code review. Let me know and we can set up a call.
Hi @talagluck thanks for getting back to me. Totally understandable, my work day sounds very similar 😄. I would be grateful if we could get on a call to discuss the scope of the work. I'll reach out over Slack. 
### Update
I've started working on this but run into an issue that I hope to figure out soon.

I do have some questions around the API that I would like GE/community feedback on.

With the introduction of `unexpected_rows` I'm torn between calling this new result format arg, `unexpected_index_columns` or `unexpected_row_columns`. The idea being that a user should provide to this arg a list of columns in the table that should be sub-setted from the unexpected rows.
This feature request was initially to support ID/PKs in the validation results, however I could see a use case whereby someone using the PandasExecutionEngine could want both unexpected_index_list and a subset of columns from the unexpected rows.

1. I see GE generally making use of binary args, but the arg `row_condition` led me to thinking this would be the cleanest naming convention in line with existing result format args, not to mention user interaction.
- However, I wanted to be sure that the GE team/community wouldn't prefer something like `include_unexpected_index_columns` AND `unexpected_index_columns`?
2. Similarly, I'm wondering if the returned list should be a new field in the validation results or if we should simply set `unexpected_index_list` when `unexpected_index_columns` is set?
- This would keep behavior consistent across SQLAlchemy and Pandas execution engines but the only concern is that people may want to use the same functionality for pandas - returning arbitrary columns rather than the pandas index list - and so that might create some confusion
- Alternatively, we could set `unexpected_index_list` only for SQLAlchemy but I think this only confuses matters, and it probably wouldn't do much to help keep behavior consistent across engines.

I'm personally leaning towards one arg for `unexpected_row_columns` and one new result field `unexpected_row_columns` or something alike but given that this deviates slightly from the original intention of this ticket, I wanted to open it for discussion.
Hello @abekfenn  

Sorry I just saw this post must have missed the notification. With regards to the naming convention, I believe what you are leaning towards sounds reasonable and can be easily understood. 

I like the idea of having a subset of the columns along with the PK, could be helpful in some usecases.

As for including it in the same list or returning a new validation result, I believe a validation result could be a better choice as most probably it would reduce the size of the dataset that the user is going to be working on and could make things easier to navigate through the unexpected IDs.



Hey @abekfenn! Thanks for reaching out, great discussion here. I align with your thoughts here; one arg, one result field, with the aim of keeping it clean, simple, and consistent.
Hi @abekfenn, I was wondering how the implementation was coming along. Similar to you the Pandas unexpected index list is not a viable option for my company (large Dutch insurer) because of the cost in speed. I (and my team) had similar thoughts for the solution and would love to help with implementing this feature.
Hi all, I created a very work in progress PR here https://github.com/great-expectations/great_expectations/pull/5876 at the request of @talagluck (thanks for your guidance thus far). I haven't been able to spend as much time on this as I would have hoped so apologies for the delay.

Currently I've setup a new arg simply to see if I can get this new expectation registered and recognized, and would then implement the actual subsetting functionality thereafter. Right now, the expectation doesn't seem to get registered properly, so any help here would be greatly appreciated.

Resolving patch:
diff --git a/great_expectations/expectations/metrics/map_metric_provider.py b/great_expectations/expectations/metrics/map_metric_provider.py
--- a/great_expectations/expectations/metrics/map_metric_provider.py
+++ b/great_expectations/expectations/metrics/map_metric_provider.py
@@ -1688,7 +1688,7 @@ def _pandas_map_condition_index(
     metric_value_kwargs: Dict,
     metrics: Dict[str, Any],
     **kwargs,
-):
+) -> Union[List[int], List[Dict[str, Any]]]:
     (
         boolean_mapped_unexpected_values,
         compute_domain_kwargs,
@@ -1733,10 +1733,31 @@ def _pandas_map_condition_index(
 
     df = df[boolean_mapped_unexpected_values]
 
-    if result_format["result_format"] == "COMPLETE":
-        return list(df.index)
+    if "unexpected_index_column_names" in result_format:
+
+        unexpected_index_list: Optional[List[Dict[str, Any]]] = []
+        unexpected_index_column_names: List[str] = result_format[
+            "unexpected_index_column_names"
+        ]
+        unexpected_indices: List[int] = list(df.index)
+        for index in unexpected_indices:
+            primary_key_dict: Dict[str, Any] = {}
+            for column_name in unexpected_index_column_names:
+                if column_name not in metrics["table.columns"]:
+                    raise ge_exceptions.InvalidMetricAccessorDomainKwargsKeyError(
+                        message=f'Error: The unexpected_index_column: "{column_name}" in does not exist in Dataframe. '
+                        f"Please check your configuration and try again."
+                    )
+                primary_key_dict[column_name] = df.at[index, column_name]
+            unexpected_index_list.append(primary_key_dict)
+        if result_format["result_format"] == "COMPLETE":
+            return unexpected_index_list
+        return unexpected_index_list[: result_format["partial_unexpected_count"]]
 
-    return list(df.index[: result_format["partial_unexpected_count"]])
+    else:
+        if result_format["result_format"] == "COMPLETE":
+            return list(df.index)
+        return list(df.index[: result_format["partial_unexpected_count"]])
 
 
 def _pandas_column_map_condition_value_counts(


Test patch:
diff --git a/tests/expectations/metrics/test_map_metric.py b/tests/expectations/metrics/test_map_metric.py
--- a/tests/expectations/metrics/test_map_metric.py
+++ b/tests/expectations/metrics/test_map_metric.py
@@ -3,10 +3,9 @@
 
 from great_expectations.core import (
     ExpectationConfiguration,
-    ExpectationSuite,
     ExpectationValidationResult,
 )
-from great_expectations.core.batch import Batch, RuntimeBatchRequest
+from great_expectations.core.batch import Batch
 from great_expectations.core.util import convert_to_json_serializable
 from great_expectations.execution_engine import (
     PandasExecutionEngine,
@@ -36,6 +35,25 @@ def dataframe_for_unexpected_rows():
     )
 
 
+@pytest.fixture
+def pandas_dataframe_for_unexpected_rows_with_index():
+    return pd.DataFrame(
+        {
+            "pk_1": [0, 1, 2, 3, 4, 5],
+            "pk_2": ["zero", "one", "two", "three", "four", "five"],
+            "numbers_with_duplicates": [1, 5, 22, 3, 5, 10],
+            "animal_names_no_duplicates": [
+                "cat",
+                "fish",
+                "dog",
+                "giraffe",
+                "lion",
+                "zebra",
+            ],
+        }
+    )
+
+
 @pytest.fixture()
 def expected_evr_without_unexpected_rows():
     return ExpectationValidationResult(
@@ -334,6 +352,403 @@ def test_pandas_unexpected_rows_complete_result_format(dataframe_for_unexpected_
     }
 
 
+def test_pandas_default_complete_result_format(
+    pandas_dataframe_for_unexpected_rows_with_index: pd.DataFrame,
+):
+    expectationConfiguration = ExpectationConfiguration(
+        expectation_type="expect_column_values_to_be_in_set",
+        kwargs={
+            "column": "numbers_with_duplicates",
+            "value_set": [1, 5, 22],
+            "result_format": {
+                "result_format": "COMPLETE",
+            },
+        },
+    )
+
+    expectation = ExpectColumnValuesToBeInSet(expectationConfiguration)
+    batch: Batch = Batch(data=pandas_dataframe_for_unexpected_rows_with_index)
+    engine = PandasExecutionEngine()
+    validator = Validator(
+        execution_engine=engine,
+        batches=[
+            batch,
+        ],
+    )
+    result = expectation.validate(validator)
+    assert convert_to_json_serializable(result.result) == {
+        "element_count": 6,
+        "unexpected_count": 2,
+        "unexpected_index_list": [
+            3,
+            5,
+        ],  # Default pandas index applied, compatible with existing functionality (list of ints)
+        "partial_unexpected_index_list": [3, 5],
+        "unexpected_percent": 33.33333333333333,
+        "partial_unexpected_list": [3, 10],
+        "unexpected_list": [3, 10],
+        "partial_unexpected_counts": [
+            {"value": 3, "count": 1},
+            {"value": 10, "count": 1},
+        ],
+        "missing_count": 0,
+        "missing_percent": 0.0,
+        "unexpected_percent_total": 33.33333333333333,
+        "unexpected_percent_nonmissing": 33.33333333333333,
+    }
+
+
+def test_pandas_single_unexpected_index_column_names_complete_result_format(
+    pandas_dataframe_for_unexpected_rows_with_index: pd.DataFrame,
+):
+    expectationConfiguration = ExpectationConfiguration(
+        expectation_type="expect_column_values_to_be_in_set",
+        kwargs={
+            "column": "numbers_with_duplicates",
+            "value_set": [1, 5, 22],
+            "result_format": {
+                "result_format": "COMPLETE",
+                "unexpected_index_column_names": ["pk_1"],  # Single column
+            },
+        },
+    )
+
+    expectation = ExpectColumnValuesToBeInSet(expectationConfiguration)
+    batch: Batch = Batch(data=pandas_dataframe_for_unexpected_rows_with_index)
+    engine = PandasExecutionEngine()
+    validator = Validator(
+        execution_engine=engine,
+        batches=[
+            batch,
+        ],
+    )
+    result = expectation.validate(validator)
+    assert convert_to_json_serializable(result.result) == {
+        "element_count": 6,
+        "unexpected_count": 2,
+        "unexpected_index_list": [
+            {
+                "pk_1": 3,
+            },
+            {
+                "pk_1": 5,
+            },
+        ],  # Dicts since a column was provided
+        "partial_unexpected_index_list": [
+            {
+                "pk_1": 3,
+            },
+            {
+                "pk_1": 5,
+            },
+        ],  # Dicts since a column was provided
+        "unexpected_percent": 33.33333333333333,
+        "partial_unexpected_list": [3, 10],
+        "unexpected_list": [3, 10],
+        "partial_unexpected_counts": [
+            {"value": 3, "count": 1},
+            {"value": 10, "count": 1},
+        ],
+        "missing_count": 0,
+        "missing_percent": 0.0,
+        "unexpected_percent_total": 33.33333333333333,
+        "unexpected_percent_nonmissing": 33.33333333333333,
+    }
+
+
+def test_pandas_multiple_unexpected_index_column_names_complete_result_format(
+    pandas_dataframe_for_unexpected_rows_with_index: pd.DataFrame,
+):
+    expectationConfiguration = ExpectationConfiguration(
+        expectation_type="expect_column_values_to_be_in_set",
+        kwargs={
+            "column": "numbers_with_duplicates",
+            "value_set": [1, 5, 22],
+            "result_format": {
+                "result_format": "COMPLETE",
+                "unexpected_index_column_names": ["pk_1", "pk_2"],  # Multiple columns
+            },
+        },
+    )
+
+    expectation = ExpectColumnValuesToBeInSet(expectationConfiguration)
+    batch: Batch = Batch(data=pandas_dataframe_for_unexpected_rows_with_index)
+    engine = PandasExecutionEngine()
+    validator = Validator(
+        execution_engine=engine,
+        batches=[
+            batch,
+        ],
+    )
+    result = expectation.validate(validator)
+    assert convert_to_json_serializable(result.result) == {
+        "element_count": 6,
+        "unexpected_count": 2,
+        "unexpected_index_list": [
+            {"pk_1": 3, "pk_2": "three"},
+            {"pk_1": 5, "pk_2": "five"},
+        ],  # Dicts since columns were provided
+        "partial_unexpected_index_list": [
+            {"pk_1": 3, "pk_2": "three"},
+            {"pk_1": 5, "pk_2": "five"},
+        ],  # Dicts since columns were provided
+        "unexpected_percent": 33.33333333333333,
+        "partial_unexpected_list": [3, 10],
+        "unexpected_list": [3, 10],
+        "partial_unexpected_counts": [
+            {"value": 3, "count": 1},
+            {"value": 10, "count": 1},
+        ],
+        "missing_count": 0,
+        "missing_percent": 0.0,
+        "unexpected_percent_total": 33.33333333333333,
+        "unexpected_percent_nonmissing": 33.33333333333333,
+    }
+
+
+def test_pandas_multiple_unexpected_index_column_names_complete_result_format_limit_1(
+    pandas_dataframe_for_unexpected_rows_with_index: pd.DataFrame,
+):
+    expectationConfiguration = ExpectationConfiguration(
+        expectation_type="expect_column_values_to_be_in_set",
+        kwargs={
+            "column": "numbers_with_duplicates",
+            "value_set": [1, 5, 22],
+            "result_format": {
+                "result_format": "COMPLETE",
+                "unexpected_index_column_names": ["pk_1", "pk_2"],  # Multiple columns
+                "partial_unexpected_count": 1,
+            },
+        },
+    )
+
+    expectation = ExpectColumnValuesToBeInSet(expectationConfiguration)
+    batch: Batch = Batch(data=pandas_dataframe_for_unexpected_rows_with_index)
+    engine = PandasExecutionEngine()
+    validator = Validator(
+        execution_engine=engine,
+        batches=[
+            batch,
+        ],
+    )
+    result = expectation.validate(validator)
+    assert convert_to_json_serializable(result.result) == {
+        "element_count": 6,
+        "unexpected_count": 2,
+        "unexpected_index_list": [
+            {"pk_1": 3, "pk_2": "three"},
+            {"pk_1": 5, "pk_2": "five"},
+        ],  # Dicts since columns were provided
+        "partial_unexpected_index_list": [
+            {"pk_1": 3, "pk_2": "three"},
+        ],  # Dicts since columns were provided
+        "unexpected_percent": 33.33333333333333,
+        "partial_unexpected_list": [3],
+        "unexpected_list": [3, 10],
+        "partial_unexpected_counts": [
+            {"value": 3, "count": 1},
+        ],
+        "missing_count": 0,
+        "missing_percent": 0.0,
+        "unexpected_percent_total": 33.33333333333333,
+        "unexpected_percent_nonmissing": 33.33333333333333,
+    }
+
+
+def test_pandas_multiple_unexpected_index_column_names_summary_result_format(
+    pandas_dataframe_for_unexpected_rows_with_index: pd.DataFrame,
+):
+    expectationConfiguration = ExpectationConfiguration(
+        expectation_type="expect_column_values_to_be_in_set",
+        kwargs={
+            "column": "numbers_with_duplicates",
+            "value_set": [1, 5, 22],
+            "result_format": {
+                "result_format": "SUMMARY",  # SUMMARY will include partial_unexpected* values only
+                "unexpected_index_column_names": ["pk_1", "pk_2"],  # Multiple columns
+            },
+        },
+    )
+
+    expectation = ExpectColumnValuesToBeInSet(expectationConfiguration)
+    batch: Batch = Batch(data=pandas_dataframe_for_unexpected_rows_with_index)
+    engine = PandasExecutionEngine()
+    validator = Validator(
+        execution_engine=engine,
+        batches=[
+            batch,
+        ],
+    )
+    result = expectation.validate(validator)
+    assert convert_to_json_serializable(result.result) == {
+        "element_count": 6,
+        "unexpected_count": 2,
+        "partial_unexpected_index_list": [
+            {"pk_1": 3, "pk_2": "three"},
+            {"pk_1": 5, "pk_2": "five"},
+        ],  # Dicts since columns were provided
+        "unexpected_percent": 33.33333333333333,
+        "partial_unexpected_list": [3, 10],
+        "partial_unexpected_counts": [
+            {"value": 3, "count": 1},
+            {"value": 10, "count": 1},
+        ],
+        "missing_count": 0,
+        "missing_percent": 0.0,
+        "unexpected_percent_total": 33.33333333333333,
+        "unexpected_percent_nonmissing": 33.33333333333333,
+    }
+
+
+def test_pandas_multiple_unexpected_index_column_names_summary_result_format_limit_1(
+    pandas_dataframe_for_unexpected_rows_with_index: pd.DataFrame,
+):
+    expectationConfiguration = ExpectationConfiguration(
+        expectation_type="expect_column_values_to_be_in_set",
+        kwargs={
+            "column": "numbers_with_duplicates",
+            "value_set": [1, 5, 22],
+            "result_format": {
+                "result_format": "SUMMARY",  # SUMMARY will include partial_unexpected* values only
+                "unexpected_index_column_names": ["pk_1", "pk_2"],  # Multiple columns
+                "partial_unexpected_count": 1,
+            },
+        },
+    )
+
+    expectation = ExpectColumnValuesToBeInSet(expectationConfiguration)
+    batch: Batch = Batch(data=pandas_dataframe_for_unexpected_rows_with_index)
+    engine = PandasExecutionEngine()
+    validator = Validator(
+        execution_engine=engine,
+        batches=[
+            batch,
+        ],
+    )
+    result = expectation.validate(validator)
+    assert convert_to_json_serializable(result.result) == {
+        "element_count": 6,
+        "unexpected_count": 2,
+        "partial_unexpected_index_list": [
+            {"pk_1": 3, "pk_2": "three"},
+        ],  # Dicts since columns were provided
+        "unexpected_percent": 33.33333333333333,
+        "partial_unexpected_list": [3],
+        "partial_unexpected_counts": [
+            {"value": 3, "count": 1},
+        ],
+        "missing_count": 0,
+        "missing_percent": 0.0,
+        "unexpected_percent_total": 33.33333333333333,
+        "unexpected_percent_nonmissing": 33.33333333333333,
+    }
+
+
+def test_pandas_multiple_unexpected_index_column_names_basic_result_format(
+    pandas_dataframe_for_unexpected_rows_with_index: pd.DataFrame,
+):
+    expectationConfiguration = ExpectationConfiguration(
+        expectation_type="expect_column_values_to_be_in_set",
+        kwargs={
+            "column": "numbers_with_duplicates",
+            "value_set": [1, 5, 22],
+            "result_format": {
+                "result_format": "BASIC",  # SUMMARY will include partial_unexpected_list only, which means unexpected_index_column_names will have no effect
+                "unexpected_index_column_names": ["pk_1", "pk_2"],
+            },
+        },
+    )
+
+    expectation = ExpectColumnValuesToBeInSet(expectationConfiguration)
+    batch: Batch = Batch(data=pandas_dataframe_for_unexpected_rows_with_index)
+    engine = PandasExecutionEngine()
+    validator = Validator(
+        execution_engine=engine,
+        batches=[
+            batch,
+        ],
+    )
+    result = expectation.validate(validator)
+    assert convert_to_json_serializable(result.result) == {
+        "element_count": 6,
+        "unexpected_count": 2,
+        "unexpected_percent": 33.33333333333333,
+        "partial_unexpected_list": [3, 10],
+        "missing_count": 0,
+        "missing_percent": 0.0,
+        "unexpected_percent_total": 33.33333333333333,
+        "unexpected_percent_nonmissing": 33.33333333333333,
+    }
+
+
+def test_pandas_single_unexpected_index_column_names_complete_result_format_non_existing_column(
+    pandas_dataframe_for_unexpected_rows_with_index: pd.DataFrame,
+):
+    expectationConfiguration = ExpectationConfiguration(
+        expectation_type="expect_column_values_to_be_in_set",
+        kwargs={
+            "column": "numbers_with_duplicates",
+            "value_set": [1, 5, 22],
+            "result_format": {
+                "result_format": "COMPLETE",
+                "unexpected_index_column_names": ["i_dont_exist"],  # Single column
+            },
+        },
+    )
+
+    expectation = ExpectColumnValuesToBeInSet(expectationConfiguration)
+    batch: Batch = Batch(data=pandas_dataframe_for_unexpected_rows_with_index)
+    engine = PandasExecutionEngine()
+    validator = Validator(
+        execution_engine=engine,
+        batches=[
+            batch,
+        ],
+    )
+    result = expectation.validate(validator)
+    assert result.success is False
+    assert result.exception_info
+    assert (
+        result.exception_info["exception_message"]
+        == 'Error: The unexpected_index_column: "i_dont_exist" in does not exist in Dataframe. Please check your configuration and try again.'
+    )
+
+
+def test_pandas_multiple_unexpected_index_column_names_complete_result_format_non_existing_column(
+    pandas_dataframe_for_unexpected_rows_with_index: pd.DataFrame,
+):
+    expectationConfiguration = ExpectationConfiguration(
+        expectation_type="expect_column_values_to_be_in_set",
+        kwargs={
+            "column": "numbers_with_duplicates",
+            "value_set": [1, 5, 22],
+            "result_format": {
+                "result_format": "COMPLETE",
+                "unexpected_index_column_names": [
+                    "pk_1",
+                    "i_dont_exist",
+                ],  # Only 1 column is valid
+            },
+        },
+    )
+    expectation = ExpectColumnValuesToBeInSet(expectationConfiguration)
+    batch: Batch = Batch(data=pandas_dataframe_for_unexpected_rows_with_index)
+    engine = PandasExecutionEngine()
+    validator = Validator(
+        execution_engine=engine,
+        batches=[
+            batch,
+        ],
+    )
+    result = expectation.validate(validator)
+    assert result.success is False
+    assert result.exception_info
+    assert (
+        result.exception_info["exception_message"]
+        == 'Error: The unexpected_index_column: "i_dont_exist" in does not exist in Dataframe. Please check your configuration and try again.'
+    )
+
+
 def test_pandas_default_to_not_include_unexpected_rows(
     dataframe_for_unexpected_rows, expected_evr_without_unexpected_rows
 ):