Repository: unionai-oss/pandera

Issue:
Incorrect Pandera Polars DataFrameModel Type Coercion Logic
**Describe the bug**
A clear and concise description of what the bug is.
When defining a Polars DataFrameModel, setting `coerce=True` for an individual column will result in the entire DataFrame being coerced. 

- [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 main branch of pandera.

**Note**: Please read [this guide](https://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports) detailing how to provide the necessary information for us to reproduce your bug.

#### Code Sample, a copy-pastable example

```python
import pandera.polars as pa
import polars as pl
class MyModelSchema(pa.DataFrameModel):
    IntegerColumn: int = pa.Field(coerce=True)
    FloatColumn: float = pa.Field(coerce=False)
    class Config:
        coerce = False

if __name__ == "__main__":
    data = {"IntegerColumn": [1.5, 2.2, 3.1], "FloatColumn": ["1.0", "2.8", "3"]}
    validated_df = MyModelSchema.validate(df)

```

#### Expected behavior

I set coerce=True for the IntegerColumn, and explicitly False in the Config class as well as the FloatColumn. I pass string values into the "FloatColumn" data, expecting to get a validation error when validating the data against the DataFrameModel since these values aren't float types. However, the string values of "FloatColumn" do get coerced into floats and the resulting validated_df has the FloatColumn as type f64. 

#### Desktop (please complete the following information):

 - OS: Windows 11 Pro
 - Version: pandera==0.19.0b0
 - Python==3.11.5
 - polars==0.20.16

#### Screenshots
If applicable, add screenshots to help explain your problem.
![pandera_polars_coercion_bug](https://github.com/unionai-oss/pandera/assets/156021643/e0beb5fe-2331-4429-bc3c-29aaf6ba3434)

#### Context 
Line 390 of ..\pandera\backends\polars\container.py:
```
if not (
            schema.coerce or any(col.coerce for col in schema.columns.values())
        ):
```
Coercion handling is the same if **any** column has coerce=True. 


Discussion:
Hi @evanrasmussen9 are you able to make a PR to fix that line of code?

Resolving patch:
diff --git a/pandera/backends/polars/container.py b/pandera/backends/polars/container.py
--- a/pandera/backends/polars/container.py
+++ b/pandera/backends/polars/container.py
@@ -431,6 +431,10 @@ def _coerce_dtype_helper(
         error_handler = ErrorHandler(lazy=True)
 
         config_ctx = get_config_context(validation_depth_default=None)
+
+        # If validation depth involves validating data, use try_coerce since we
+        # want to check actual data values. Otherwise, coerce simply detects
+        # datatype mismatches.
         coerce_fn: str = (
             "try_coerce"
             if config_ctx.validation_depth
@@ -446,9 +450,10 @@ def _coerce_dtype_helper(
                 obj = getattr(schema.dtype, coerce_fn)(obj)
             else:
                 for col_schema in schema.columns.values():
-                    obj = getattr(col_schema.dtype, coerce_fn)(
-                        PolarsData(obj, col_schema.selector)
-                    )
+                    if schema.coerce or col_schema.coerce:
+                        obj = getattr(col_schema.dtype, coerce_fn)(
+                            PolarsData(obj, col_schema.selector)
+                        )
         except ParserError as exc:
             error_handler.collect_error(
                 validation_type(SchemaErrorReason.DATATYPE_COERCION),


Test patch:
diff --git a/tests/polars/test_polars_container.py b/tests/polars/test_polars_container.py
--- a/tests/polars/test_polars_container.py
+++ b/tests/polars/test_polars_container.py
@@ -7,6 +7,7 @@
 import pytest
 from hypothesis import given
 from hypothesis import strategies as st
+from polars.testing import assert_frame_equal
 from polars.testing.parametric import column, dataframes
 
 import pandera as pa
@@ -588,3 +589,23 @@ class Model(DataFrameModel):
             {"failure_case": "abc"},
             {"failure_case": "String"},
         ]
+
+
+def test_dataframe_column_level_coerce():
+
+    schema = DataFrameSchema(
+        {
+            "a": Column(int, coerce=True),
+            "b": Column(float, coerce=False),
+        }
+    )
+
+    df = pl.DataFrame({"a": [1.5, 2.2, 3.1], "b": ["1.0", "2.8", "3"]})
+    with pytest.raises(
+        pa.errors.SchemaError,
+        match="expected column 'b' to have type Float64, got String",
+    ):
+        schema.validate(df)
+
+    schema = schema.update_column("b", coerce=True)
+    assert_frame_equal(schema.validate(df), df.cast({"a": int, "b": float}))