Skip to content

Commit df0ea65

Browse files
committed
School escorting estimation updates
Most changes are needed to avoid crash if estimation run actually had no school escorting in the input data
1 parent a8e755f commit df0ea65

File tree

2 files changed

+98
-38
lines changed

2 files changed

+98
-38
lines changed

activitysim/abm/models/school_escorting.py

Lines changed: 90 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def determine_escorting_participants(choosers, persons, model_settings):
4646

4747
# can specify different weights to determine chaperones
4848
persontype_weight = model_settings.get("PERSON_WEIGHT", 100)
49-
gender_weight = model_settings.get("PERSON_WEIGHT", 10)
49+
gender_weight = model_settings.get("GENDER_WEIGHT", 10)
5050
age_weight = model_settings.get("AGE_WEIGHT", 1)
5151

5252
# can we move all of these to a config file?
@@ -122,7 +122,7 @@ def add_prev_choices_to_choosers(choosers, choices, alts, stage):
122122
stage_alts,
123123
how="left",
124124
left_on=escorting_choice,
125-
right_on=stage_alts.index.name,
125+
right_index=True,
126126
)
127127
.set_index("household_id")
128128
)
@@ -198,8 +198,12 @@ def create_school_escorting_bundles_table(choosers, tours, stage):
198198
bundles : pd.DataFrame
199199
one school escorting bundle per row
200200
"""
201-
# making a table of bundles
202-
choosers = choosers.reset_index()
201+
# want to keep household_id in columns, which is already there if running in estimation mode
202+
if "household_id" in choosers.columns:
203+
choosers = choosers.reset_index(drop=True)
204+
else:
205+
choosers = choosers.reset_index()
206+
# creating a row for every school escorting bundle
203207
choosers = choosers.loc[choosers.index.repeat(choosers["nbundles"])]
204208

205209
bundles = pd.DataFrame()
@@ -362,7 +366,11 @@ def school_escorting(
362366
households_merged = households_merged.to_frame()
363367
tours = tours.to_frame()
364368

365-
alts = simulate.read_model_alts(model_settings["ALTS"], set_index="Alt")
369+
# FIXME setting index as "Alt" causes crash in estimation mode...
370+
# happens in joint_tour_frequency_composition too!
371+
# alts = simulate.read_model_alts(model_settings["ALTS"], set_index="Alt")
372+
alts = simulate.read_model_alts(model_settings["ALTS"], set_index=None)
373+
alts.index = alts["Alt"].values
366374

367375
households_merged, participant_columns = determine_escorting_participants(
368376
households_merged, persons, model_settings
@@ -379,7 +387,9 @@ def school_escorting(
379387
choices = None
380388
for stage_num, stage in enumerate(school_escorting_stages):
381389
stage_trace_label = trace_label + "_" + stage
382-
estimator = estimation.manager.begin_estimation("school_escorting_" + stage)
390+
estimator = estimation.manager.begin_estimation(
391+
model_name="school_escorting_" + stage, bundle_name="school_escorting"
392+
)
383393

384394
model_spec_raw = simulate.read_model_spec(
385395
file_name=model_settings[stage.upper() + "_SPEC"]
@@ -434,9 +444,26 @@ def school_escorting(
434444

435445
if estimator:
436446
estimator.write_model_settings(model_settings, model_settings_file_name)
437-
estimator.write_spec(model_settings)
438-
estimator.write_coefficients(coefficients_df, model_settings)
447+
estimator.write_spec(model_settings, tag=stage.upper() + "_SPEC")
448+
estimator.write_coefficients(
449+
coefficients_df, file_name=stage.upper() + "_COEFFICIENTS"
450+
)
439451
estimator.write_choosers(choosers)
452+
estimator.write_alternatives(alts, bundle_directory=True)
453+
454+
# FIXME #interaction_simulate_estimation_requires_chooser_id_in_df_column
455+
# shuold we do it here or have interaction_simulate do it?
456+
# chooser index must be duplicated in column or it will be omitted from interaction_dataset
457+
# estimation requires that chooser_id is either in index or a column of interaction_dataset
458+
# so it can be reformatted (melted) and indexed by chooser_id and alt_id
459+
assert choosers.index.name == "household_id"
460+
assert "household_id" not in choosers.columns
461+
choosers["household_id"] = choosers.index
462+
463+
# FIXME set_alt_id - do we need this for interaction_simulate estimation bundle tables?
464+
estimator.set_alt_id("alt_id")
465+
466+
estimator.set_chooser_id(choosers.index.name)
440467

441468
log_alt_losers = config.setting("log_alt_losers", False)
442469

@@ -474,47 +501,72 @@ def school_escorting(
474501

475502
if stage_num >= 1:
476503
choosers["Alt"] = choices
477-
choosers = choosers.join(alts, how="left", on="Alt")
504+
choosers = choosers.join(alts.set_index("Alt"), how="left", on="Alt")
478505
bundles = create_school_escorting_bundles_table(
479506
choosers[choosers["Alt"] > 1], tours, stage
480507
)
481508
escort_bundles.append(bundles)
482509

510+
pipeline.replace_table("households", households)
511+
483512
escort_bundles = pd.concat(escort_bundles)
484-
escort_bundles["bundle_id"] = (
485-
escort_bundles["household_id"] * 10
486-
+ escort_bundles.groupby("household_id").cumcount()
487-
+ 1
488-
)
489-
escort_bundles.sort_values(
490-
by=["household_id", "school_escort_direction"],
491-
ascending=[True, False],
492-
inplace=True,
493-
)
494513

495-
school_escort_tours = school_escort_tours_trips.create_pure_school_escort_tours(
496-
escort_bundles
497-
)
498-
chauf_tour_id_map = {
499-
v: k for k, v in school_escort_tours["bundle_id"].to_dict().items()
500-
}
501-
escort_bundles["chauf_tour_id"] = np.where(
502-
escort_bundles["escort_type"] == "ride_share",
503-
escort_bundles["first_mand_tour_id"],
504-
escort_bundles["bundle_id"].map(chauf_tour_id_map),
505-
)
514+
# Only want to create bundles and tours and trips if at least one household has school escorting
515+
if len(escort_bundles) > 0:
516+
escort_bundles["bundle_id"] = (
517+
escort_bundles["household_id"] * 10
518+
+ escort_bundles.groupby("household_id").cumcount()
519+
+ 1
520+
)
521+
escort_bundles.sort_values(
522+
by=["household_id", "school_escort_direction"],
523+
ascending=[True, False],
524+
inplace=True,
525+
)
506526

507-
tours = school_escort_tours_trips.add_pure_escort_tours(tours, school_escort_tours)
508-
tours = school_escort_tours_trips.process_tours_after_escorting_model(
509-
escort_bundles, tours
510-
)
527+
school_escort_tours = school_escort_tours_trips.create_pure_school_escort_tours(
528+
escort_bundles
529+
)
530+
chauf_tour_id_map = {
531+
v: k for k, v in school_escort_tours["bundle_id"].to_dict().items()
532+
}
533+
escort_bundles["chauf_tour_id"] = np.where(
534+
escort_bundles["escort_type"] == "ride_share",
535+
escort_bundles["first_mand_tour_id"],
536+
escort_bundles["bundle_id"].map(chauf_tour_id_map),
537+
)
511538

512-
school_escort_trips = school_escort_tours_trips.create_school_escort_trips(
513-
escort_bundles
514-
)
539+
tours = school_escort_tours_trips.add_pure_escort_tours(
540+
tours, school_escort_tours
541+
)
542+
tours = school_escort_tours_trips.process_tours_after_escorting_model(
543+
escort_bundles, tours
544+
)
545+
546+
school_escort_trips = school_escort_tours_trips.create_school_escort_trips(
547+
escort_bundles
548+
)
549+
550+
else:
551+
# create empty school escort tours & trips tables to be used downstream
552+
tours["school_esc_outbound"] = pd.NA
553+
tours["school_esc_inbound"] = pd.NA
554+
tours["school_escort_direction"] = pd.NA
555+
tours["next_pure_escort_start"] = pd.NA
556+
school_escort_tours = pd.DataFrame(columns=tours.columns)
557+
trip_cols = [
558+
"household_id",
559+
"person_id",
560+
"tour_id",
561+
"trip_id",
562+
"outbound",
563+
"depart",
564+
"purpose",
565+
"destination",
566+
]
567+
school_escort_trips = pd.DataFrame(columns=trip_cols)
515568

516569
# update pipeline
517-
pipeline.replace_table("households", households)
518570
pipeline.replace_table("tours", tours)
519571
pipeline.get_rn_generator().drop_channel("tours")
520572
pipeline.get_rn_generator().add_channel("tours", tours)

activitysim/abm/models/util/school_escort_tours_trips.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,10 @@ def merge_school_escort_trips_into_pipeline():
399399
tours = pipeline.get_table("tours")
400400
trips = pipeline.get_table("trips")
401401

402+
# checking to see if there are school escort trips to merge in
403+
if len(school_escort_trips) == 0:
404+
return trips
405+
402406
# want to remove stops if school escorting takes place on that half tour so we can replace them with the actual stops
403407
out_se_tours = tours[
404408
tours["school_esc_outbound"].isin(["pure_escort", "ride_share"])
@@ -603,6 +607,10 @@ def force_escortee_tour_modes_to_match_chauffeur(tours):
603607
# Does it even matter if trip modes are getting matched later?
604608
escort_bundles = inject.get_table("escort_bundles").to_frame()
605609

610+
if len(escort_bundles) == 0:
611+
# do not need to do anything if no escorting
612+
return tours
613+
606614
# grabbing the school tour ids for each school escort bundle
607615
se_tours = escort_bundles[["school_tour_ids", "chauf_tour_id"]].copy()
608616
# merging in chauffeur tour mode

0 commit comments

Comments
 (0)