Contrasting Runs

Often when we run PassengerSim, we’re not really interested in just the result of a simulation run – we are interested in how the results change across two or more slightly different runs. For this kind of analysis, PassengerSim offers a Contrast tool, which bundles multiple SimulationTables objects together, and provides comparison figures mirroring many of the single-simulation visualizations.

import passengersim as pax
from passengersim.contrast import Contrast

summary0 = pax.from_file(pax.demo_output("US25/baseline"))
summary1 = pax.from_file(pax.demo_output("US25/high-demand"))

comps = Contrast({"Baseline": summary0, "High Demand": summary1})

Carrier Revenues

Contrast.fig_carrier_revenues.

Display the average revenues by carrier.

comps.fig_carrier_revenues()

Carrier Load Factors

Contrast.fig_carrier_load_factors.

Display the average system load factors by carrier. The system load factor is calculated based on ASM and RPM figures for the carrier, which weights larger capacity vehicles and longer distance legs more heavily, to reflect their larger relative importance in evaluating carrier performance.

comps.fig_carrier_load_factors()
comps.fig_carrier_load_factors(load_measure="avg_leg_lf")

Carrier Yields

Contrast.fig_carrier_yields.

Display the average yield (revenue per passenger mile) by carrier.

comps.fig_carrier_yields()

Fare Class Mix

Contrast.fig_fare_class_mix.

Display the fare class mix by carrier.

comps.fig_fare_class_mix()
/Users/jpn/Git/pax/packages/passengersim-public/src/passengersim/reporting/__init__.py:66: UserWarning: error in getting data from 'Baseline': "['fare_class', 'brand'] not in index"
  fig = func(*args, **kwargs)
/Users/jpn/Git/pax/packages/passengersim-public/src/passengersim/reporting/__init__.py:66: UserWarning: error in getting data from 'High Demand': "['fare_class', 'brand'] not in index"
  fig = func(*args, **kwargs)
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[7], line 1
----> 1 comps.fig_fare_class_mix()

File ~/Git/pax/packages/passengersim-public/src/passengersim/reporting/__init__.py:66, in report_figure.<locals>.report_figure_wrapper(report, trace, *args, **kwargs)
     64 @wraps(func)
     65 def report_figure_wrapper(*args, report=None, trace=None, **kwargs):
---> 66     fig = func(*args, **kwargs)
     67     if report is not None:
     68         report.add_figure(fig)

File ~/Git/pax/packages/passengersim-public/src/passengersim/contrast/__init__.py:1344, in fig_fare_class_mix(summaries, raw_df, also_df, label_threshold, group_by)
   1309 @report_figure
   1310 def fig_fare_class_mix(
   1311     summaries: dict[str, SimulationTables],
   (...)   1316     group_by: str = "",
   1317 ) -> alt.Chart | pd.DataFrame | tuple[alt.Chart, pd.DataFrame]:
   1318     """Generate a figure contrasting carrier fare class mix for one or more runs.
   1319 
   1320     Displays stacked bar charts of average seats sold per booking class for
   (...)   1342         ``(chart, dataframe)`` tuple if ``also_df`` is True.
   1343     """
-> 1344     df = _assemble(summaries, "fare_class_mix")
   1345     source_order = list(summaries.keys())
   1347     sub_title = ""

File ~/Git/pax/packages/passengersim-public/src/passengersim/contrast/__init__.py:338, in _assemble(summaries, base, **kwargs)
    335         summaries_[k] = v
    336 if len(summaries_) == 0:
    337     # no data recovered, re-raise last exception
--> 338     raise last_exception
    339 return pd.concat(summaries_, names=["source"]).reset_index(level="source")

File ~/Git/pax/packages/passengersim-public/src/passengersim/contrast/__init__.py:324, in _assemble(summaries, base, **kwargs)
    322 if (fun := getattr(v, f"fig_{base}", None)) is not None:
    323     try:
--> 324         summaries_[k] = fun(raw_df=True, **kwargs)
    325     except Exception as err:
    326         last_exception = err

File ~/Git/pax/packages/passengersim-public/src/passengersim/reporting/__init__.py:66, in report_figure.<locals>.report_figure_wrapper(report, trace, *args, **kwargs)
     64 @wraps(func)
     65 def report_figure_wrapper(*args, report=None, trace=None, **kwargs):
---> 66     fig = func(*args, **kwargs)
     67     if report is not None:
     68         report.add_figure(fig)

File ~/Git/pax/packages/passengersim-public/src/passengersim/summaries/fare_class_mix.py:165, in SimTabFareClassMix.fig_fare_class_mix(self, raw_df, also_df, label_threshold, title, group_by)
    163     df = self.fare_class_mix / self.n_total_samples
    164     df = df.rename(columns={"sold": "avg_sold"})
--> 165     df = df.reset_index()[["carrier", "fare_class", "booking_class", "brand", "avg_sold"]]
    166 else:
    167     return None

File ~/Git/pax/.venv/lib/python3.12/site-packages/pandas/core/frame.py:4119, in DataFrame.__getitem__(self, key)
   4115                 indexer = [indexer]
   4116         else:
   4117             if is_iterator(key):
   4118                 key = list(key)
-> 4119             indexer = self.columns._get_indexer_strict(key, "columns")[1]
   4120 
   4121         # take() does not accept boolean indexers
   4122         if getattr(indexer, "dtype", None) == bool:

File ~/Git/pax/.venv/lib/python3.12/site-packages/pandas/core/indexes/base.py:6212, in Index._get_indexer_strict(self, key, axis_name)
   6209 else:
   6210     keyarr, indexer, new_indexer = self._reindex_non_unique(keyarr)
-> 6212 self._raise_if_missing(keyarr, indexer, axis_name)
   6214 keyarr = self.take(indexer)
   6215 if isinstance(key, Index):
   6216     # GH 42790 - Preserve name from an Index

File ~/Git/pax/.venv/lib/python3.12/site-packages/pandas/core/indexes/base.py:6264, in Index._raise_if_missing(self, key, indexer, axis_name)
   6261     raise KeyError(f"None of [{key}] are in the [{axis_name}]")
   6263 not_found = list(ensure_index(key)[missing_mask.nonzero()[0]].unique())
-> 6264 raise KeyError(f"{not_found} not in index")

KeyError: "['fare_class', 'brand'] not in index"

Bookings by Timeframe

Contrast.fig_bookings_by_timeframe.

Display the bookings by timeframe, optionally filtered by carrier and broken down by fare class.

comps.fig_bookings_by_timeframe(by_carrier="Bison")
comps.fig_bookings_by_timeframe(by_carrier="Bison", by_class=True, source_labels=True)

Demand to Come

Contrast.fig_demand_to_come.

Display the demand to come (total demand, not just bookings) over time, showing how anticipated demand evolves as the departure date approaches. This figure can show the mean, standard deviation, or both.

comps.fig_demand_to_come(["mean", "std"])

Bid Prices

Contrast.fig_bid_price_history.

Display the bid price history over time, showing how bid prices evolve as the departure date approaches across different simulation scenarios.

comps_3mkt.fig_bid_price_history()

Displacement Costs

Contrast.fig_displacement_history.

Display the displacement cost history over time, showing how displacement costs evolve as the departure date approaches across different simulation scenarios. Note that not all bid price based RM algorithms compute a displacement cost, in which case these values will be reported as zeros.

comps_3mkt.fig_displacement_history()