数据框模型

0.5.0 中的新功能

pandera 提供了一个类基础的 API,深受 pydantic 的启发。与 基于对象的 API 相比,您可以以与定义 pydantic 模型非常相似的方式定义数据框模型。

DataFrameModel 使用标准的 typing 语法,通过 pandera.typing 模块进行注解。模型可以显式转换为 DataFrameSchema,或直接用于验证 DataFrame

注意

由于当前在pandas库中的限制(请参见讨论这里), pandera 注释仅用于运行时验证,并且对静态类型检查器如mypy的支持有限。 有关更多详细信息,请参见Mypy集成

基本用法

import pandas as pd
import pandera as pa
from pandera.typing import Index, DataFrame, Series


class InputSchema(pa.DataFrameModel):
    year: Series[int] = pa.Field(gt=2000, coerce=True)
    month: Series[int] = pa.Field(ge=1, le=12, coerce=True)
    day: Series[int] = pa.Field(ge=0, le=365, coerce=True)

class OutputSchema(InputSchema):
    revenue: Series[float]

@pa.check_types
def transform(df: DataFrame[InputSchema]) -> DataFrame[OutputSchema]:
    return df.assign(revenue=100.0)


df = pd.DataFrame({
    "year": ["2001", "2002", "2003"],
    "month": ["3", "6", "12"],
    "day": ["200", "156", "365"],
})

transform(df)

invalid_df = pd.DataFrame({
    "year": ["2001", "2002", "1999"],
    "month": ["3", "6", "12"],
    "day": ["200", "156", "365"],
})

try:
    transform(invalid_df)
except pa.errors.SchemaError as exc:
    print(exc)
error in check_types decorator of function 'transform': Column 'year' failed element-wise validator number 0: greater_than(2000) failure cases: 1999

正如您在上面的示例中看到的,您可以通过子类化 DataFrameModel 并将列/索引字段定义为类属性来定义一个模式。 check_types() 装饰器是必需的,以便在运行时对数据框进行验证。

请注意,Field 适用于 ColumnIndex 对象,通过关键字参数暴露内置的 Check

(0.6.2中新增) 当您访问在模式中定义的类属性时,它将返回在经过验证的 pd.DataFrame 中使用的列名。在上面的示例中,这将只是字符串 "year"

print(f"Column name for 'year' is {InputSchema.year}\n")
print(df.loc[:, [InputSchema.year, "day"]])
Column name for 'year' is year

   year  day
0  2001  200
1  2002  156
2  2003  365

直接使用数据类型进行列类型注释

0.15.0中新功能

为了简洁,您可以在不使用Series泛型的情况下,为列使用类型注解。这个类属性将在内部被解释为Column对象。

class InputSchema(pa.DataFrameModel):
    year: int = pa.Field(gt=2000, coerce=True)
    month: int = pa.Field(ge=1, le=12, coerce=True)
    day: int = pa.Field(ge=0, le=365, coerce=True)

重用字段对象

要定义可重用的 Field 定义,您需要使用 functools.partial。这确保每个字段属性都绑定到一个独特的 Field 实例。

from functools import partial
from pandera import DataFrameModel, Field

NormalizedField = partial(Field, ge=0, le=1)

class SchemaWithReusedFields(DataFrameModel):
    xnorm: float = NormalizedField()
    ynorm: float = NormalizedField()

初始化验证

在0.8.0中新增

Pandera 提供了一个用于在初始化时验证数据框的接口。这个 API 使用 pandera.typing.pandas.DataFrame 泛型类型 进行初始化时验证 DataFrameModel 类型变量:

import pandas as pd
import pandera as pa

from pandera.typing import DataFrame, Series


class Schema(pa.DataFrameModel):
    state: Series[str]
    city: Series[str]
    price: Series[int] = pa.Field(in_range={"min_value": 5, "max_value": 20})

DataFrame[Schema](
    {
        'state': ['NY','FL','GA','CA'],
        'city': ['New York', 'Miami', 'Atlanta', 'San Francisco'],
        'price': [8, 12, 10, 16],
    }
)
状态 城市 价格
0 纽约 纽约 8
1 佛罗里达 迈阿密 12
2 GA 亚特兰大 10
3 CA 旧金山 16

请参考 支持的数据框库 以查看该语法如何应用于其他支持的数据框类型。

转换为DataFrameSchema

您可以轻松地将一个 DataFrameModel 类转换为一个 DataFrameSchema

print(InputSchema.to_schema())
<Schema DataFrameSchema(
    columns={
        'year': <Schema Column(name=year, type=DataType(int64))>
        'month': <Schema Column(name=month, type=DataType(int64))>
        'day': <Schema Column(name=day, type=DataType(int64))>
    },
    checks=[],
    parsers=[],
    coerce=False,
    dtype=None,
    index=None,
    strict=False,
    name=InputSchema,
    ordered=False,
    unique_column_names=False,
    metadata=None, 
    add_missing_columns=False
)>

您也可以使用 validate() 方法来验证数据框:

print(InputSchema.validate(df))
   year  month  day
0  2001      3  200
1  2002      6  156
2  2003     12  365

或者您可以直接使用DataFrameModel()类来验证数据框,这是一种语法糖,简单地委托给validate()方法。

print(InputSchema(df))
   year  month  day
0  2001      3  200
1  2002      6  156
2  2003     12  365

针对多个架构进行验证

在 0.14.0 中的新功能

内置的 typing.Union 类型支持多个 DataFrame 架构。

from typing import Union
import pandas as pd
import pandera as pa
from pandera.typing import DataFrame, Series

class OnlyZeroesSchema(pa.DataFrameModel):
    a: Series[int] = pa.Field(eq=0)

class OnlyOnesSchema(pa.DataFrameModel):
    a: Series[int] = pa.Field(eq=1)

@pa.check_types
def return_zeros_or_ones(
    df: Union[DataFrame[OnlyZeroesSchema], DataFrame[OnlyOnesSchema]]
) -> Union[DataFrame[OnlyZeroesSchema], DataFrame[OnlyOnesSchema]]:
    return df

# passes
return_zeros_or_ones(pd.DataFrame({"a": [0, 0]}))
return_zeros_or_ones(pd.DataFrame({"a": [1, 1]}))

# fails
try:
    return_zeros_or_ones(pd.DataFrame({"a": [0, 2]}))
except pa.errors.SchemaErrors as exc:
    print(exc)
{
    "DATA": {
        "INVALID_TYPE": [
            {
                "schema": "OnlyOnesSchema",
                "column": "OnlyZeroesSchema",
                "check": "equal_to(0)",
                "error": "error in check_types decorator of function 'return_zeros_or_ones': Column 'a' failed element-wise validator number 0: equal_to(0) failure cases: 2"
            },
            {
                "schema": "OnlyOnesSchema",
                "column": "OnlyOnesSchema",
                "check": "equal_to(1)",
                "error": "error in check_types decorator of function 'return_zeros_or_ones': Column 'a' failed element-wise validator number 0: equal_to(1) failure cases: 0, 2"
            }
        ]
    }
}

请注意,DataFrame 模式和内置类型的混合将忽略使用 pandera 对内置类型的检查。应使用 Pydantic 来检查和/或强制转换任何内置类型。

import pandas as pd
from typing import Union
import pandera as pa
from pandera.typing import DataFrame, Series

class OnlyZeroesSchema(pa.DataFrameModel):
    a: Series[int] = pa.Field(eq=0)


@pa.check_types
def df_and_int_types(

    val: Union[DataFrame[OnlyZeroesSchema], int]
) -> Union[DataFrame[OnlyZeroesSchema], int]:
    return val


df_and_int_types(pd.DataFrame({"a": [0, 0]}))
int_val = df_and_int_types(5)
str_val = df_and_int_types("5")

no_pydantic_report = f"No Pydantic: {isinstance(int_val, int)}, {isinstance(str_val, int)}"


@pa.check_types(with_pydantic=True)
def df_and_int_types_with_pydantic(
    val: Union[DataFrame[OnlyZeroesSchema], int]
) -> Union[DataFrame[OnlyZeroesSchema], int]:
    return val


df_and_int_types_with_pydantic(pd.DataFrame({"a": [0, 0]}))
int_val_w_pyd = df_and_int_types_with_pydantic(5)
str_val_w_pyd = df_and_int_types_with_pydantic("5")

pydantic_report = f"With Pydantic: {isinstance(int_val_w_pyd, int)}, {isinstance(str_val_w_pyd, int)}"

print(no_pydantic_report)
print(pydantic_report)
No Pydantic: True, False
With Pydantic: True, True

排除的属性

以下划线开头的类变量将自动从模型中排除。 Config 也是一个保留名称。 然而,aliases 可以用来规避这些限制。

支持的数据类型

任何由 pandera 支持的数据类型都可以用作 SeriesIndex 的类型参数。但是,有几个注意事项。

重要

您可以了解更多关于数据类型验证如何工作的内容 Data Type Validation

数据类型别名

import pandera as pa
from pandera.typing import Series, String

class Schema(pa.DataFrameModel):
    a: Series[String]

类型与实例

您必须提供一个 类型,而不是一个 实例

✅ 好的:

import pandas as pd

class Schema(pa.DataFrameModel):
    a: Series[pd.StringDtype]

❌ 不好:

注意

这仅适用于 pandas 版本 < 2.0.0。在 pandas > 2.0.0 中,pd.StringDtype() 将产生一个类型。

class Schema(pa.DataFrameModel):
    a: Series[pd.StringDtype()]

参数化数据类型

Pandas支持几种参数化的数据类型。从pandas 1.2.0开始:

数据种类

数据类型

参数

时区感知的日期时间

DatetimeTZDtype

unit, tz

分类

CategoricalDtype

categories, ordered

周期

PeriodDtype

freq

稀疏

SparseDtype

dtype, fill_value

区间

IntervalDtype

子类型

注解

参数可以通过 typing.Annotated 提供。这需要 python >= 3.9 或 typing_extensions,这已经是 Pandera 的一个要求。不幸的是 typing.Annotated 尚未被移植到 python 3.6。

✅ 好的:

try:
    from typing import Annotated  # python 3.9+
except ImportError:
    from typing_extensions import Annotated

class Schema(pa.DataFrameModel):
    col: Series[Annotated[pd.DatetimeTZDtype, "ns", "est"]]

此外,您必须按照dtype构造函数中定义的顺序传递所有参数(见 table)。

❌ 不好:

class Schema(pa.DataFrameModel):
    col: Series[Annotated[pd.DatetimeTZDtype, "utc"]]

Schema.to_schema()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[14], line 4
      1 class Schema(pa.DataFrameModel):
      2     col: Series[Annotated[pd.DatetimeTZDtype, "utc"]]
----> 4 Schema.to_schema()

File ~/checkouts/readthedocs.org/user_builds/pandera/conda/stable/lib/python3.12/site-packages/pandera/api/dataframe/model.py:262, in DataFrameModel.to_schema(cls)
    248 if cls.__config__ is not None:
    249     kwargs = {
    250         "dtype": cls.__config__.dtype,
    251         "coerce": cls.__config__.coerce,
   (...)
    260         "drop_invalid_rows": cls.__config__.drop_invalid_rows,
    261     }
--> 262 cls.__schema__ = cls.build_schema_(**kwargs)
    263 if cls not in MODEL_CACHE:
    264     MODEL_CACHE[cls] = cls.__schema__  # type: ignore

File ~/checkouts/readthedocs.org/user_builds/pandera/conda/stable/lib/python3.12/site-packages/pandera/api/pandas/model.py:39, in DataFrameModel.build_schema_(cls, **kwargs)
     32 @classmethod
     33 def build_schema_(cls, **kwargs) -> DataFrameSchema:
     34     multiindex_kwargs = {
     35         name[len("multiindex_") :]: value
     36         for name, value in vars(cls.__config__).items()
     37         if name.startswith("multiindex_")
     38     }
---> 39     columns, index = cls._build_columns_index(
     40         cls.__fields__,
     41         cls.__checks__,
     42         cls.__parsers__,
     43         **multiindex_kwargs,
     44     )
     45     return DataFrameSchema(
     46         columns,
     47         index=index,
   (...)
     50         **kwargs,
     51     )

File ~/checkouts/readthedocs.org/user_builds/pandera/conda/stable/lib/python3.12/site-packages/pandera/api/pandas/model.py:85, in DataFrameModel._build_columns_index(cls, fields, checks, parsers, **multiindex_kwargs)
     79     if field.dtype_kwargs:
     80         raise TypeError(
     81             "Cannot specify redundant 'dtype_kwargs' "
     82             + f"for {annotation.raw_annotation}."
     83             + "\n Usage Tip: Drop 'typing.Annotated'."
     84         )
---> 85     dtype_kwargs = get_dtype_kwargs(annotation)
     86     dtype = annotation.arg(**dtype_kwargs)  # type: ignore
     87 elif annotation.default_dtype:

File ~/checkouts/readthedocs.org/user_builds/pandera/conda/stable/lib/python3.12/site-packages/pandera/api/dataframe/model.py:73, in get_dtype_kwargs(annotation)
     71 dtype_arg_names = list(sig.parameters.keys())
     72 if len(annotation.metadata) != len(dtype_arg_names):  # type: ignore
---> 73     raise TypeError(
     74         f"Annotation '{annotation.arg.__name__}' requires "  # type: ignore
     75         + f"all positional arguments {dtype_arg_names}."
     76     )
     77 return dict(zip(dtype_arg_names, annotation.metadata))

TypeError: Annotation 'DatetimeTZDtype' requires all positional arguments ['unit', 'tz'].

字段

✅ 好的:

class SchemaFieldDatetimeTZDtype(pa.DataFrameModel):
    col: Series[pd.DatetimeTZDtype] = pa.Field(
        dtype_kwargs={"unit": "ns", "tz": "EST"}
    )

您无法同时使用 typing.Annotateddtype_kwargs

❌ 不好:

class SchemaFieldDatetimeTZDtype(pa.DataFrameModel):
    col: Series[Annotated[pd.DatetimeTZDtype, "ns", "est"]] = pa.Field(
        dtype_kwargs={"unit": "ns", "tz": "EST"}
    )

Schema.to_schema()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[16], line 6
      1 class SchemaFieldDatetimeTZDtype(pa.DataFrameModel):
      2     col: Series[Annotated[pd.DatetimeTZDtype, "ns", "est"]] = pa.Field(
      3         dtype_kwargs={"unit": "ns", "tz": "EST"}
      4     )
----> 6 Schema.to_schema()

File ~/checkouts/readthedocs.org/user_builds/pandera/conda/stable/lib/python3.12/site-packages/pandera/api/dataframe/model.py:262, in DataFrameModel.to_schema(cls)
    248 if cls.__config__ is not None:
    249     kwargs = {
    250         "dtype": cls.__config__.dtype,
    251         "coerce": cls.__config__.coerce,
   (...)
    260         "drop_invalid_rows": cls.__config__.drop_invalid_rows,
    261     }
--> 262 cls.__schema__ = cls.build_schema_(**kwargs)
    263 if cls not in MODEL_CACHE:
    264     MODEL_CACHE[cls] = cls.__schema__  # type: ignore

File ~/checkouts/readthedocs.org/user_builds/pandera/conda/stable/lib/python3.12/site-packages/pandera/api/pandas/model.py:39, in DataFrameModel.build_schema_(cls, **kwargs)
     32 @classmethod
     33 def build_schema_(cls, **kwargs) -> DataFrameSchema:
     34     multiindex_kwargs = {
     35         name[len("multiindex_") :]: value
     36         for name, value in vars(cls.__config__).items()
     37         if name.startswith("multiindex_")
     38     }
---> 39     columns, index = cls._build_columns_index(
     40         cls.__fields__,
     41         cls.__checks__,
     42         cls.__parsers__,
     43         **multiindex_kwargs,
     44     )
     45     return DataFrameSchema(
     46         columns,
     47         index=index,
   (...)
     50         **kwargs,
     51     )

File ~/checkouts/readthedocs.org/user_builds/pandera/conda/stable/lib/python3.12/site-packages/pandera/api/pandas/model.py:85, in DataFrameModel._build_columns_index(cls, fields, checks, parsers, **multiindex_kwargs)
     79     if field.dtype_kwargs:
     80         raise TypeError(
     81             "Cannot specify redundant 'dtype_kwargs' "
     82             + f"for {annotation.raw_annotation}."
     83             + "\n Usage Tip: Drop 'typing.Annotated'."
     84         )
---> 85     dtype_kwargs = get_dtype_kwargs(annotation)
     86     dtype = annotation.arg(**dtype_kwargs)  # type: ignore
     87 elif annotation.default_dtype:

File ~/checkouts/readthedocs.org/user_builds/pandera/conda/stable/lib/python3.12/site-packages/pandera/api/dataframe/model.py:73, in get_dtype_kwargs(annotation)
     71 dtype_arg_names = list(sig.parameters.keys())
     72 if len(annotation.metadata) != len(dtype_arg_names):  # type: ignore
---> 73     raise TypeError(
     74         f"Annotation '{annotation.arg.__name__}' requires "  # type: ignore
     75         + f"all positional arguments {dtype_arg_names}."
     76     )
     77 return dict(zip(dtype_arg_names, annotation.metadata))

TypeError: Annotation 'DatetimeTZDtype' requires all positional arguments ['unit', 'tz'].

必需的列

默认情况下,架构中指定的所有列都是 必需的,这意味着如果输入的 DataFrame 中缺少某一列,将会抛出异常。如果您想将某一列设为可选,请使用 typing.Optional 进行注释。

from typing import Optional

import pandas as pd
import pandera as pa
from pandera.typing import Series


class Schema(pa.DataFrameModel):
    a: Series[str]
    b: Optional[Series[int]]

df = pd.DataFrame({"a": ["2001", "2002", "2003"]})
Schema.validate(df)
a
0 2001
1 2002
2 2003

模式继承

您还可以使用继承在基础架构之上构建模式。

class BaseSchema(pa.DataFrameModel):
    year: Series[str]

class FinalSchema(BaseSchema):
    year: Series[int] = pa.Field(ge=2000, coerce=True)  # overwrite the base type
    passengers: Series[int]
    idx: Index[int] = pa.Field(ge=0)

df = pd.DataFrame({
    "year": ["2000", "2001", "2002"],
})

@pa.check_types
def transform(df: DataFrame[BaseSchema]) -> DataFrame[FinalSchema]:
    return (
        df.assign(passengers=[61000, 50000, 45000])
        .set_index(pd.Index([1, 2, 3]))
        .astype({"year": int})
    )

transform(df)
乘客人数
1 2000 61000
2 2001 50000
3 2002 45000

配置

架构范围的选项可以通过 Config 类来控制,该类位于 DataFrameModel 子类中。完整的选项集合可以在 BaseConfig 类中找到。

class Schema(pa.DataFrameModel):

    year: Series[int] = pa.Field(gt=2000, coerce=True)
    month: Series[int] = pa.Field(ge=1, le=12, coerce=True)
    day: Series[int] = pa.Field(ge=0, le=365, coerce=True)

    class Config:
        name = "BaseSchema"
        strict = True
        coerce = True
        foo = "bar"  # Interpreted as dataframe check
        baz = ...    # Interpreted as a dataframe check with no additional arguments

不需要Config继承 BaseConfig,但 它必须命名为‘Config’。

有关使用注册的数据框检查的详细信息,请参见 基于类的 API 注册的自定义检查

多重索引

支持MultiIndex功能的类基础API:

import pandera as pa
from pandera.typing import Index, Series

class MultiIndexSchema(pa.DataFrameModel):

    year: Index[int] = pa.Field(gt=2000, coerce=True)
    month: Index[int] = pa.Field(ge=1, le=12, coerce=True)
    passengers: Series[int]

    class Config:
        # provide multi index options in the config
        multiindex_name = "time"
        multiindex_strict = True
        multiindex_coerce = True

index = MultiIndexSchema.to_schema().index
print(index)
<Schema MultiIndex(
    indexes=[
        <Schema Index(name=year, type=DataType(int64))>
        <Schema Index(name=month, type=DataType(int64))>
    ]
    coerce=True,
    strict=True,
    name=time,
    ordered=True
)>
from pprint import pprint

pprint({name: col.checks for name, col in index.columns.items()})
{'month': [<Check greater_than_or_equal_to: greater_than_or_equal_to(1)>,
           <Check less_than_or_equal_to: less_than_or_equal_to(12)>],
 'year': [<Check greater_than: greater_than(2000)>]}

多个 Index 注释会自动转换为 MultiIndex。MultiIndex 选项在 Config 中给出。

索引名称

使用 check_name 来验证单索引数据框的索引名称:

import pandas as pd
import pandera as pa
from pandera.typing import Index, Series

class Schema(pa.DataFrameModel):
    year: Series[int] = pa.Field(gt=2000, coerce=True)
    passengers: Series[int]
    idx: Index[int] = pa.Field(ge=0, check_name=True)

df = pd.DataFrame({
    "year": [2001, 2002, 2003],
    "passengers": [61000, 50000, 45000],
})

try:
    Schema.validate(df)
except pa.errors.SchemaError as exc:
    print(exc)
Expected <class 'pandas.core.series.Series'> to have name 'idx', found 'None'

check_name 的默认值 None 对于列和多重索引转换为 True

自定义检查

与基于对象的API不同,自定义检查可以作为类方法来指定。

列/索引检查

import pandera as pa
from pandera.typing import Index, Series

class CustomCheckSchema(pa.DataFrameModel):

    a: Series[int] = pa.Field(gt=0, coerce=True)
    abc: Series[int]
    idx: Index[str]

    @pa.check("a", name="foobar")
    def custom_check(cls, a: Series[int]) -> Series[bool]:
        return a < 100

    @pa.check("^a", regex=True, name="foobar")
    def custom_check_regex(cls, a: Series[int]) -> Series[bool]:
        return a > 0

    @pa.check("idx")
    def check_idx(cls, idx: Index[int]) -> Series[bool]:
        return idx.str.contains("dog")

注意

  • 您可以提供Check类初始化器的关键字参数,以获得分组检查的灵活性。

  • pydantic 类似,classmethod() 装饰器在幕后被添加,如果被省略的话。

  • 如果你的静态类型检查器或 linter 报告问题,你仍然可能需要在 @classmethod 装饰器 之后 添加 check() 装饰器。

  • 由于 checks 是类方法,它们接收的第一个参数值是一个 DataFrameModel 子类,而不是模型的实例。

from typing import Dict

class GroupbyCheckSchema(pa.DataFrameModel):

    value: Series[int] = pa.Field(gt=0, coerce=True)
    group: Series[str] = pa.Field(isin=["A", "B"])

    @pa.check("value", groupby="group", regex=True, name="check_means")
    def check_groupby(cls, grouped_value: Dict[str, Series[int]]) -> bool:
        return grouped_value["A"].mean() < grouped_value["B"].mean()

df = pd.DataFrame({
    "value": [100, 110, 120, 10, 11, 12],
    "group": list("AAABBB"),
})

try:
    print(GroupbyCheckSchema.validate(df))
except pa.errors.SchemaError as exc:
    print(exc)
Column 'value' failed series or dataframe validator 1: <Check check_means>

数据框检查

您还可以定义数据框级别的检查,类似于 基于对象的 API,使用 dataframe_check() 装饰器:

import pandas as pd
import pandera as pa
from pandera.typing import Index, Series

class DataFrameCheckSchema(pa.DataFrameModel):

    col1: Series[int] = pa.Field(gt=0, coerce=True)
    col2: Series[float] = pa.Field(gt=0, coerce=True)
    col3: Series[float] = pa.Field(lt=0, coerce=True)

    @pa.dataframe_check
    def product_is_negative(cls, df: pd.DataFrame) -> Series[bool]:
        return df["col1"] * df["col2"] * df["col3"] < 0

df = pd.DataFrame({
    "col1": [1, 2, 3],
    "col2": [5, 6, 7],
    "col3": [-1, -2, -3],
})

DataFrameCheckSchema.validate(df)
列1 列2 列3
0 1 5.0 -1.0
1 2 6.0 -2.0
2 3 7.0 -3.0

继承

自定义检查是继承的,因此可以被子类重写。

import pandas as pd
import pandera as pa
from pandera.typing import Index, Series

class Parent(pa.DataFrameModel):

    a: Series[int] = pa.Field(coerce=True)

    @pa.check("a", name="foobar")
    def check_a(cls, a: Series[int]) -> Series[bool]:
        return a < 100


class Child(Parent):

    a: Series[int] = pa.Field(coerce=False)

    @pa.check("a", name="foobar")
    def check_a(cls, a: Series[int]) -> Series[bool]:
        return a > 100

is_a_coerce = Child.to_schema().columns["a"].coerce
print(f"coerce: {is_a_coerce}")
coerce: False
df = pd.DataFrame({"a": [1, 2, 3]})

try:
    Child.validate(df)
except pa.errors.SchemaError as exc:
    print(exc)
Column 'a' failed element-wise validator number 0: <Check foobar> failure cases: 1, 2, 3

别名

DataFrameModel 通过 Fieldalias 参数支持不合法的 python 变量名的列。

检查必须引用别名。

import pandera as pa
import pandas as pd

class Schema(pa.DataFrameModel):
    col_2020: pa.typing.Series[int] = pa.Field(alias=2020)
    idx: pa.typing.Index[int] = pa.Field(alias="_idx", check_name=True)

    @pa.check(2020)
    def int_column_lt_100(cls, series):
        return series < 100


df = pd.DataFrame({2020: [99]}, index=[0])
df.index.name = "_idx"

print(Schema.validate(df))
      2020
_idx      
0       99

(0.6.2新功能) 当使用类属性获取底层 pd.DataFrame 列名或索引级别名称时,将尊重 alias

print(Schema.col_2020)
2020

与上面的示例非常相似,您也可以在类的作用域内直接使用变量名,且它会尊重别名。

注意

要从类作用域访问变量,您需要将其设置为类属性,因此给它分配一个默认的 Field

import pandera as pa
import pandas as pd

class Schema(pa.DataFrameModel):
    a: pa.typing.Series[int] = pa.Field()
    col_2020: pa.typing.Series[int] = pa.Field(alias=2020)

    @pa.check(col_2020)
    def int_column_lt_100(cls, series):
        return series < 100

    @pa.check(a)
    def int_column_gt_100(cls, series):
        return series > 100


df = pd.DataFrame({2020: [99], "a": [101]})
print(Schema.validate(df))
   2020    a
0    99  101

定义后操作DataFrame模型

使用继承构建层叠模式的一个注意事项是,没有明确的方法让子类例如删除字段或更新字段,而不完全覆盖之前的设置。这是因为继承是严格的添加性。

DataFrameSchema 对象确实具有这些选项,如在 DataFrameSchema Transformations 中所描述的,您可以通过重写您的 DataFrame 模型的 to_schema() 方法来利用这些选项。

数据框模型在很大程度上只是 DataFrameSchema API 的一个代理;调用 validate() 将只是重定向到数据框架模式的 validate 返回的 to_schema 的验证方法。因此,在那里进行的任何模式更新都将干净地传播。

作为一个例子,以下类层次结构无法将字段 bcBaz 移动到基类中,而不完全混淆继承树。因此,我们可以 像这样摆脱它们:

import pandera as pa
import pandas as pd

class Foo(pa.DataFrameModel):
    a: pa.typing.Series[int]
    b: pa.typing.Series[int]

class Bar(pa.DataFrameModel):
    c: pa.typing.Series[int]
    d: pa.typing.Series[int]

class Baz(Foo, Bar):

    @classmethod
    def to_schema(cls) -> pa.DataFrameSchema:
        schema = super().to_schema()
        return schema.remove_columns(["b", "c"])

df = pd.DataFrame({"a": [99], "d": [101]})
print(Baz.validate(df))
    a    d
0  99  101

注意

以这种方式操作架构形状存在缺点:

  • 静态代码分析无法确定哪些字段已从类定义和继承层次中移除/更新。

  • 任何重写了 to_schema 的类的子类可能会遇到意想不到的行为 – 如果 Baz 的子类尝试再次定义字段 bc,它将在其 to_schema 调用中失去它,因为 Bazto_schema 始终会在任何子类的类体已完全组装之后执行。