

Bild vom Autor
# Einführung
Die Wartung von Datenvalidierungscode in Python ist oft mühsam. Geschäftsregeln werden verschachtelt if Anweisungen, Validierungslogik vermischt sich mit Fehlerbehandlung, und das Hinzufügen neuer Prüfungen bedeutet oft, prozedurale Funktionen zu durchsuchen, um die richtige Stelle zum Einfügen von Code zu finden. Ja, es gibt Datenvalidierungs-Frameworks, die Sie verwenden können, aber wir konzentrieren uns darauf, mit Python etwas ganz Einfaches, aber Nützliches zu erstellen.
Schreiben wir eine Artwork einfache Domänenspezifische Sprache (DSL), indem wir eine erstellen Vokabular speziell für die Datenvalidierung. Anstatt generischen Python-Code zu schreiben, erstellen Sie spezielle Funktionen und Klassen, die Validierungsregeln in Begriffen ausdrücken, die Ihrer Einstellung zum Downside entsprechen.
Für die Datenvalidierung bedeutet das Regeln, die sich wie Geschäftsanforderungen lesen: „Das Alter des Kunden muss zwischen 18 und 120 Jahren liegen“ oder „E-Mail-Adressen müssen ein @-Image enthalten und sollten eine gültige Area haben.“ Sie möchten, dass die DSL die Mechanismen der Datenprüfung und Meldung von Verstößen übernimmt, während Sie sich auf die Äußerung konzentrieren Was gültige Daten aussehen. Das Ergebnis ist eine Validierungslogik, die lesbar, leicht zu warten und zu testen sowie einfach zu erweitern ist. Additionally, fangen wir mit dem Codieren an!
🔗 Hyperlink zum Code auf GitHub
# Warum ein DSL aufbauen?
Erwägen Sie die Validierung von Kundendaten mit Python:
def validate_customers(df):
errors = ()
if df('customer_id').duplicated().any():
errors.append("Duplicate IDs")
if (df('age') < 0).any():
errors.append("Unfavorable ages")
if not df('e-mail').str.incorporates('@').all():
errors.append("Invalid emails")
return errors
Dieser Ansatz codiert die Validierungslogik fest, vermischt Geschäftsregeln mit Fehlerbehandlung und wird mit zunehmender Regelzahl nicht mehr wartbar. Stattdessen möchten wir eine DSL schreiben, die Bedenken trennt und wiederverwendbare Validierungskomponenten erstellt.
Anstatt prozedurale Validierungsfunktionen zu schreiben, können Sie mit einer DSL Regeln ausdrücken, die sich wie Geschäftsanforderungen lesen:
# Conventional strategy
if df('age').min() < 0 or df('age').max() > 120:
increase ValueError("Invalid ages discovered")
# DSL strategy
validator.add_rule(Rule("Legitimate ages", between('age', 0, 120), "Ages should be 0-120"))
Der DSL-Ansatz trennt, was Sie validieren (Geschäftsregeln) und wie mit Verstößen umgegangen wird (Fehlerberichterstattung).. Dies macht die Validierungslogik testbar, wiederverwendbar und für Nicht-Programmierer lesbar.
# Erstellen eines Beispieldatensatzes
Erstellen Sie zunächst beispielhafte, realistische E-Commerce-Kundendaten mit häufigen Qualitätsproblemen:
import pandas as pd
prospects = pd.DataFrame({
'customer_id': (101, 102, 103, 103, 105),
'e-mail': ('john@gmail.com', 'invalid-email', '', 'sarah@yahoo.com', 'mike@area.co'),
'age': (25, -5, 35, 200, 28),
'total_spent': (250.50, 1200.00, 0.00, -50.00, 899.99),
'join_date': ('2023-01-15', '2023-13-45', '2023-02-20', '2023-02-20', '')
}) # Observe: 2023-13-45 is an deliberately malformed date.
Dieser Datensatz weist doppelte Kunden-IDs, ungültige E-Mail-Formate, unmögliche Altersangaben, adverse Ausgabenbeträge und fehlerhafte Datumsangaben auf. Das sollte zum Testen von Validierungsregeln ziemlich intestine funktionieren.
# Schreiben der Validierungslogik
// Erstellen der Regelklasse
Beginnen wir mit dem Schreiben einer einfachen Rule Klasse, die die Validierungslogik umschließt:
class Rule:
def __init__(self, identify, situation, error_msg):
self.identify = identify
self.situation = situation
self.error_msg = error_msg
def examine(self, df):
# The situation operate returns True for VALID rows.
# We use ~ (bitwise NOT) to pick out the rows that VIOLATE the situation.
violations = df(~self.situation(df))
if not violations.empty:
return {
'rule': self.identify,
'message': self.error_msg,
'violations': len(violations),
'sample_rows': violations.head(3).index.tolist()
}
return None
Der situation Der Parameter akzeptiert jede Funktion, die einen DataFrame annimmt und einen booleschen Wert zurückgibt Sequence Gibt gültige Zeilen an. Der Tilde-Operator (~) invertiert diesen Booleschen Wert Sequence Verstöße zu erkennen. Bei Verstößen wird die examine Die Methode gibt detaillierte Informationen zurück, einschließlich Regelname, Fehlermeldung, Anzahl der Verstöße und Beispielzeilenindizes für das Debuggen.
Dieses Design trennt die Validierungslogik von der Fehlerberichterstattung. Der situation Die Funktion konzentriert sich ausschließlich auf die Geschäftsregel, während die Rule Die Klasse behandelt Fehlerdetails konsistent.
// Mehrere Regeln hinzufügen
Als nächstes programmieren wir a DataValidator Klasse, die Regelsammlungen verwaltet:
class DataValidator:
def __init__(self):
self.guidelines = ()
def add_rule(self, rule):
self.guidelines.append(rule)
return self # Allows methodology chaining
def validate(self, df):
outcomes = ()
for rule in self.guidelines:
violation = rule.examine(df)
if violation:
outcomes.append(violation)
return outcomes
Der add_rule Methode gibt zurück self um die Methodenverkettung zu ermöglichen. Der validate Die Methode führt alle Regeln unabhängig aus und sammelt Verstoßberichte. Dieser Ansatz stellt sicher, dass eine fehlerhafte Regel nicht die Ausführung anderer verhindert.
// Erstellen lesbarer Bedingungen
Denken Sie daran, dass beim Instanziieren eines Objekts der Rule Klasse, wir brauchen auch eine situation Funktion. Dies kann jede Funktion sein, die einen DataFrame aufnimmt und einen Booleschen Wert zurückgibt Sequence. Obwohl einfache Lambda-Funktionen funktionieren, sind sie nicht sehr einfach zu lesen. Schreiben wir additionally Hilfsfunktionen, um ein lesbares Validierungsvokabular zu erstellen:
def not_null(column):
return lambda df: df(column).notna()
def unique_values(column):
return lambda df: ~df.duplicated(subset=(column), maintain=False)
def between(column, min_val, max_val):
return lambda df: df(column).between(min_val, max_val)
Jede Hilfsfunktion gibt ein Lambda zurück, das mit booleschen Pandas-Operationen funktioniert.
- Der
not_nullHelfer nutzt Pandasnotna()Methode zum Identifizieren von Nicht-Null-Werten. - Der
unique_valuesHelfer verwendetduplicated(..., maintain=False)mit einem Teilmengenparameter, um alle doppelten Vorkommen zu kennzeichnen und so eine genauere Anzahl von Verstößen zu gewährleisten. - Der
betweenHelfer nutzt die Pandasbetween()Methode, die Bereichsprüfungen automatisch durchführt.
Für den Mustervergleich reguläre Ausdrücke unkompliziert werden:
import re
def matches_pattern(column, sample):
return lambda df: df(column).str.match(sample, na=False)
Der na=False Der Parameter stellt sicher, dass fehlende Werte als Validierungsfehler und nicht als Übereinstimmungen behandelt werden, was normalerweise das gewünschte Verhalten für erforderliche Felder ist.
# Erstellen eines Datenvalidators für den Beispieldatensatz
Lassen Sie uns nun einen Validator für den Kundendatensatz erstellen, um zu sehen, wie dieser DSL funktioniert:
validator = DataValidator()
validator.add_rule(Rule(
"Distinctive buyer IDs",
unique_values('customer_id'),
"Buyer IDs should be distinctive throughout all data"
))
validator.add_rule(Rule(
"Legitimate e-mail format",
matches_pattern('e-mail', r'^(^@s)+@(^@s)+.(^@s)+$'),
"E mail addresses should include @ image and area"
))
validator.add_rule(Rule(
"Cheap buyer age",
between('age', 13, 120),
"Buyer age should be between 13 and 120 years"
))
validator.add_rule(Rule(
"Non-negative spending",
lambda df: df('total_spent') >= 0,
"Whole spending quantity can't be adverse"
))
Jede Regel folgt demselben Muster: ein beschreibender Title, eine Validierungsbedingung und eine Fehlermeldung.
- Die erste Regel verwendet die
unique_valuesHilfsfunktion zur Prüfung auf doppelte Kunden-IDs. - Die zweite Regel wendet den Mustervergleich mit regulären Ausdrücken an, um E-Mail-Formate zu validieren. Das Muster erfordert mindestens ein Zeichen vor und nach dem @-Image sowie eine Domänenerweiterung.
- Die dritte Regel verwendet die
betweenHelfer zur Bereichsvalidierung, Festlegung angemessener Altersgrenzen für Kunden. - Die letzte Regel verwendet eine Lambda-Funktion für eine Inline-Bedingung, die dies überprüft
total_spentWerte sind nicht negativ.
Beachten Sie, dass sich jede Regel quick wie eine Geschäftsanforderung liest. Der Validator sammelt diese Regeln und kann sie alle für jeden DataFrame mit passenden Spaltennamen ausführen:
points = validator.validate(prospects)
for problem in points:
print(f"❌ Rule: {problem('rule')}")
print(f"Downside: {problem('message')}")
print(f"Affected rows: {problem('sample_rows')}")
print()
Die Ausgabe identifiziert eindeutig spezifische Probleme und ihre Positionen im Datensatz, was das Debuggen vereinfacht. Für die Beispieldaten erhalten Sie die folgende Ausgabe:
Validation Outcomes:
❌ Rule: Distinctive buyer IDs
Downside: Buyer IDs should be distinctive throughout all data
Violations: 2
Affected rows: (2, 3)
❌ Rule: Legitimate e-mail format
Downside: E mail addresses should include @ image and area
Violations: 3
Affected rows: (1, 2, 4)
❌ Rule: Cheap buyer age
Downside: Buyer age should be between 13 and 120 years
Violations: 2
Affected rows: (1, 3)
❌ Rule: Non-negative spending
Downside: Whole spending quantity can't be adverse
Violations: 1
Affected rows: (3)
# Hinzufügen spaltenübergreifender Validierungen
Echte Geschäftsregeln beinhalten häufig Beziehungen zwischen Spalten. Benutzerdefinierte Lambda-Funktionen verarbeiten komplexe Validierungslogik:
def high_spender_email_required(df):
high_spenders = df('total_spent') > 500
has_valid_email = df('e-mail').str.incorporates('@', na=False)
# Passes if: (Not a excessive spender) OR (Has a sound e-mail)
return ~high_spenders | has_valid_email
validator.add_rule(Rule(
"Excessive Spenders Want Legitimate E mail",
high_spender_email_required,
"Prospects spending over $500 should have legitimate e-mail addresses"
))
Diese Regel verwendet eine boolesche Logik, bei der Kunden mit hohen Ausgaben über gültige E-Mail-Adressen verfügen müssen, Kunden mit geringen Ausgaben jedoch möglicherweise über fehlende Kontaktinformationen verfügen. Der Ausdruck ~high_spenders | has_valid_email bedeutet „kein Viel-Ausgaber ODER hat eine gültige E-Mail-Adresse“, was es Niedrig-Ausgabern ermöglicht, die Validierung unabhängig vom E-Mail-Standing zu bestehen.
# Handhabung der Datumsvalidierung
Die Datumsvalidierung erfordert eine sorgfältige Handhabung, da die Datumsanalyse fehlschlagen kann:
def valid_date_format(column, date_format="%Y-%m-%d"):
def check_dates(df):
# pd.to_datetime with errors="coerce" turns invalid dates into NaT (Not a Time)
parsed_dates = pd.to_datetime(df(column), format=date_format, errors="coerce")
# A row is legitimate if the unique worth isn't null AND the parsed date isn't NaT
return df(column).notna() & parsed_dates.notna()
return check_dates
validator.add_rule(Rule(
"Legitimate Be a part of Dates",
valid_date_format('join_date'),
"Be a part of dates should observe YYYY-MM-DD format"
))
Die Validierung ist nur erfolgreich, wenn der ursprüngliche Wert nicht null ist UND das analysierte Datum gültig ist (d. h. nicht). NaT). Wir entfernen das Unnötige try-except blockieren, sich darauf verlassen errors="coerce" In pd.to_datetime um fehlerhafte Zeichenfolgen elegant zu behandeln, indem sie in konvertiert werden NaTdie dann von gefangen wird parsed_dates.notna().
# Schreiben von Decorator-Integrationsmustern
Für Produktionspipelines können Sie Dekoratormuster schreiben, die eine saubere Integration ermöglichen:
def validate_dataframe(validator):
def decorator(func):
def wrapper(df, *args, **kwargs):
points = validator.validate(df)
if points:
error_details = (f"{problem('rule')}: {problem('violations')} violations" for problem in points)
increase ValueError(f"Knowledge validation failed: {'; '.be part of(error_details)}")
return func(df, *args, **kwargs)
return wrapper
return decorator
# Observe: 'customer_validator' must be outlined globally or handed in an actual implementation
# Assuming 'customer_validator' is the occasion we constructed earlier
# @validate_dataframe(customer_validator)
def process_customer_data(df):
return df.groupby('age').agg({'total_spent': 'sum'})
Dieser Dekorator stellt sicher, dass die Daten die Validierung bestehen, bevor die Verarbeitung beginnt, und verhindert so, dass sich beschädigte Daten durch die Pipeline verbreiten. Der Dekorateur löst beschreibende Fehler aus, die bestimmte Validierungsfehler umfassen. Um dies zu vermerken, wurde dem Codeausschnitt ein Kommentar hinzugefügt customer_validator müsste für den Dekorateur zugänglich sein.
# Das Muster erweitern
Sie können die DSL bei Bedarf um weitere Validierungsregeln erweitern:
# Statistical outlier detection
def within_standard_deviations(column, std_devs=3):
# Legitimate if absolute distinction from imply is inside N customary deviations
return lambda df: abs(df(column) - df(column).imply()) <= std_devs * df(column).std()
# Referential integrity throughout datasets
def foreign_key_exists(column, reference_df, reference_column):
# Legitimate if worth in column is current within the reference_column of the reference_df
return lambda df: df(column).isin(reference_df(reference_column))
# Customized enterprise logic
def profit_margin_reasonable(df):
# Ensures 0 <= margin <= 1
margin = (df('income') - df('value')) / df('income')
return (margin >= 0) & (margin <= 1)
Auf diese Weise können Sie Validierungslogik als zusammensetzbare Funktionen erstellen, die boolesche Reihen zurückgeben.
Hier ist ein Beispiel dafür, wie Sie die Datenvalidierungs-DSL verwenden können, die wir anhand der Beispieldaten erstellt haben, vorausgesetzt, die Hilfsfunktionen befinden sich in einem aufgerufenen Modul data_quality_dsl:
import pandas as pd
from data_quality_dsl import DataValidator, Rule, unique_values, between, matches_pattern
# Pattern information
df = pd.DataFrame({
'user_id': (1, 2, 2, 3),
'e-mail': ('consumer@take a look at.com', 'invalid', 'consumer@actual.com', ''),
'age': (25, -5, 30, 150)
})
# Construct validator
validator = DataValidator()
validator.add_rule(Rule("Distinctive customers", unique_values('user_id'), "Person IDs should be distinctive"))
validator.add_rule(Rule("Legitimate emails", matches_pattern('e-mail', r'^(^@)+@(^@)+.(^@)+$'), "Invalid e-mail format"))
validator.add_rule(Rule("Cheap ages", between('age', 0, 120), "Age should be 0-120"))
# Run validation
points = validator.validate(df)
for problem in points:
print(f"❌ {problem('rule')}: {problem('violations')} violations")
# Abschluss
Dieses DSL ist zwar einfach, funktioniert aber, weil es mit der Denkweise von Datenexperten über Validierung übereinstimmt. Regeln drücken Geschäftslogik in leicht verständlichen Anforderungen aus und ermöglichen uns gleichzeitig, Pandas sowohl für Leistung als auch für Flexibilität zu verwenden.
Die Trennung der Belange macht die Validierungslogik testbar und wartbar. Dieser Ansatz erfordert keine externen Abhängigkeiten über Pandas hinaus und führt zu keinem Lernaufwand für diejenigen, die bereits mit Pandas-Operationen vertraut sind.
Daran habe ich ein paar abendliche Coding-Sprints und mehrere Tassen Kaffee (natürlich!) gearbeitet. Aber Sie können diese Model als Ausgangspunkt verwenden und etwas viel Cooleres bauen. Viel Spaß beim Codieren!
Bala Priya C ist ein Entwickler und technischer Redakteur aus Indien. Sie arbeitet gerne an der Schnittstelle von Mathematik, Programmierung, Datenwissenschaft und Inhaltserstellung. Zu ihren Interessen- und Fachgebieten gehören DevOps, Datenwissenschaft und Verarbeitung natürlicher Sprache. Sie liebt es zu lesen, zu schreiben, zu programmieren und Kaffee zu trinken! Derzeit arbeitet sie daran, zu lernen und ihr Wissen mit der Entwickler-Neighborhood zu teilen, indem sie Tutorials, Anleitungen, Meinungsbeiträge und mehr verfasst. Bala erstellt außerdem ansprechende Ressourcenübersichten und Programmier-Tutorials.
