pandas / Advanced: ownership, windows and analytical acceptance / Lesson 17
Reproducible pipelines and acceptance tests
Connect explicit transformation contracts to independent expected results, metadata checks and reproducible source evidence.
On this page
Lesson goals
- Design small transformations with clear input/output and mutation behavior.
- Use value, dtype, index and coverage checks appropriate to the analytical claim.
Make every transformation’s responsibility visible
A reproducible pipeline names source version, accepted schema, row grain, key policy, units and the order of transformations. Keep functions focused on a meaningful operation such as validation, a line-total calculation or a named summary. State whether each returns a new frame or mutates its input. A pipe chain improves readability only when those contracts remain understandable; it does not make hidden global state or arbitrary side effects reproducible. Avoid depending on a notebook cell having run earlier. A historical rerun should select the same source contract and calculation version, or explicitly document why the accepted result changed.
Compare a complete declared result
The illustrative pandas pipeline calculates line totals, sorts by item and resets only the presentation index. assert_frame_equal compares the complete expected DataFrame, with dtype and exact integer comparison enabled. The expected table is constructed independently from the stated two-item result rather than by repeating the transformation. Check column order, index names, categorical metadata and timezone information when they belong to the contract. Relaxing checks to silence a failure can hide a meaningful schema change. This program is not executed because pandas is absent; its assertions document the intended engine acceptance test without claiming it passed.
import pandas as pd
from pandas.testing import assert_frame_equal
def calculate(frame):
return frame.assign(line_total=lambda value: value['units'] * value['price']).sort_values('item').reset_index(drop=True)
source = pd.DataFrame({'item': ['I2', 'I1'], 'units': [3, 2], 'price': [7, 5]})
result = source.pipe(calculate)
expected = pd.DataFrame({'item': ['I1', 'I2'], 'units': [2, 3], 'price': [5, 7], 'line_total': [10, 21]})
assert_frame_equal(result, expected, check_dtype=True, check_exact=True)
print(result['line_total'].tolist())
[10, 21]
Expected line totals are[10,21] with the declared schema/order; assert_frame_equal was not executed.
Choose tolerances and ordering from meaning
Exact comparison suits many integer identities and counts, while floating calculations can need justified relative and absolute tolerances. A convenient tolerance is not automatically acceptable measurement error. Likewise, ignoring row order is valid only when the result is semantically unordered and identity correspondence is verified. check_like can affect comparison of axes but should not conceal duplicate labels or missing entities. Test missing values and dtype changes deliberately rather than converting everything to strings for comparison. A visually identical CSV can lose timezone or category information, so compare the structured result before serialization when those properties matter.
Check a source-preserving transformation independently
The standard-library model computes and sorts plain records while keeping a snapshot of the input dictionaries. It verifies both expected item order and unchanged input values. This establishes a small functional contract without emulating DataFrame equality, CoW or dtype behavior. Include an invalid unit, duplicate identity, unmatched reference and boundary timestamp in a broader acceptance suite only when relevant to the transformation. Prefer tests that expose a real plausible defect over many assertions restating the implementation. Keep synthetic fixtures small enough that a reviewer can derive the expected result without running the same code again.
source = [{'item': 'I2', 'units': 3, 'price': 7}, {'item': 'I1', 'units': 2, 'price': 5}]
snapshot = [dict(row) for row in source]
result = sorted([{**row, 'line_total': row['units'] * row['price']} for row in source], key=lambda row: row['item'])
assert source == snapshot
assert [row['line_total'] for row in result] == [10, 21]
print([row['item'] for row in result])
['I1', 'I2']
Plain-record transformation preserves input and expected order; it does not test pandas equality or CoW.
Report the exact scope that was exercised
An acceptance record should include fixture identity, expected result, observed result, interpreter/library versions and untested boundaries. Syntax parsing proves code is structurally valid Python, not that pandas imports or its semantics match the expectation. The course executes only standard-library models and parsers; every pandas program remains runnable:false. No project dependency, optional engine, real dataset or external service is installed or used. After a code change, rerun the checks tied to the changed claim and preserve evidence rather than broadening a narrow success into a full analytical certification. Reproducibility makes a result reviewable; validity still depends on the business contract.
Common errors and fixes
A test compares the result only with another call to the same implementation.
- Why it happens
- There is no independent expected invariant.
- How to fix it
- Derive a small expected table from the business contract.
- Prevent it next time
- Choose fixtures whose result can be reasoned about manually.
Dtype and index checks are disabled merely to make a test pass.
- Why it happens
- A meaningful contract difference was treated as formatting noise.
- How to fix it
- Determine whether the difference is allowed and normalize only by a documented rule.
- Prevent it next time
- State comparison tolerances and metadata requirements explicitly.
A syntax-only check is reported as a successful pandas execution.
- Why it happens
- Static and runtime evidence were conflated.
- How to fix it
- Report exact checks and retain unexecuted engine boundaries.
- Prevent it next time
- Separate model, parser and pandas-runtime validation records.
Check your understanding
1. Why is converting both frames to strings a weak general equality test?
Show worked solution
It can hide dtype, missing-sentinel, timezone and category differences that affect later operations. Compare structured values and required metadata with a suitable pandas testing API in an authorized compatible runtime. If a formatting-only comparison is intended, state that narrow claim rather than calling it analytical equivalence.
2. Does a reproducible result automatically answer the correct business question?
Show worked solution
No. A deterministic pipeline can consistently use the wrong grain, denominator or time interval. Reproducibility supports audit and comparison, while validity requires independently stated business invariants. Keep both source/version evidence and population/measure acceptance checks so a stable wrong answer is not mistaken for a trustworthy one.
Key takeaways
- Independent expected values and metadata make acceptance meaningful.
- Reproducibility and analytical validity require different evidence.