PandasSeniorExperience
Как проверять корректность решения на Pandas, а не только успешное выполнение кода?
Корректность в Pandas проверяют через явные assertions на dtype и shape, проверку бизнес-инвариантов (нет дублей, нет null в ключах, диапазоны значений), conservation checks агрегатов и тесты с pd.testing.assert_frame_equal на известных входах.
Стратегия проверки корректности, а не только успешного запуска
Код на Pandas выполняется без ошибок, но возвращает неверный результат — это самый опасный класс багов в data-пайплайнах. Проверка корректности требует явных assertions на форму, типы, диапазоны значений и семантику данных.
1. Проверка формы и типов
import pandas as pd
def validate_schema(df: pd.DataFrame) -> None:
expected_cols = {"user_id", "date", "amount", "region"}
assert set(df.columns) >= expected_cols, f"Missing columns: {expected_cols - set(df.columns)}"
assert df["user_id"].dtype == "Int64", f"user_id dtype: {df['user_id'].dtype}"
assert pd.api.types.is_datetime64_any_dtype(df["date"]), "date not datetime"
assert df["amount"].dtype in ("float64", "Float64"), f"amount dtype: {df['amount'].dtype}"
2. Проверка бизнес-инвариантов
def validate_business_rules(df: pd.DataFrame) -> None:
# Нет отрицательных сумм
neg = df[df["amount"] < 0]
assert len(neg) == 0, f"Negative amounts: {len(neg)} rows\n{neg.head()}"
# Уникальность первичного ключа
dupes = df[df.duplicated(subset=["user_id", "date"], keep=False)]
assert len(dupes) == 0, f"Duplicate (user_id, date): {len(dupes)} rows"
# Нет пропусков в критических колонках
nulls = df[["user_id", "date", "amount"]].isnull().sum()
assert nulls.sum() == 0, f"Nulls found:\n{nulls[nulls > 0]}"
# Диапазон дат осмыслен
assert df["date"].min() >= pd.Timestamp("2020-01-01"), "Dates too old"
assert df["date"].max() <= pd.Timestamp.now(), "Future dates found"
3. Контрольные суммы и агрегаты
Если результат — агрегированная таблица, проверяют, что сумма по группам равна сумме по исходному датасету (conservation check).
def validate_aggregation(raw: pd.DataFrame, agg: pd.DataFrame) -> None:
raw_total = raw["amount"].sum()
agg_total = agg["revenue"].sum()
assert abs(raw_total - agg_total) < 0.01, (
f"Aggregation error: raw={raw_total:.2f}, agg={agg_total:.2f}"
)
# Количество уникальных регионов не должно увеличиться
assert agg["region"].nunique() <= raw["region"].nunique()
4. Тесты с pytest и известными входами
import pytest
def transform(df: pd.DataFrame) -> pd.DataFrame:
return (
df.groupby("region", as_index=False)
.agg(revenue=("amount", "sum"))
)
def test_transform_known_input():
inp = pd.DataFrame({
"region": ["EU", "EU", "US"],
"amount": [100.0, 50.0, 200.0],
})
out = transform(inp)
pd.testing.assert_frame_equal(
out.sort_values("region").reset_index(drop=True),
pd.DataFrame({"region": ["EU", "US"], "revenue": [150.0, 200.0]}),
)
pd.testing.assert_frame_equal проверяет значения, dtype и индекс — используйте его вместо ручного сравнения через ==.
5. Профилирование данных перед обработкой
# Быстрый профиль перед запуском пайплайна
def quick_profile(df: pd.DataFrame) -> None:
print(f"Shape: {df.shape}")
print(f"Dtypes:\n{df.dtypes}")
print(f"Nulls:\n{df.isnull().sum()}")
print(f"Duplicates: {df.duplicated().sum()}")
for col in df.select_dtypes(include="number").columns:
print(f"{col}: min={df[col].min()}, max={df[col].max()}, mean={df[col].mean():.2f}")
Подводные камни
df1 == df2возвращает DataFrame булевых значений, а не одно True/False; для сравнения используйтеpd.testing.assert_frame_equal- NaN != NaN в Python — прямое сравнение через
==пропускает NaN-строки; нуженdf.isnull().sum() - После merge без
validate=строки могут дублироваться — silent data multiplication без ошибки - apply() с side effects (запись в внешний список) даёт непредсказуемое количество вызовов при оптимизации
- Проверять только shape недостаточно — правильная форма при неверном содержимом типична для join на неправильном ключе
- Не проверять порядок строк при сравнении агрегатов — sort перед assert_frame_equal обязателен
- Игнорировать предупреждения dtype при чтении CSV — они сигнализируют о потенциальной потере данных
What hurts your answer
- Сразу обвинять Pandas, не проверив соседние слои системы
- Чинить симптом без минимального воспроизведения и evidence
- Не учитывать версии, конфигурацию, окружение и recent changes
What they're listening for
- Умеет локализовать проблему вокруг Pandas
- Двигается от симптома к гипотезам и проверкам
- Отличает баг инструмента от ошибки использования или окружения