DataFrame을 Validation pandera 시작하기
개시하다
파이톤을 사용하여 데이터 분석을 할 때 자주 사용하는 프로그램 라이브러리에pandas가 있습니다.
판다스는 매우 사용하기 좋은 프로그램 라이브러리이지만 데이터 전체를 저장하기 위해
pd.DataFrame
형은 원본 코드만 보면'어떤 열이 있나','어떤 열이 어떤 유형인가','열별 값이 어떤 값이 있을까'등을 모르는 경우가 많다.그 결과 처리 블랙박스화로 인해 디버깅 비용이 증가하고 코드의 가독성이 떨어지는 등 문제가 발생했다.
이 문제를 해결하는 방법 중 하나로서 본 글은 데이터 프레임 검증 기능을 제공하는 프로그램 라이브러리pandera를 소개한다.
이른바 판다라
데이터 처리 파이프의 가독성과 루팡성을 높이기 위해 데이터 검증 기능을 제공하는 프로그램 라이브러리.
주로 다음과 같은 기능을 제공합니다.(위의 문서에서 참조합니다.부분 발췌문)
개인적으로pydantic처럼 학급 기반의 API로 모델을 정의할 수 있다는 점은 고맙다고 생각합니다.
설치하다.
pip로 설치할 수 있습니다.
pip install pandera
사용법
DataFramSchema의 발리에 따르면
정부 강좌에서 발췌하다.
import pandas as pd
import pandera as pa
# バリデーション用のデータ
df = pd.DataFrame({
"column1": [1, 4, 0, 10, 9],
"column2": [-1.3, -1.4, -2.9, -10.1, -20.4],
"column3": ["value_1", "value_2", "value_3", "value_2", "value_1"],
})
# スキーマ定義
schema = pa.DataFrameSchema({
"column1": pa.Column(int, checks=pa.Check.le(10)),
"column2": pa.Column(float, checks=pa.Check.lt(-1.2)),
"column3": pa.Column(str, checks=[
pa.Check.str_startswith("value_"),
# series の入力を受け取り boolean か boolean 型の series を返すカスタムチェックメソッドを定義
pa.Check(lambda s: s.str.split("_", expand=True).shape[1] == 2)
]),
})
validated_df = schema(df)
print(validated_df)
column1 column2 column3
0 1 -1.3 value_1
1 4 -1.4 value_2
2 0 -2.9 value_3
3 10 -10.1 value_2
4 9 -20.4 value_1
는 사전에 모델을 정의하고 모델에 데이터 프레임을 입력한 후 검증된 데이터 프레임을 출력한다.SchemaModel 기반 검증
이제 Schema Model을 사용하는 방법을 살펴보겠습니다.이것도 교과서에서 발췌한 것이다.
from pandera.typing import Series
class Schema(pa.SchemaModel):
column1: Series[int] = pa.Field(le=10)
column2: Series[float] = pa.Field(lt=-1.2)
column3: Series[str] = pa.Field(str_startswith="value_")
@pa.check("column3")
def column_3_check(cls, series: Series[str]) -> Series[bool]:
"""Check that column3 values have two elements after being split with '_'"""
return series.str.split("_", expand=True).shape[1] == 2
Schema.validate(df)
맞춤형 검사 방법은 lambda가 아니라 캠코더를 끼는 방법으로 실시되었지만 쓰기 방법은 큰 차이가 없다.또한
pandera.Field
에서 얻은 주요 매개 변수는 다음과 같은 몇 가지가 있다.매개 변수
설명
nullable
빈 열 허용 여부
unique
열에 고유한 제한을 적용할지 여부
coerce
강제 유형
ignore_na
형식 검사 중null 무시 여부
eq
지정한 값과 같거나 같음
ge
지정된 값보다 큽니다.
gt
지정된 값보다 크거나 같음
le
보다 작음
lt
지정된 값보다 낮음
ne
요소가 있는지 없는지
in_range
지정된 최소값 또는 최대값 범위 내
isin
지정된 목록 범위 내
str_contains
지정한 문자열 포함 여부
str_startswith
지정한 문자열부터 시작하기
str_endswith
지정한 문자열로 끝냅니까
str_length
문자 길이에 지정된 최소값 또는 최대값 범위 내
실천적 사용 방법
사용법을 간단히 이해했더라도 타이타닉의 데이터 세트를 읽어 가공 처리해 봤다.
데이터 읽기
우선 패턴 정의를 하지 않는 상황을 고려한다.
import pandas as pd
def load_data(filepath: str) -> pd.DataFrame:
df = pd.read_csv(filepath)
return df
df = load_data("./train.csv")
파일에서 데이터를 읽는 것은 당연하지만 데이터 프레임의 내용이 어떤지 모른다.이어서 정의 모델의 상황을 고려한다.
from typing import Optional
import pandas as pd
import pandera as pa
from pandera.typing import Series, DataFrame
class TitanicSchema(pa.SchemaModel):
PassengerId: Series[int] = pa.Field(nullable=False, unique=True)
Survived: Optional[Series[int]] = pa.Field(nullable=True, isin=(0, 1))
Pclass: Series[int] = pa.Field(nullable=False, isin=(1, 2, 3))
Name: Series[str] = pa.Field(nullable=False)
Sex: Series[str] = pa.Field(nullable=False, isin=("male", "female"))
Age: Series[float] = pa.Field(nullable=True, in_range={"min_value": 0, "max_value": 100})
SibSp: Series[int] = pa.Field(nullable=False, ge=0, le=10)
Parch: Series[int] = pa.Field(nullable=False, ge=0, le=10)
Ticket: Series[str] = pa.Field(nullable=False)
Fare: Series[float] = pa.Field(nullable=True, ge=0)
Cabin: Series[str] = pa.Field(nullable=True)
Embarked: Series[str] = pa.Field(nullable=True, str_length=1, isin=("S", "C", "Q"))
class Config:
strict = True
def load_dataset(filepath:str) -> DataFrame[TitanicSchema]:
df = pd.read_csv(filepath)
df = TitanicSchema.validate(df)
return df
df = load_data("./train.csv")
데이터 집합이 가지고 있는 열과 각 열의 정보는 모델로 정의되기 때문에 데이터 프레임의 내용이 어느 정도 명확해졌다.또한 읽기 후 바로 검증을 실시하여 데이터 프레임이 모델로 정의된 내용을 만족시켰음을 확보했다.
보태다
위의 예에서
TitanicSchema
클래스의 구성원 변수에 Config
클래스를 정의하고 설정strict=True
.이렇게 하면
pa.SchemaModel
등록Config
류를 통해 원 정보를 정의할 수 있다.기본적으로
SchemaModel
에 등록된 열이 존재하지 않으면 검증에서 오류가 발생하지만 등록되지 않은 열이 있어도 오류가 발생하지 않습니다.등록된 열이 없는 경우에도 오류가 발생하도록 설정
strict=True
합니다.데이터 가공
다음은 데이터를 가공해 보자.가공 과정은 다음과 같은 notebook을 참고했다.
이 글에서 가공 처리 자체는 중요하지 않으니 걸어가면서 읽어도 괜찮습니다.
아까와 마찬가지로 패턴을 정의하지 않는 상황을 먼저 고려한다.
import numpy as np
import pandas as pd
def transform(df: pd.DataFrame) -> pd.DataFrame:
df["Sex"] = df["Sex"].str.match("male").map(int)
df["Title"] = df["Name"].str.extract(" ([A-Za-z]+)\.", expand=False)
df["Title"] = df["Title"].replace(
["Lady", "Countess", "Capt", "Col", "Don", "Dr", "Major", "Rev", "Sir", "Jonkheer", "Dona"], "Rare"
)
df["Title"] = df["Title"].replace("Mlle", "Miss")
df["Title"] = df["Title"].replace("Ms", "Miss")
df["Title"] = df["Title"].replace("Mme", "Mrs")
df["Title"] = df["Title"].map({"Mr": 1, "Miss": 2, "Mrs": 3, "Master": 4, "Rare": 5})
guess_ages = np.zeros((2, 3))
for i in range(2):
for j in range(3):
guess_df = df[(df["Sex"] == i) & (df["Pclass"] == j + 1)]["Age"].dropna()
age_guess = guess_df.median()
guess_ages[i, j] = int(age_guess / 0.5 + 0.5) * 0.5
for i in range(2):
for j in range(3):
df.loc[(df["Age"].isnull()) & (df["Sex"] == i) & (df["Pclass"] == j + 1), "Age"] = guess_ages[i, j]
df["Age"] = df["Age"].astype(int)
df.loc[df["Age"] <= 16, "Age"] = 0
df.loc[(df["Age"] > 16) & (df["Age"] <= 32), "Age"] = 1
df.loc[(df["Age"] > 32) & (df["Age"] <= 48), "Age"] = 2
df.loc[(df["Age"] > 48) & (df["Age"] <= 64), "Age"] = 3
df.loc[df["Age"] > 64, "Age"] = 4
df["FamilySize"] = df["SibSp"] + df["Parch"] + 1
df["IsAlone"] = df["FamilySize"].map(lambda x: 1 if x == 1 else 0)
df = df.drop(["Ticket", "Cabin", "PassengerId", "Name"], axis=1)
return df
df = load_data("./train.csv")
df = transform(df)
가공은 결손 보완, 합병, 유형 전환, 각 열 연산, 무용열 삭제 등 각종 처리를 포함한다.나는 이러한 처리 결과를 통해 최종적으로 얻을 수 있는 데이터 프레임이 어떤 상태로 변하는지 곧 이해하기 어렵다고 생각한다.
이어서 정의 모델의 상황을 고려한다.
class TransformedTitanicSchema(pa.SchemaModel):
Survived: Optional[Series[int]] = pa.Field(nullable=True, isin=(0, 1))
Pclass: Series[int] = pa.Field(nullable=False, isin=(1, 2, 3))
Sex: Series[int] = pa.Field(nullable=False, isin=(0, 1))
Age: Series[int] = pa.Field(nullable=False, isin=(0, 1, 2, 3, 4))
SibSp: Series[int] = pa.Field(nullable=False, ge=0, le=10)
Parch: Series[int] = pa.Field(nullable=False, ge=0, le=10)
Fare: Series[float] = pa.Field(nullable=True, ge=0)
Embarked: Series[str] = pa.Field(nullable=True, str_length=1, isin=("S", "C", "Q"))
Title: Series[int] = pa.Field(nullable=False, isin=(1, 2, 3, 4, 5))
FamilySize: Series[int] = pa.Field(nullable=False, ge=0, le=15)
IsAlone: Series[int] = pa.Field(nullable=False, isin=(0, 1))
class Config:
strict = True
def transform(df: DataFrame[TitanicSchema]) -> DataFrame[TransformedTitanicSchema]:
df["Sex"] = df["Sex"].str.match("male").map(int)
df["Title"] = df["Name"].str.extract(" ([A-Za-z]+)\.", expand=False)
df["Title"] = df["Title"].replace(
["Lady", "Countess", "Capt", "Col", "Don", "Dr", "Major", "Rev", "Sir", "Jonkheer", "Dona"], "Rare"
)
df["Title"] = df["Title"].replace("Mlle", "Miss")
df["Title"] = df["Title"].replace("Ms", "Miss")
df["Title"] = df["Title"].replace("Mme", "Mrs")
df["Title"] = df["Title"].map({"Mr": 1, "Miss": 2, "Mrs": 3, "Master": 4, "Rare": 5})
guess_ages = np.zeros((2, 3))
for i in range(2):
for j in range(3):
guess_df = df[(df["Sex"] == i) & (df["Pclass"] == j + 1)]["Age"].dropna()
age_guess = guess_df.median()
guess_ages[i, j] = int(age_guess / 0.5 + 0.5) * 0.5
for i in range(2):
for j in range(3):
df.loc[(df["Age"].isnull()) & (df["Sex"] == i) & (df["Pclass"] == j + 1), "Age"] = guess_ages[i, j]
df["Age"] = df["Age"].astype(int)
df.loc[df["Age"] <= 16, "Age"] = 0
df.loc[(df["Age"] > 16) & (df["Age"] <= 32), "Age"] = 1
df.loc[(df["Age"] > 32) & (df["Age"] <= 48), "Age"] = 2
df.loc[(df["Age"] > 48) & (df["Age"] <= 64), "Age"] = 3
df.loc[df["Age"] > 64, "Age"] = 4
df["FamilySize"] = df["SibSp"] + df["Parch"] + 1
df["IsAlone"] = df["FamilySize"].map(lambda x: 1 if x == 1 else 0)
df = df.drop(["Ticket", "Cabin", "PassengerId", "Name"], axis=1)
df = TransformedTitanicSchema.validate(df)
return df
df = load_data("./train.csv")
df = transform(df)
가공 처리에는 변화가 없지만 처리 내용을 완전히 이해하지 못하더라도 어느 정도 어떤 값이 나올지 알 수 있다.또한 가공 처리가 끝난 후 바로 데이터의 검증이 이루어졌기 때문에 가공 후 예상치 못한 값이 섞이지 않도록 보증할 수 있다.
읽기 및 가공
총괄은 아래와 같다.
또한 상기 원본 코드는 방법의 마지막 실행
SchemaModel.validate
에서 유형 확인을 했지만 매번 쓰기가 번거롭다.유형 알림이 있는 방법에 도매기
@pa.check_types
를 추가하면 자동으로 검증할 수 있다.import numpy as np
import pandas as pd
import pandera as pa
from typing import Optional
from pandera.typing import Series, DataFrame
class TitanicSchema(pa.SchemaModel):
PassengerId: Series[int] = pa.Field(nullable=False, unique=True)
Survived: Optional[Series[int]] = pa.Field(nullable=True, isin=(0, 1))
Pclass: Series[int] = pa.Field(nullable=False, isin=(1, 2, 3))
Name: Series[str] = pa.Field(nullable=False)
Sex: Series[str] = pa.Field(nullable=False, isin=("male", "female"))
Age: Series[float] = pa.Field(nullable=True, in_range={"min_value": 0, "max_value": 100})
SibSp: Series[int] = pa.Field(nullable=False, ge=0, le=10)
Parch: Series[int] = pa.Field(nullable=False, ge=0, le=10)
Ticket: Series[str] = pa.Field(nullable=False)
Fare: Series[float] = pa.Field(nullable=True, ge=0)
Cabin: Series[str] = pa.Field(nullable=True)
Embarked: Series[str] = pa.Field(nullable=True, str_length=1, isin=("S", "C", "Q"))
class Config:
strict = True
class TransformedTitanicSchema(pa.SchemaModel):
Survived: Optional[Series[int]] = pa.Field(nullable=True, isin=(0, 1))
Pclass: Series[int] = pa.Field(nullable=False, isin=(1, 2, 3))
Sex: Series[int] = pa.Field(nullable=False, isin=(0, 1))
Age: Series[int] = pa.Field(nullable=True, isin=(0, 1, 2, 3, 4))
SibSp: Series[int] = pa.Field(nullable=False, ge=0, le=10)
Parch: Series[int] = pa.Field(nullable=False, ge=0, le=10)
Fare: Series[float] = pa.Field(nullable=True, ge=0)
Embarked: Series[str] = pa.Field(nullable=True, str_length=1, isin=("S", "C", "Q"))
Title: Series[int] = pa.Field(nullable=False, isin=(1, 2, 3, 4, 5))
FamilySize: Series[int] = pa.Field(nullable=False, ge=0, le=15)
IsAlone: Series[int] = pa.Field(nullable=False, isin=(0, 1))
class Config:
strict = True
@pa.check_types
def load_dataset(filepath: str) -> DataFrame[TitanicSchema]:
df = pd.read_csv(filepath)
return df
@pa.check_types
def transform(df: DataFrame[TitanicSchema]) -> DataFrame[TransformedTitanicSchema]:
df["Sex"] = df["Sex"].str.match("male").map(int)
df["Title"] = df["Name"].str.extract(" ([A-Za-z]+)\.", expand=False)
df["Title"] = df["Title"].replace(
["Lady", "Countess", "Capt", "Col", "Don", "Dr", "Major", "Rev", "Sir", "Jonkheer", "Dona"], "Rare"
)
df["Title"] = df["Title"].replace("Mlle", "Miss")
df["Title"] = df["Title"].replace("Ms", "Miss")
df["Title"] = df["Title"].replace("Mme", "Mrs")
df["Title"] = df["Title"].map({"Mr": 1, "Miss": 2, "Mrs": 3, "Master": 4, "Rare": 5})
guess_ages = np.zeros((2, 3))
for i in range(2):
for j in range(3):
guess_df = df[(df["Sex"] == i) & (df["Pclass"] == j + 1)]["Age"].dropna()
age_guess = guess_df.median()
guess_ages[i, j] = int(age_guess / 0.5 + 0.5) * 0.5
for i in range(2):
for j in range(3):
df.loc[(df["Age"].isnull()) & (df["Sex"] == i) & (df["Pclass"] == j + 1), "Age"] = guess_ages[i, j]
df["Age"] = df["Age"].astype(int)
df.loc[df["Age"] <= 16, "Age"] = 0
df.loc[(df["Age"] > 16) & (df["Age"] <= 32), "Age"] = 1
df.loc[(df["Age"] > 32) & (df["Age"] <= 48), "Age"] = 2
df.loc[(df["Age"] > 48) & (df["Age"] <= 64), "Age"] = 3
df.loc[df["Age"] > 64, "Age"] = 4
df["FamilySize"] = df["SibSp"] + df["Parch"] + 1
df["IsAlone"] = df["FamilySize"].map(lambda x: 1 if x == 1 else 0)
df = df.drop(["Ticket", "Cabin", "PassengerId", "Name"], axis=1)
return df
def main():
df = load_dataset("./train.csv")
df = transform(df)
if __name__ == "__main__":
main()
끝맺다
본고는 데이터 프레임 검증 기능을 제공하는 프로그램 라이브러리pandera를 소개한다.
단순히 원본 코드의 분량을 보면 배 가까이 증가한다. 원가 없이 검증과 유형 검사를 할 수 없기 때문에 짧은 시간 안에 폐기되는 전제에서 원본 코드는 사용할 가치가 없을 수 있다.여러 사람이 장기적으로 유지보수해야 하는 원본 코드는 매우 효과적이라고 느낀다.
기계 학습 모델이 생산 환경에서 작동하는 것은 당연한 일이기 때문에 데이터클래스와pydantic 등 모델에 대한 정의, 도입 유형 알림을 개발하는 기업이 증가했다.데이터 프레임에 유형 힌트와 유형 검사를 추가하지 못해 곤란하다면pandera를 사용하는 것도 방법을 연구해 보고 싶습니다.
그때 기사에 도움이 된다면 영광입니다.끝까지 읽어주셔서 감사합니다.
참고 자료
창고.
문서
일본어 소개
Reference
이 문제에 관하여(DataFrame을 Validation pandera 시작하기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/anieca/articles/37157cf65d70ea텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)