数据框模型¶
0.5.0 中的新功能
pandera 提供了一个类基础的 API,深受
pydantic 的启发。与
基于对象的 API 相比,您可以以与定义 pydantic 模型非常相似的方式定义数据框模型。
DataFrameModel 使用标准的 typing 语法,通过 pandera.typing 模块进行注解。模型可以显式转换为 DataFrameSchema,或直接用于验证 DataFrame。
基本用法¶
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 适用于 Column 和 Index 对象,通过关键字参数暴露内置的 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 支持的数据类型都可以用作
Series 和 Index 的类型参数。但是,有几个注意事项。
重要
您可以了解更多关于数据类型验证如何工作的内容 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开始:
数据种类 |
数据类型 |
参数 |
|---|---|---|
时区感知的日期时间 |
|
|
分类 |
|
|
周期 |
|
|
稀疏 |
|
|
区间 |
|
|
注解¶
参数可以通过 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.Annotated 和 dtype_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")
注意
与
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 通过 Field 的 alias 参数支持不合法的 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 的验证方法。因此,在那里进行的任何模式更新都将干净地传播。
作为一个例子,以下类层次结构无法将字段 b 和 c 从
Baz 移动到基类中,而不完全混淆继承树。因此,我们可以
像这样摆脱它们:
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的子类尝试再次定义字段b或c,它将在其to_schema调用中失去它,因为Baz的to_schema始终会在任何子类的类体已完全组装之后执行。