We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Implicit ignore_na=True behaviour causes custom dataframe checks to be ignored by default
**Describe the bug**
When using custom dataframe checks returning boolean series, `SchemaModel.validate()` does not report validation errors generated by a custom check if the record in question (or is it the entire dataframe?) contains null values. This behaviour is deafult and can be disabled by setting `ignore_na` to `False` (either via `@pa.dataframe_check(ignore_na=False)` or a Config entry such as `check_name = {"ignore_na": False}`). This default behaviour is surprising, and the root cause of my issue was discovered only after a lengthy debugging process with much help from @cosmicBboy. Perhaps this default behaviour could be changed or at least better documented, as Niels mentions that this has come up more than once previously.

- [x] I have checked that this issue has not already been reported.
- [x] I have confirmed this bug exists on the latest version of pandera.
- [x] (optional) I have confirmed this bug exists on the master branch of pandera.


#### Code Sample, a copy-pastable example

CSV (DataFrame):
```
field0,field1,field2
,foo,foo
```
```python
@pa.dataframe_check
def field1_does_not_equal_field2(cls, df: pd.DataFrame) -> Series[bool]:
    return df["field1"] != df["field2"]

# Even though field1 == field2, validation passes because field0 is null
```

#### Expected behavior
Create a custom Pandera dataframe check returning a boolean series that accepts a CSV containing null values.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pandera/check_utils.py`
Content:
```
1 """Utility functions for validation."""
2 
3 from functools import lru_cache
4 from typing import NamedTuple, Optional, Tuple, Union
5 
6 import pandas as pd
7 
8 SupportedTypes = NamedTuple(
9     "SupportedTypes",
10     (
11         ("table_types", Tuple[type, ...]),
12         ("field_types", Tuple[type, ...]),
13         ("index_types", Tuple[type, ...]),
14         ("multiindex_types", Tuple[type, ...]),
15     ),
16 )
17 
18 
19 @lru_cache(maxsize=None)
20 def _supported_types():
21     # pylint: disable=import-outside-toplevel
22     table_types = [pd.DataFrame]
23     field_types = [pd.Series]
24     index_types = [pd.Index]
25     multiindex_types = [pd.MultiIndex]
26 
27     try:
28         import pyspark.pandas as ps
29 
30         table_types.append(ps.DataFrame)
31         field_types.append(ps.Series)
32         index_types.append(ps.Index)
33         multiindex_types.append(ps.MultiIndex)
34     except ImportError:
35         pass
36     try:  # pragma: no cover
37         import modin.pandas as mpd
38 
39         table_types.append(mpd.DataFrame)
40         field_types.append(mpd.Series)
41         index_types.append(mpd.Index)
42         multiindex_types.append(mpd.MultiIndex)
43     except ImportError:
44         pass
45     try:
46         import dask.dataframe as dd
47 
48         table_types.append(dd.DataFrame)
49         field_types.append(dd.Series)
50         index_types.append(dd.Index)
51     except ImportError:
52         pass
53 
54     return SupportedTypes(
55         tuple(table_types),
56         tuple(field_types),
57         tuple(index_types),
58         tuple(multiindex_types),
59     )
60 
61 
62 def is_table(obj):
63     """Verifies whether an object is table-like.
64 
65     Where a table is a 2-dimensional data matrix of rows and columns, which
66     can be indexed in multiple different ways.
67     """
68     return isinstance(obj, _supported_types().table_types)
69 
70 
71 def is_field(obj):
72     """Verifies whether an object is field-like.
73 
74     Where a field is a columnar representation of data in a table-like
75     data structure.
76     """
77     return isinstance(obj, _supported_types().field_types)
78 
79 
80 def is_index(obj):
81     """Verifies whether an object is a table index."""
82     return isinstance(obj, _supported_types().index_types)
83 
84 
85 def is_multiindex(obj):
86     """Verifies whether an object is a multi-level table index."""
87     return isinstance(obj, _supported_types().multiindex_types)
88 
89 
90 def is_supported_check_obj(obj):
91     """Verifies whether an object is table- or field-like."""
92     return is_table(obj) or is_field(obj)
93 
94 
95 def prepare_series_check_output(
96     check_obj: Union[pd.Series, pd.DataFrame],
97     check_output: pd.Series,
98     ignore_na: bool = True,
99     n_failure_cases: Optional[int] = None,
100 ) -> Tuple[pd.Series, pd.Series]:
101     """Prepare the check output and failure cases for a Series check output.
102 
103     check_obj can be a dataframe, since a check function can potentially return
104     a Series resulting from applying some check function that outputs a Series.
105     """
106     if ignore_na:
107         isna = (
108             check_obj.isna().any(axis="columns")
109             if isinstance(check_obj, pd.DataFrame)
110             else check_obj.isna()
111         )
112         try:
113             check_output = check_output | isna
114         except AttributeError:
115             # convert check_output to numpy for modin compatibility
116             check_output = check_output.to_numpy() | isna
117 
118     failure_cases = check_obj[~check_output]
119     if not failure_cases.empty and n_failure_cases is not None:
120         # NOTE: this is a hack to support pyspark.pandas and modin, since you
121         # can't use groupby on a dataframe with another dataframe
122         if type(failure_cases).__module__.startswith("pyspark.pandas") or type(
123             failure_cases
124         ).__module__.startswith("modin.pandas"):
125             failure_cases = (
126                 failure_cases.rename("failure_cases")
127                 .to_frame()
128                 .assign(check_output=check_output)
129                 .groupby("check_output")
130                 .head(n_failure_cases)["failure_cases"]
131             )
132         else:
133             failure_cases = failure_cases.groupby(check_output).head(
134                 n_failure_cases
135             )
136     return check_output, failure_cases
137 
138 
139 def prepare_dataframe_check_output(
140     check_obj: pd.DataFrame,
141     check_output: pd.DataFrame,
142     df_orig: Optional[pd.DataFrame] = None,
143     ignore_na: bool = True,
144     n_failure_cases: Optional[int] = None,
145 ) -> Tuple[pd.Series, pd.Series]:
146     """Unstack a dataframe of boolean values.
147 
148     Check results consisting of a boolean dataframe should be reported at the
149     most granular level.
150     """
151     if df_orig is not None:
152         assert df_orig.shape == check_output.shape
153 
154     if df_orig is None:
155         df_orig = check_obj
156     check_output = check_output.unstack()
157     if ignore_na:
158         check_output = check_output | df_orig.unstack().isna()
159     failure_cases = (
160         check_obj.unstack()[~check_output]
161         .rename("failure_case")
162         .rename_axis(["column", "index"])
163         .reset_index()
164     )
165     if not failure_cases.empty and n_failure_cases is not None:
166         failure_cases = failure_cases.drop_duplicates().head(n_failure_cases)
167     return check_output, failure_cases
168
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/pandera/check_utils.py b/pandera/check_utils.py
--- a/pandera/check_utils.py
+++ b/pandera/check_utils.py
@@ -105,7 +105,7 @@
     """
     if ignore_na:
         isna = (
-            check_obj.isna().any(axis="columns")
+            check_obj.isna().all(axis="columns")
             if isinstance(check_obj, pd.DataFrame)
             else check_obj.isna()
         )

{"golden_diff": "diff --git a/pandera/check_utils.py b/pandera/check_utils.py\n--- a/pandera/check_utils.py\n+++ b/pandera/check_utils.py\n@@ -105,7 +105,7 @@\n     \"\"\"\n     if ignore_na:\n         isna = (\n-            check_obj.isna().any(axis=\"columns\")\n+            check_obj.isna().all(axis=\"columns\")\n             if isinstance(check_obj, pd.DataFrame)\n             else check_obj.isna()\n         )\n", "issue": "Implicit ignore_na=True behaviour causes custom dataframe checks to be ignored by default\n**Describe the bug**\r\nWhen using custom dataframe checks returning boolean series, `SchemaModel.validate()` does not report validation errors generated by a custom check if the record in question (or is it the entire dataframe?) contains null values. This behaviour is deafult and can be disabled by setting `ignore_na` to `False` (either via `@pa.dataframe_check(ignore_na=False)` or a Config entry such as `check_name = {\"ignore_na\": False}`). This default behaviour is surprising, and the root cause of my issue was discovered only after a lengthy debugging process with much help from @cosmicBboy. Perhaps this default behaviour could be changed or at least better documented, as Niels mentions that this has come up more than once previously.\r\n\r\n- [x] I have checked that this issue has not already been reported.\r\n- [x] I have confirmed this bug exists on the latest version of pandera.\r\n- [x] (optional) I have confirmed this bug exists on the master branch of pandera.\r\n\r\n\r\n#### Code Sample, a copy-pastable example\r\n\r\nCSV (DataFrame):\r\n```\r\nfield0,field1,field2\r\n,foo,foo\r\n```\r\n```python\r\n@pa.dataframe_check\r\ndef field1_does_not_equal_field2(cls, df: pd.DataFrame) -> Series[bool]:\r\n    return df[\"field1\"] != df[\"field2\"]\r\n\r\n# Even though field1 == field2, validation passes because field0 is null\r\n```\r\n\r\n#### Expected behavior\r\nCreate a custom Pandera dataframe check returning a boolean series that accepts a CSV containing null values.\r\n\n", "before_files": [{"content": "\"\"\"Utility functions for validation.\"\"\"\n\nfrom functools import lru_cache\nfrom typing import NamedTuple, Optional, Tuple, Union\n\nimport pandas as pd\n\nSupportedTypes = NamedTuple(\n    \"SupportedTypes\",\n    (\n        (\"table_types\", Tuple[type, ...]),\n        (\"field_types\", Tuple[type, ...]),\n        (\"index_types\", Tuple[type, ...]),\n        (\"multiindex_types\", Tuple[type, ...]),\n    ),\n)\n\n\n@lru_cache(maxsize=None)\ndef _supported_types():\n    # pylint: disable=import-outside-toplevel\n    table_types = [pd.DataFrame]\n    field_types = [pd.Series]\n    index_types = [pd.Index]\n    multiindex_types = [pd.MultiIndex]\n\n    try:\n        import pyspark.pandas as ps\n\n        table_types.append(ps.DataFrame)\n        field_types.append(ps.Series)\n        index_types.append(ps.Index)\n        multiindex_types.append(ps.MultiIndex)\n    except ImportError:\n        pass\n    try:  # pragma: no cover\n        import modin.pandas as mpd\n\n        table_types.append(mpd.DataFrame)\n        field_types.append(mpd.Series)\n        index_types.append(mpd.Index)\n        multiindex_types.append(mpd.MultiIndex)\n    except ImportError:\n        pass\n    try:\n        import dask.dataframe as dd\n\n        table_types.append(dd.DataFrame)\n        field_types.append(dd.Series)\n        index_types.append(dd.Index)\n    except ImportError:\n        pass\n\n    return SupportedTypes(\n        tuple(table_types),\n        tuple(field_types),\n        tuple(index_types),\n        tuple(multiindex_types),\n    )\n\n\ndef is_table(obj):\n    \"\"\"Verifies whether an object is table-like.\n\n    Where a table is a 2-dimensional data matrix of rows and columns, which\n    can be indexed in multiple different ways.\n    \"\"\"\n    return isinstance(obj, _supported_types().table_types)\n\n\ndef is_field(obj):\n    \"\"\"Verifies whether an object is field-like.\n\n    Where a field is a columnar representation of data in a table-like\n    data structure.\n    \"\"\"\n    return isinstance(obj, _supported_types().field_types)\n\n\ndef is_index(obj):\n    \"\"\"Verifies whether an object is a table index.\"\"\"\n    return isinstance(obj, _supported_types().index_types)\n\n\ndef is_multiindex(obj):\n    \"\"\"Verifies whether an object is a multi-level table index.\"\"\"\n    return isinstance(obj, _supported_types().multiindex_types)\n\n\ndef is_supported_check_obj(obj):\n    \"\"\"Verifies whether an object is table- or field-like.\"\"\"\n    return is_table(obj) or is_field(obj)\n\n\ndef prepare_series_check_output(\n    check_obj: Union[pd.Series, pd.DataFrame],\n    check_output: pd.Series,\n    ignore_na: bool = True,\n    n_failure_cases: Optional[int] = None,\n) -> Tuple[pd.Series, pd.Series]:\n    \"\"\"Prepare the check output and failure cases for a Series check output.\n\n    check_obj can be a dataframe, since a check function can potentially return\n    a Series resulting from applying some check function that outputs a Series.\n    \"\"\"\n    if ignore_na:\n        isna = (\n            check_obj.isna().any(axis=\"columns\")\n            if isinstance(check_obj, pd.DataFrame)\n            else check_obj.isna()\n        )\n        try:\n            check_output = check_output | isna\n        except AttributeError:\n            # convert check_output to numpy for modin compatibility\n            check_output = check_output.to_numpy() | isna\n\n    failure_cases = check_obj[~check_output]\n    if not failure_cases.empty and n_failure_cases is not None:\n        # NOTE: this is a hack to support pyspark.pandas and modin, since you\n        # can't use groupby on a dataframe with another dataframe\n        if type(failure_cases).__module__.startswith(\"pyspark.pandas\") or type(\n            failure_cases\n        ).__module__.startswith(\"modin.pandas\"):\n            failure_cases = (\n                failure_cases.rename(\"failure_cases\")\n                .to_frame()\n                .assign(check_output=check_output)\n                .groupby(\"check_output\")\n                .head(n_failure_cases)[\"failure_cases\"]\n            )\n        else:\n            failure_cases = failure_cases.groupby(check_output).head(\n                n_failure_cases\n            )\n    return check_output, failure_cases\n\n\ndef prepare_dataframe_check_output(\n    check_obj: pd.DataFrame,\n    check_output: pd.DataFrame,\n    df_orig: Optional[pd.DataFrame] = None,\n    ignore_na: bool = True,\n    n_failure_cases: Optional[int] = None,\n) -> Tuple[pd.Series, pd.Series]:\n    \"\"\"Unstack a dataframe of boolean values.\n\n    Check results consisting of a boolean dataframe should be reported at the\n    most granular level.\n    \"\"\"\n    if df_orig is not None:\n        assert df_orig.shape == check_output.shape\n\n    if df_orig is None:\n        df_orig = check_obj\n    check_output = check_output.unstack()\n    if ignore_na:\n        check_output = check_output | df_orig.unstack().isna()\n    failure_cases = (\n        check_obj.unstack()[~check_output]\n        .rename(\"failure_case\")\n        .rename_axis([\"column\", \"index\"])\n        .reset_index()\n    )\n    if not failure_cases.empty and n_failure_cases is not None:\n        failure_cases = failure_cases.drop_duplicates().head(n_failure_cases)\n    return check_output, failure_cases\n", "path": "pandera/check_utils.py"}], "after_files": [{"content": "\"\"\"Utility functions for validation.\"\"\"\n\nfrom functools import lru_cache\nfrom typing import NamedTuple, Optional, Tuple, Union\n\nimport pandas as pd\n\nSupportedTypes = NamedTuple(\n    \"SupportedTypes\",\n    (\n        (\"table_types\", Tuple[type, ...]),\n        (\"field_types\", Tuple[type, ...]),\n        (\"index_types\", Tuple[type, ...]),\n        (\"multiindex_types\", Tuple[type, ...]),\n    ),\n)\n\n\n@lru_cache(maxsize=None)\ndef _supported_types():\n    # pylint: disable=import-outside-toplevel\n    table_types = [pd.DataFrame]\n    field_types = [pd.Series]\n    index_types = [pd.Index]\n    multiindex_types = [pd.MultiIndex]\n\n    try:\n        import pyspark.pandas as ps\n\n        table_types.append(ps.DataFrame)\n        field_types.append(ps.Series)\n        index_types.append(ps.Index)\n        multiindex_types.append(ps.MultiIndex)\n    except ImportError:\n        pass\n    try:  # pragma: no cover\n        import modin.pandas as mpd\n\n        table_types.append(mpd.DataFrame)\n        field_types.append(mpd.Series)\n        index_types.append(mpd.Index)\n        multiindex_types.append(mpd.MultiIndex)\n    except ImportError:\n        pass\n    try:\n        import dask.dataframe as dd\n\n        table_types.append(dd.DataFrame)\n        field_types.append(dd.Series)\n        index_types.append(dd.Index)\n    except ImportError:\n        pass\n\n    return SupportedTypes(\n        tuple(table_types),\n        tuple(field_types),\n        tuple(index_types),\n        tuple(multiindex_types),\n    )\n\n\ndef is_table(obj):\n    \"\"\"Verifies whether an object is table-like.\n\n    Where a table is a 2-dimensional data matrix of rows and columns, which\n    can be indexed in multiple different ways.\n    \"\"\"\n    return isinstance(obj, _supported_types().table_types)\n\n\ndef is_field(obj):\n    \"\"\"Verifies whether an object is field-like.\n\n    Where a field is a columnar representation of data in a table-like\n    data structure.\n    \"\"\"\n    return isinstance(obj, _supported_types().field_types)\n\n\ndef is_index(obj):\n    \"\"\"Verifies whether an object is a table index.\"\"\"\n    return isinstance(obj, _supported_types().index_types)\n\n\ndef is_multiindex(obj):\n    \"\"\"Verifies whether an object is a multi-level table index.\"\"\"\n    return isinstance(obj, _supported_types().multiindex_types)\n\n\ndef is_supported_check_obj(obj):\n    \"\"\"Verifies whether an object is table- or field-like.\"\"\"\n    return is_table(obj) or is_field(obj)\n\n\ndef prepare_series_check_output(\n    check_obj: Union[pd.Series, pd.DataFrame],\n    check_output: pd.Series,\n    ignore_na: bool = True,\n    n_failure_cases: Optional[int] = None,\n) -> Tuple[pd.Series, pd.Series]:\n    \"\"\"Prepare the check output and failure cases for a Series check output.\n\n    check_obj can be a dataframe, since a check function can potentially return\n    a Series resulting from applying some check function that outputs a Series.\n    \"\"\"\n    if ignore_na:\n        isna = (\n            check_obj.isna().all(axis=\"columns\")\n            if isinstance(check_obj, pd.DataFrame)\n            else check_obj.isna()\n        )\n        try:\n            check_output = check_output | isna\n        except AttributeError:\n            # convert check_output to numpy for modin compatibility\n            check_output = check_output.to_numpy() | isna\n\n    failure_cases = check_obj[~check_output]\n    if not failure_cases.empty and n_failure_cases is not None:\n        # NOTE: this is a hack to support pyspark.pandas and modin, since you\n        # can't use groupby on a dataframe with another dataframe\n        if type(failure_cases).__module__.startswith(\"pyspark.pandas\") or type(\n            failure_cases\n        ).__module__.startswith(\"modin.pandas\"):\n            failure_cases = (\n                failure_cases.rename(\"failure_cases\")\n                .to_frame()\n                .assign(check_output=check_output)\n                .groupby(\"check_output\")\n                .head(n_failure_cases)[\"failure_cases\"]\n            )\n        else:\n            failure_cases = failure_cases.groupby(check_output).head(\n                n_failure_cases\n            )\n    return check_output, failure_cases\n\n\ndef prepare_dataframe_check_output(\n    check_obj: pd.DataFrame,\n    check_output: pd.DataFrame,\n    df_orig: Optional[pd.DataFrame] = None,\n    ignore_na: bool = True,\n    n_failure_cases: Optional[int] = None,\n) -> Tuple[pd.Series, pd.Series]:\n    \"\"\"Unstack a dataframe of boolean values.\n\n    Check results consisting of a boolean dataframe should be reported at the\n    most granular level.\n    \"\"\"\n    if df_orig is not None:\n        assert df_orig.shape == check_output.shape\n\n    if df_orig is None:\n        df_orig = check_obj\n    check_output = check_output.unstack()\n    if ignore_na:\n        check_output = check_output | df_orig.unstack().isna()\n    failure_cases = (\n        check_obj.unstack()[~check_output]\n        .rename(\"failure_case\")\n        .rename_axis([\"column\", \"index\"])\n        .reset_index()\n    )\n    if not failure_cases.empty and n_failure_cases is not None:\n        failure_cases = failure_cases.drop_duplicates().head(n_failure_cases)\n    return check_output, failure_cases\n", "path": "pandera/check_utils.py"}]}