Python SDK
Diff Models
SDK Reference: dataset diff result objects
fs.diff() profiles two snapshots and compares them, returning one frozen, serializable DatasetDiffResult — the equivalent of a git diff for structured datasets. All models below live in featuresmith.diff.schema.
DatasetDiffResult
@dataclass(frozen=True, slots=True)class DatasetDiffResult: version: str # "0.2.0" schema: SchemaDiff structure: StructureDiff missing_values: tuple[MissingValueDiff, ...] duplicates: DuplicateDiff constant_columns: ConstantColumnDiff cardinality: tuple[CardinalityDiff, ...] statistics: tuple[StatisticDiff, ...] distributions: tuple[DistributionDiff, ...] leakage: LeakageDiff | None # None when no target column is given summary: DatasetDiffSummary overall_summary: str def to_dict(self) -> dict[str, Any]: ...DiffConfig
Tunable thresholds for the diff engine:
@dataclass(frozen=True, slots=True)class DiffConfig: distribution_shift_threshold: float = 0.10 missing_change_threshold: float = 1.0 duplicate_change_threshold: float = 1.0 numeric_tolerance: float = 1e-9Schema-Level Deltas
@dataclass(frozen=True, slots=True)class ColumnRename: previous_name: str name: str@dataclass(frozen=True, slots=True)class ColumnTypeChange: column: str previous_dtype: str dtype: str previous_logical_type: str logical_type: str@dataclass(frozen=True, slots=True)class SchemaDiff: added_columns: tuple[str, ...] = () removed_columns: tuple[str, ...] = () renamed_columns: tuple[ColumnRename, ...] = () type_changes: tuple[ColumnTypeChange, ...] = () @property def changed(self) -> bool: ...added_columns and removed_columns are plain column names.
StructureDiff
@dataclass(frozen=True, slots=True)class StructureDiff: previous_row_count: int row_count: int previous_column_count: int column_count: int @property def rows_added(self) -> int: ... # never negative @property def rows_removed(self) -> int: ... @property def columns_added(self) -> int: ... @property def columns_removed(self) -> int: ...Quality Deltas
@dataclass(frozen=True, slots=True)class MissingValueDiff: column: str previous_missing_count: int missing_count: int previous_missing_percentage: float missing_percentage: float @property def delta_count(self) -> int: ... @property def delta_percentage(self) -> float: ... # status: "new" | "resolved" | "regressed" | "improved" | "unchanged" @property def status(self) -> str: ...@dataclass(frozen=True, slots=True)class DuplicateDiff: previous_duplicate_count: int duplicate_count: int previous_duplicate_percentage: float duplicate_percentage: float @property def delta_percentage(self) -> float: ... # status: "regressed" | "improved" | "unchanged" @property def status(self) -> str: ...@dataclass(frozen=True, slots=True)class ConstantColumnDiff: newly_constant: tuple[str, ...] = () no_longer_constant: tuple[str, ...] = () @property def changed(self) -> bool: ...MissingValueDiff.status reports "new" when missingness was introduced, "resolved" when it disappeared, "regressed"/"improved" when it increased/decreased, and "unchanged" otherwise.
Numeric Deltas
@dataclass(frozen=True, slots=True)class CardinalityDiff: column: str previous_cardinality: int cardinality: int @property def delta(self) -> int: ...@dataclass(frozen=True, slots=True)class StatisticDiff: column: str statistic: str # "mean" | "median" | "std_dev" | "minimum" | "maximum" previous: float | None current: float | None delta: float | None relative_delta: float | None@dataclass(frozen=True, slots=True)class DistributionDiff: column: str previous_mean: float | None mean: float | None mean_relative_shift: float | None significant: boolOnly statistics that actually changed are emitted, and only distribution shifts that exceed the configured threshold are flagged.
Leakage Deltas
@dataclass(frozen=True, slots=True)class LeakageColumnDiff: column: str previous_severity: str | None severity: str | None status: str # "new" | "removed" | "escalated" | "de_escalated" | "unchanged" @property def changed(self) -> bool: ...@dataclass(frozen=True, slots=True)class LeakageDiff: columns: tuple[LeakageColumnDiff, ...] = () @property def new_findings(self) -> tuple[LeakageColumnDiff, ...]: ... @property def removed_findings(self) -> tuple[LeakageColumnDiff, ...]: ... @property def escalated(self) -> tuple[LeakageColumnDiff, ...]: ... @property def de_escalated(self) -> tuple[LeakageColumnDiff, ...]: ... @property def changed(self) -> bool: ...The leakage comparison is only produced when a target column is provided to fs.diff().
DatasetDiffSummary
@dataclass(frozen=True, slots=True)class DatasetDiffSummary: rows_added: int rows_removed: int columns_added: int columns_removed: int columns_renamed: int type_changes: int schema_changed: bool missing_values_increased: int missing_values_decreased: int duplicate_rows_increased: bool duplicate_rows_decreased: bool newly_constant_columns: int no_longer_constant_columns: int leakage_new: int leakage_removed: int leakage_escalated: int leakage_de_escalated: int overall_health: str # "regressed" | "improved" | "unchanged" recommendation: strExample
import featuresmith as fsresult = fs.diff("v1.csv", "v2.csv", target_column="churn")print(result.overall_summary)print(f"Health: {result.summary.overall_health}")for diff in result.missing_values: print(f" {diff.column}: {diff.status} ({diff.delta_percentage:+.2f}pp)")