Repository: unionai-oss/pandera

Issue:
DataFrameSchema.from_yaml ignores "ordered"
We are using `DataFrameSchema.from_yaml` and our schema YAML file includes the line `ordered: true`. However, we found that the column order was not being validated as expected. By examining the string representation of the `DataFrameSchema` we observed that `ordered=False` - which is not expected.

It looks to us like the cause may be at https://github.com/unionai-oss/pandera/blob/master/pandera/io.py#L262
```python
    return DataFrameSchema(
        columns=columns,
        checks=checks,
        index=index,
        coerce=serialized_schema.get("coerce", False),
        strict=serialized_schema.get("strict", False),
        unique=serialized_schema.get("unique", None),
```
This does not include `ordered` and therefore the `ordered: true` in our YAML file is being ignored and the default of `ordered=False` is used.

- I have checked that this issue has not already been reported.
- I have confirmed this bug exists on the latest version of pandera.
- I have confirmed this bug exists on the master branch of pandera.


Discussion:
hi @zak1632, thanks for reporting this issue!

Yeah, basically we need to add the `ordered` key in `_serialize_schema`:
- https://github.com/unionai-oss/pandera/blob/master/pandera/io.py#L156

And then add an `ordered=serialized_schema.get("ordered", False) in `_deserialize_schema`:
- https://github.com/unionai-oss/pandera/blob/master/pandera/io.py#L268

And finally update the [`test_io`](https://github.com/unionai-oss/pandera/blob/master/tests/io/test_io.py) tests to catch this case.

Would you be open to making a PR for this?

Resolving patch:
diff --git a/pandera/io.py b/pandera/io.py
--- a/pandera/io.py
+++ b/pandera/io.py
@@ -154,6 +154,7 @@ def _serialize_schema(dataframe_schema):
         "coerce": dataframe_schema.coerce,
         "strict": dataframe_schema.strict,
         "unique": dataframe_schema.unique,
+        "ordered": dataframe_schema.ordered,
     }
 
 
@@ -266,6 +267,7 @@ def _deserialize_schema(serialized_schema):
         coerce=serialized_schema.get("coerce", False),
         strict=serialized_schema.get("strict", False),
         unique=serialized_schema.get("unique", None),
+        ordered=serialized_schema.get("ordered", False),
     )
 
 


Test patch:
diff --git a/tests/io/test_io.py b/tests/io/test_io.py
--- a/tests/io/test_io.py
+++ b/tests/io/test_io.py
@@ -237,6 +237,7 @@ def _create_schema(index="single"):
 coerce: false
 strict: true
 unique: null
+ordered: false
 """
 
 
@@ -637,6 +638,50 @@ def test_from_yaml_load_required_fields():
         )
 
 
+@pytest.mark.parametrize(
+    "is_ordered,test_data,expected",
+    [
+        (True, {"b": [1], "a": [1]}, pandera.errors.SchemaError),
+        (True, {"a": [1], "b": [1]}, pd.DataFrame(data={"a": [1], "b": [1]})),
+        (False, {"b": [1], "a": [1]}, pd.DataFrame(data={"b": [1], "a": [1]})),
+        (False, {"a": [1], "b": [1]}, pd.DataFrame(data={"a": [1], "b": [1]})),
+    ],
+)
+def test_from_yaml_retains_ordered_keyword(is_ordered, test_data, expected):
+    """Test that from_yaml() retains the 'ordered' keyword."""
+    yaml_schema = f"""
+    schema_type: dataframe
+    version: {pandera.__version__}
+    columns:
+        a:
+            dtype: int64
+            required: true
+        b:
+            dtype: int64
+            required: true
+    checks: null
+    index: null
+    coerce: false
+    strict: false
+    unique: null
+    ordered: {str(is_ordered).lower()}
+    """
+
+    # make sure the schema contains the ordered key word
+    schema = io.from_yaml(yaml_schema)
+    assert schema.ordered == is_ordered
+
+    # raise the error only when the ordered condition is violated
+    test_df = pd.DataFrame(data=test_data)
+
+    if isinstance(expected, type) and issubclass(expected, Exception):
+        with pytest.raises(expected):
+            assert schema.validate(test_df)
+    else:
+        validation = schema.validate(test_df)
+        assert test_df.equals(validation)
+
+
 def test_io_yaml_file_obj():
     """Test read and write operation on file object."""
     schema = _create_schema()
@@ -836,6 +881,38 @@ def unregistered_check(self, _):
         CheckedSchemaModel.to_yaml()
 
 
+@pytest.mark.parametrize(
+    "is_ordered,test_data,expected",
+    [
+        (True, {"b": [1], "a": [1]}, pandera.errors.SchemaError),
+        (True, {"a": [1], "b": [1]}, pd.DataFrame(data={"a": [1], "b": [1]})),
+        (False, {"b": [1], "a": [1]}, pd.DataFrame(data={"b": [1], "a": [1]})),
+        (False, {"a": [1], "b": [1]}, pd.DataFrame(data={"a": [1], "b": [1]})),
+    ],
+)
+def test_to_yaml_retains_ordered_keyword(is_ordered, test_data, expected):
+    """Test that to_yaml() retains the 'ordered' keyword."""
+    schema = pandera.DataFrameSchema(
+        columns={
+            "a": pandera.Column(pandera.Int),
+            "b": pandera.Column(pandera.Int),
+        },
+        ordered=is_ordered,
+    )
+
+    # make sure the schema contains the ordered key word
+    yaml_schema = schema.to_yaml()
+    assert "ordered" in yaml_schema  # pylint: disable=E1135
+
+    # raise the error only when the ordered condition is violated
+    if isinstance(expected, type) and issubclass(expected, Exception):
+        with pytest.raises(expected):
+            assert schema.validate(pd.DataFrame(data=test_data))
+    else:
+        validation_df = schema.validate(pd.DataFrame(data=test_data))
+        assert validation_df.equals(expected)
+
+
 def test_serialize_deserialize_custom_datetime_checks():
     """
     Test that custom checks for datetime columns can be serialized and
@@ -1079,6 +1156,7 @@ def datetime_check(pandas_obj, *, stat):
 coerce: true
 strict: true
 unique: null
+ordered: false
 """
 
 VALID_FRICTIONLESS_DF = pd.DataFrame(