import sys
from pathlib import Path
sys.path.append(str(Path.cwd().parent))import gcsfs
import pandas as pd
import numpy as np
import process_ntd
import chart_ideas
import altair as alt
alt.data_transformers.enable("vegafusion");df_capex, df_opex, df_merged, df_mode_efficiency, df_agency_mode_efficiency = process_ntd.load_processed_data_for_mode_report()Analytical Composition¶
Analytical Sample Disclaimer: The mode-level analysis includes all available agency observations in the NTD data for each year. Results are not separately segmented by reporter type, reporting module, or agency status in the remaining charts. The composition of the available agency observations by these characteristics is shown in the following composition chart and should be considered when interpreting year-to-year differences in spending, performance, and cost-effectiveness.
sample_chart = chart_ideas.composition_by_year(
df_opex,
{
"reporter_type": "Reporter Type",
"reporting_module": "Reporting Module",
"agency_status": "Agency Status",
},
chart_title="Analytic Sample Composition by Year"
)
sample_chartLoading...
1. Trend & composition¶
1.1 Ridership, OPEX, CAPEX, VRH and VRM Trends¶
ridership_trend = chart_ideas.mode_trend_chart(chart_ideas.prep_for_mode_trend(df_opex, "unlinked_passenger_trips"), "unlinked_passenger_trips").properties(title="Unlinked Passenger Trips")
opex_trend = chart_ideas.mode_trend_chart(chart_ideas.prep_for_mode_trend(df_opex, "operating_expenses_total_real"), "operating_expenses_total_real").properties(title="Operating Expenditures (Total)")
capex_trend = chart_ideas.mode_trend_chart(chart_ideas.prep_for_mode_trend(df_capex, "total_capital_expenditures_real"), "total_capital_expenditures_real").properties(title="Capital Expenditures (Total)")
vrh_trend = chart_ideas.mode_trend_chart(chart_ideas.prep_for_mode_trend(df_opex, "vehicle_revenue_hours"), "vehicle_revenue_hours").properties(title="Vehicle Revenue Hours")
vrm_trend = chart_ideas.mode_trend_chart(chart_ideas.prep_for_mode_trend(df_opex, "vehicle_revenue_miles"), "vehicle_revenue_miles").properties(title="Vehicle Revenue Miles")
mode_report_grid = (
alt.vconcat(
ridership_trend.properties(width=900),
alt.hconcat(opex_trend, capex_trend),
alt.hconcat(vrh_trend, vrm_trend)
)
.resolve_scale(color="shared")
.configure_view(strokeWidth=0, fill="white")
.configure_axis(labelFont="Arial", titleFont="Arial")
.configure_title(font="Arial")
).configure_axis(**chart_ideas.ALT_AXIS_CONFIG)
mode_report_gridLoading...
1.2 Capital and Operating Expenditures by Mode Group Over Time¶
capex_opex_share = chart_ideas.percent_stacked_bar(
df_merged,
"mode_group",
{
"CAPEX": "total_capital_expenditures_real",
"OPEX": "operating_expenses_total_real",
},
year_range=(2019, 2024),
chart_title="CAPEX and OPEX by Mode Group, 2019–2024",
y_title="Share of Total Expenditure (%)",
colors=["#BFD8B8", "#C7B8D8"],
).configure_axis(**chart_ideas.ALT_AXIS_CONFIG)
capex_opex_shareLoading...
1.3 Expenses Breakdown by Mode for Latest NTD Year¶
opex_chart = chart_ideas.breakdown_chart(
df_opex,
group_column = "mode_group",
year_column = "year",
year=2024,
component_columns=[
"operating_expenses_vehicle_operations_real",
"operating_expenses_vehicle_maintenance_real",
"operating_expenses_nonvehicle_maintenance_real",
"operating_expenses_general_administration_real",
],
title="Operating Expense Composition by Mode, 2024",
)
capex_chart = chart_ideas.breakdown_chart(
df_capex,
group_column = "mode_group",
year_column = "year",
year=2024,
component_columns=[
"rolling_stock_expenditures_real",
"facilities_expenditures_real",
"other_expenditures_real",
],
title="Capital Expenditure Composition by Mode, 2024",
)
charts = [opex_chart, capex_chart]
breakdown_by_mode = alt.hconcat(*charts).configure_axis(**chart_ideas.ALT_AXIS_CONFIG)
breakdown_by_mode
Loading...
2. Efficiency/ Performance¶
2.1 Mode Performance (HHI, Spending Per Capita and Volatility)¶
scorecard = df_opex[df_opex["year"] == 2024].drop_duplicates("mode_group")
opex_scorecard = chart_ideas.plot_scorecard(
scorecard,
metrics=["hhi", "per_capita", "volatility"],
group_column="mode_group",
titles=[
"Expense Concentration",
"Spending per Capita",
"Spending Volatility (Compared to Previous Year)",
],
x_labels=[
"Operating Expenditure Composition HHI",
"Spending per Capita (USD)",
"Average Absolute YoY Change (%)",
],
formats=[".3f", "$,.0f", ".1%"],
main_title="OPEX Mode Performance 2024",
).configure_axis(**chart_ideas.ALT_AXIS_CONFIG)
opex_scorecardLoading...
scorecard = df_capex[df_capex["year"] == 2024].drop_duplicates("mode_group")
capex_scorecard = chart_ideas.plot_scorecard(
scorecard,
metrics=["hhi", "per_capita", "volatility"],
group_column="mode_group",
titles=[
"Expense Concentration",
"Spending per Capita",
"Spending Volatility (Compared to Previous Year)",
],
x_labels=[
"Capital Expenditure Composition HHI",
"Spending per Capita (USD)",
"Average Absolute YoY Change (%)",
],
formats=[".3f", "$,.0f", ".1%"],
main_title="CAPEX Mode Performance 2024",
).configure_axis(**chart_ideas.ALT_AXIS_CONFIG)
capex_scorecardLoading...
2.2 Cost-Efficiency by Mode¶
cost_efficiency_matrix = chart_ideas.efficiency_matrix(
df_mode_efficiency[df_mode_efficiency["year"] == 2024],
metrics=["opex_per_vrh", "opex_per_vrm", "opex_per_upt"],
group_column="mode_full_name",
titles=["Opex per VRH", "Opex per VRM", "Opex per Trip"],
normalize="zscore",
reverse_scale=[True, True, True],
chart_title="Cost-Efficiency by Mode, 2024",
width=600,
height=400
).configure_axis(**chart_ideas.ALT_AXIS_CONFIG)
cost_efficiency_matrixLoading...
2.3 Cost per VRH: Variation Across Agencies Within Each Mode¶
spread_vrh = chart_ideas.simple_box_plot(
df_agency_mode_efficiency,
value_column="opex_per_vrh",
group_column="mode_full_name",
year_column="year",
year=2024,
value_format="$,.0f",
x_title="Operating Expense per Vehicle Revenue Hour",
chart_title="Operating Cost per Revenue Hour: Agency Variation by Mode, 2024",
chart_subtitle="Each observation represents one agency within a mode."
).configure_axis(**chart_ideas.ALT_AXIS_CONFIG)
spread_vrhLoading...
3. Cost-effectiveness¶
3.1 Farebox Recovery Ratio by Mode¶
farebox_recovery_ridge = chart_ideas.ridge_plot(df_agency_mode_efficiency, "farebox_recovery_ratio", "mode_full_name",
year_column="year", year=2024,
step=30,
overlap=1.5,
chart_title="Farebox Recovery Ratio Distribution by Transit Mode",
chart_subtitle="2024 | One observation per California agency per mode").configure_axis(**chart_ideas.ALT_AXIS_CONFIG)
farebox_recovery_ridgeLoading...
3.2 Cost Effectiveness Matrix¶
cost_effectiveness_matrix = chart_ideas.efficiency_matrix(
df_mode_efficiency[df_mode_efficiency["year"] == 2024],
metrics=["farebox_recovery", "fare_revenue_per_trip", "net_subsidy_per_trip"],
group_column="mode_full_name",
titles=["Farebox Recovery", "Fare Revenue per Trip", "Net Subsidy per Trip"],
normalize="zscore",
reverse_scale=[False, False, True],
chart_title="Cost-Effectiveness by Mode, 2024",
width=600,
height=400
).configure_axis(**chart_ideas.ALT_AXIS_CONFIG)
cost_effectiveness_matrixLoading...
3.3 Operating Costs vs. Fare Revenue, Indexed to 2019¶
scissors_chart = chart_ideas.indexed_scissors(
df_opex, "mode_group", "year",
["operating_expenses_total_real", "fare_revenue_real"], 2019,
{"operating_expenses_total_real": "Operating Expenses", "fare_revenue_real": "Fare Revenue"},
["#2563EB", "#F59E0B"],
"Real Operating Costs vs. Real Fare Revenue",
"Both indexed to 2019 = 100; widening separation indicates divergent growth"
).configure_axis(**chart_ideas.ALT_AXIS_CONFIG)
scissors_chart
Loading...
4. Service Effectiveness¶
4.1 Operating Efficiency: Cost vs. Service Utilization and Service Intensity¶
# VRM
opex_vrm = chart_ideas.grouped_scatter(
df_agency_mode_efficiency,
"log_opex_vrm",
"log_trips_vrm",
group_column="mode_group",
size_column="unlinked_passenger_trips",
label_column="source_agency",
tooltip_columns=[
"source_agency", "mode_group", "log_opex_vrm", "log_trips_vrm"
],
x_title="Log Operating Expense per Vehicle Revenue Mile",
y_title="Log Passenger Trips per Vehicle Revenue Mile",
chart_title="Operating Cost vs. Service Utilization — VRM-based",
)
# VRH
opex_vrh = chart_ideas.grouped_scatter(
df_agency_mode_efficiency,
"log_opex_vrh",
"log_trips_vrh",
group_column="mode_group",
size_column="unlinked_passenger_trips",
label_column="source_agency",
tooltip_columns=[
"source_agency", "mode_group", "log_opex_vrh", "log_trips_vrh"
],
x_title="Log Operating Expense per Vehicle Revenue Hour",
y_title="Log Passenger Trips per Vehicle Revenue Hour",
chart_title="Operating Cost vs. Service Intensity — VRH-based",
)
charts = [opex_vrm, opex_vrh]
service_utilization_chart = (
alt.hconcat(*charts)
.configure_view(strokeWidth=0)
.configure_axis(**chart_ideas.ALT_AXIS_CONFIG)
.configure_title(font="Arial")
)
service_utilization_chart
Loading...