Simple FCFS¶
This is a very simple network, with only 3 markets, used to demonstrate some features of PassengerSim.
import passengersim as pax
pax.versions()
passengersim 0.18.1 passengersim.core 0.18.1
This example uses network/01-base.yaml configuration file.
Within a Jupyter notebook, we can directly initialize a PassengerSim Simulation
instance from this file using the from_yaml
class constructor:
sim = pax.Simulation.from_yaml("network/01-base.yaml")
Running the simulation is as simple as calling the run
command, which runs the simulation and returns a summary output object.
summary = sim.run()
Task Completed after 20.97 seconds
The contents of the final summary is controlled by Config.outputs.reports
, which allows the user to add reports for more detail,
or drop some unneccessary reports to improve runtime (sometimes substantially).
sim.config.outputs.reports
{'bookings_by_timeframe', 'carrier_history', 'demand_to_come', 'fare_class_mix', 'leg_forecasts', 'load_factors', 'total_demand'}
For this example, several default reports are included, which allows us to access a number of pre-packaged visualizations for the results.
summary.fig_carrier_revenues()
summary.fig_carrier_load_factors()
summary.fig_carrier_mileage()
summary.fig_fare_class_mix()
All demand is in the lowest fare class, because the simulation has no restrictions against customers simply buying the least expensive fare.
summary.fig_bookings_by_timeframe()
We are not limited to the pre-packaged visualizations. The various summary tables available in the summary
object are all just regular pandas DataFrames, so we can use all the usual Python and Pandas tools for analysis.
For example, the demand_to_come
table summarizes the total demand to come at each timeframe for every simulation sample.
summary.demand_to_come
rrd | 63 | 56 | 49 | 42 | 35 | 31 | 28 | 24 | 21 | 17 | 14 | 10 | 7 | 5 | 3 | 1 | 0 | |||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
iteration | trial | sample | segment | orig | dest | |||||||||||||||||
0 | 0 | 100 | business | BOS | LAX | 65 | 61 | 57 | 55 | 51 | 51 | 49 | 48 | 42 | 39 | 39 | 31 | 25 | 19 | 14 | 4 | 0 |
ORD | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |||||
ORD | LAX | 68 | 63 | 59 | 59 | 54 | 52 | 50 | 48 | 47 | 44 | 36 | 32 | 25 | 20 | 16 | 4 | 0 | ||||
leisure | BOS | LAX | 71 | 58 | 56 | 49 | 43 | 39 | 35 | 30 | 28 | 21 | 16 | 12 | 7 | 7 | 4 | 1 | 0 | |||
ORD | 49 | 43 | 39 | 34 | 32 | 31 | 27 | 24 | 19 | 15 | 13 | 11 | 11 | 10 | 6 | 1 | 0 | |||||
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | |
9 | 499 | business | BOS | ORD | 66 | 62 | 55 | 52 | 50 | 48 | 46 | 44 | 42 | 39 | 36 | 26 | 21 | 17 | 9 | 3 | 0 | |
ORD | LAX | 129 | 115 | 106 | 101 | 97 | 96 | 92 | 88 | 83 | 78 | 70 | 60 | 43 | 32 | 22 | 8 | 0 | ||||
leisure | BOS | LAX | 131 | 107 | 97 | 84 | 71 | 67 | 63 | 50 | 42 | 32 | 24 | 16 | 12 | 9 | 5 | 0 | 0 | |||
ORD | 96 | 87 | 85 | 76 | 63 | 52 | 47 | 39 | 35 | 29 | 22 | 16 | 13 | 10 | 5 | 4 | 0 | |||||
ORD | LAX | 156 | 128 | 117 | 105 | 85 | 75 | 72 | 57 | 46 | 33 | 28 | 20 | 15 | 11 | 6 | 3 | 0 |
24000 rows × 17 columns
With this data, we can do whatever analysis we like. Here we'll compute the correlation between total demands (from the beginning of the booking curve at DCP 63 all the way to the end) of different passenger types in different markets.
summary.demand_to_come[63].unstack(["segment", "orig", "dest"]).corr()
segment | business | leisure | ||||||
---|---|---|---|---|---|---|---|---|
orig | BOS | ORD | BOS | ORD | ||||
dest | LAX | ORD | LAX | LAX | ORD | LAX | ||
segment | orig | dest | ||||||
business | BOS | LAX | 1.000000 | 0.111814 | 0.146455 | 0.363078 | 0.122181 | 0.107158 |
ORD | 0.111814 | 1.000000 | 0.102067 | 0.106161 | 0.327514 | 0.104558 | ||
ORD | LAX | 0.146455 | 0.102067 | 1.000000 | 0.144507 | 0.115454 | 0.352284 | |
leisure | BOS | LAX | 0.363078 | 0.106161 | 0.144507 | 1.000000 | 0.125014 | 0.105721 |
ORD | 0.122181 | 0.327514 | 0.115454 | 0.125014 | 1.000000 | 0.103329 | ||
ORD | LAX | 0.107158 | 0.104558 | 0.352284 | 0.105721 | 0.103329 | 1.000000 |
The summary
object also has a command to dump all the summary tables to an Excel workbook, if you prefer to analyze the results there instead of in Jupyter.
summary.to_xlsx("outputs/3mkt-01.xlsx")
Comparing against Targets¶
In addition to summary reports for a single run, we can also use PassengerSim's contrast
package to compare simulation runs to each other, or against exogenously defined target results.
import targets
target = targets.load(1, sim.config)
from passengersim import contrast
comps = contrast.Contrast({
"simulation": summary,
"target": target,
})
comps.fig_carrier_revenues()
comps.fig_bookings_by_timeframe(by_carrier="AL1")
comps.fig_bookings_by_timeframe(by_carrier=False, by_class=True)
We can look at carrier forecasts of demand on individual legs.
comps.fig_leg_forecasts(by_flt_no=111, of=["mu", "sigma"])
We can compare the mean and standard deviation of demand to come.
comps.fig_demand_to_come("mean") | comps.fig_demand_to_come("std")
We can even take arbitrary functions that apply pandas tools, and have them run automatically against multiple summary objects. For example, we can look at the variance-covariance matrix of aggregate demand by passenger type, and compare those matrices for both the simulation and the target.
comps.apply(lambda s: s.aggregate_demand_history(by_segment=True).unstack("segment").cov())
segment | business | leisure | |
---|---|---|---|
source | segment | ||
simulation | business | 3279.856113 | 1981.353347 |
leisure | 1981.353347 | 5382.230033 | |
target | business | 3249.598992 | 1955.419868 |
leisure | 1955.419868 | 5359.555328 |