Comparing Hotel and Flight Offer DataFrames in Pandas
A practical guide to aligned snapshots, readable differences, and useful summary statistics.
Use DataFrame.compare() to locate changed values between two identically labeled offer tables. Compare corresponding snapshots of hotel offers or flight offers—not unrelated records—and use describe() separately for distribution summaries. The essential distinction is that pandas can report cell-level differences, but matching the right offers must come first. [1], [2]
In this article
- 1. Start with comparable offers, not just similar-looking tables
- 2. A worked example: isolate a price change and a term change
- 3. Choose the comparison view that answers your question
- 4. Distinguish hidden equal values from missing input data
- 5. Use describe() for distribution context, not reconciliation
1. Start with comparable offers, not just similar-looking tables
- Match the structure first. Both inputs to
compare()must have identical shapes, row labels, and column labels; otherwise pandas raisesValueError. [1] - Separate changes from context. Use
compare()for individual changed cells anddescribe()for summaries of each table’s distributions. [1], [2] - Read missing-looking output carefully. Equal values are displayed as NaN by default, while matching NaNs in the inputs are not reported as differences. [1]
For this task, a useful comparison is hotel offers before versus hotel offers after, or the equivalent pair of flight-offer snapshots. Comparing a hotel row directly with a flight row would not establish a meaningful offer change simply because both contain a price. This is a practical interpretation of the method’s element-wise behavior: pandas compares corresponding positions in identically labeled tables; the application must establish what those positions represent. [1]
Treat record identity and comparable field meanings as preparation requirements. Before comparing, decide which rows represent corresponding offers and which columns express equivalent attributes. A shared label is a structural requirement, not proof that two records describe the same product. If rows have been added or removed, resolve how to report that membership change separately rather than expecting compare() to match unequal tables automatically. [1]
2. A worked example: isolate a price change and a term change
Consider these hypothetical hotel-offer snapshots. The row labels deliberately remain identical, and both prices are assumed to use the same currency and pricing basis. One offer changes price, another changes its cancellation label, and a third remains unchanged. The example follows the documentation’s pattern of copying a DataFrame, editing selected cells, and comparing the versions. It illustrates pandas behavior, not actual booking data or a complete travel-data schema. [1]
import pandas as pd
before = pd.DataFrame(
{
"price": [180.0, 240.0, 150.0],
"cancellation": ["flexible", "nonrefundable", "flexible"],
},
index=["H101", "H102", "H103"],
)
after = before.copy()
after.loc["H101", "price"] = 195.0
after.loc["H102", "cancellation"] = "flexible"
changes = before.compare(
after,
result_names=("before", "after"),
)
print(changes)[1] [1] [1] [1]The result contains rows H101 and H102, with paired before/after columns for price and cancellation. H101 shows 180.0 versus 195.0 in price; its unchanged cancellation cells appear as NaN. H102 shows nonrefundable versus flexible in cancellation; its unchanged price cells appear as NaN. H103 disappears because neither field changed. Notice that the output is a rectangular table of affected rows and columns—not a list containing only changed cells. [1]
The same pattern can illustrate flight offers: construct two snapshots with corresponding flight-offer rows and equivalent columns, then compare them. Changing a numeric field or a text field produces the same kind of element-wise report. However, this output identifies different stored values; it does not explain why a fare changed or establish whether a revised offer is commercially equivalent to the earlier one. [1]
3. Choose the comparison view that answers your question
The default result puts the two versions side by side. Its columns form a MultiIndex, with source names at the inner level. Naming those sources before and after makes their meaning clearer than the defaults, self and other. Alternatively, align_axis=0 stacks the versions vertically, using an inner row-index level. This option changes presentation; it does not reconcile mismatched input labels. [1]
Two separate switches control how much context survives. keep_shape determines whether all original rows and columns remain, while keep_equal determines whether equal values are displayed. Crucially, keep_equal=True alone does not preserve every unchanged row or column. The following table distinguishes the useful combinations so that a compact discrepancy report is not confused with a full side-by-side snapshot. [1]
| Settings | Rows and columns retained | Equal values |
|---|---|---|
| Defaults | Only rows and columns containing differences | Shown as NaN [1] |
| keep_equal=True | Only rows and columns containing differences | Original values displayed [1] |
| keep_shape=True | All original rows and columns | Shown as NaN [1] |
| keep_shape=True, keep_equal=True | All original rows and columns | Original values displayed [1] |
4. Distinguish hidden equal values from missing input data
A NaN in the comparison report does not necessarily mean the original offer lacked that value. In the worked example, H102 has a known price in both snapshots, but the default report hides that equal price as NaN. Separately, if both snapshots actually contain NaN at the same location, pandas does not treat that matching pair as a difference. These two behaviors make it important to distinguish display suppression from input missingness. [1]
When reviewing an ambiguous result, rerun the comparison with both keep_shape=True and keep_equal=True. That view retains all original rows and columns and displays equal values too. It provides context without changing which inputs are being compared. Nevertheless, an absence of reported changes should not be interpreted as evidence that every field is populated: matching missing values can remain unflagged. [1]
5. Use describe() for distribution context, not reconciliation
before.describe() and after.describe() provide a different perspective. For a mixed table like the example, the default output summarizes numeric columns, excluding NaNs. Numeric statistics include count, mean, standard deviation, minimum, maximum, and percentiles; the defaults include the 25th, 50th, and 75th percentiles. These summaries help characterize prices across a snapshot, but they do not identify which offer changed. [2]
Use describe(include="all") to include text-valued attributes such as the example’s cancellation labels. Object columns receive count, unique, top, and freq rather than numeric statistics. Here, top means the most frequent value—not the best offer or most favorable policy—and tied values may be chosen arbitrarily. Statistics that do not apply to a column’s dtype appear as NaN. [2]
A practical review therefore has three distinct questions: are the records and fields comparable, which corresponding cells changed, and how do the snapshots’ distributions differ? Keep those questions separate. Structural compatibility enables compare(); its output exposes individual discrepancies; describe() supplies broader context. Neither a distribution summary nor a compact difference report substitutes for deciding which offers belong together. [1], [2]
Frequently asked questions
Why does compare() raise ValueError on two offer snapshots?
Its inputs must have identical shapes and identical row and column labels. A new offer, missing offer, or different set of columns can violate that requirement. Determine the intended record correspondence and comparable schema before calling the method; it does not automatically reconcile those differences. [1]
Does keep_equal=True restore every unchanged offer?
No. It displays equal values within the retained rows and columns. To preserve all original rows and columns as well, also set keep_shape=True. The two options control different aspects of the result. [1]
Can I make the source of each value clearer?
Yes. Pass result_names=("before", "after") or another meaningful pair. These names replace self and other in the comparison’s inner index level. The default layout uses paired columns; align_axis=0 places the versions on alternating rows instead. [1]
Will matching missing values be flagged?
No. Matching NaNs do not appear as a difference. Therefore, a change report is not a completeness check. Also remember that the default output uses NaN to hide equal values, even when the original values are present. [1]
Why are cancellation terms absent from the default describe() output?
For a table containing numeric and text columns, describe() analyzes numeric columns by default. Use include="all" to include text attributes. Their summaries describe counts and frequencies, not policy quality or individual changes. If there are no numeric columns, the default falls back to object and categorical columns. [2]
Conclusion
The reliable approach is to establish corresponding offers first, compare identically labeled snapshots second, and examine descriptive statistics separately. compare() gives a focused account of changed cells, with options for source labels, layout, and retained context. describe() summarizes distributions but does not reconcile records. Above all, interpret the report correctly: hidden equal values are not necessarily missing data, and matching missing values are not evidence of completeness. [1], [2]
Source notes
The referenced APIs do not establish travel-specific offer identity rules, a join or alignment workflow, currency normalization, or floating-point tolerance handling. Those require separately defined requirements.
Explore next
Sources
Open a source or expand its cited passages.
Relevance scores are model estimates of how well each source addresses this question, not probabilities or fact-check ratings. Reasoned guidance is identified on hover.
- 100pandas.DataFrame.compare — pandas 3.1.0 documentationOfficial pandas documentation directly establishes compare() requirements, output structure, options, and missing-value behavior.›
- 93pandas.DataFrame.describe — pandas 3.1.0 documentationOfficial pandas documentation explains distribution summaries and their distinction from cell-level comparisons.›
