@@ -58,7 +58,14 @@ def determine_escorting_participants(
5858 & (persons .cdap_activity == "M" )
5959 ]
6060 households_with_escortees = escortees ["household_id" ]
61- choosers = choosers [choosers .index .isin (households_with_escortees )]
61+ if len (households_with_escortees ) == 0 :
62+ logger .warning ("No households with escortees found!" )
63+ else :
64+ tot_households = len (choosers )
65+ choosers = choosers [choosers .index .isin (households_with_escortees )]
66+ logger .info (
67+ f"Proceeding with { len (choosers )} households with escortees out of { tot_households } total households"
68+ )
6269
6370 # can specify different weights to determine chaperones
6471 persontype_weight = model_settings .PERSON_WEIGHT
@@ -140,7 +147,7 @@ def add_prev_choices_to_choosers(
140147 stage_alts ,
141148 how = "left" ,
142149 left_on = escorting_choice ,
143- right_on = stage_alts . index . name ,
150+ right_index = True ,
144151 )
145152 .set_index ("household_id" )
146153 )
@@ -216,8 +223,12 @@ def create_school_escorting_bundles_table(choosers, tours, stage):
216223 bundles : pd.DataFrame
217224 one school escorting bundle per row
218225 """
219- # making a table of bundles
220- choosers = choosers .reset_index ()
226+ # want to keep household_id in columns, which is already there if running in estimation mode
227+ if "household_id" in choosers .columns :
228+ choosers = choosers .reset_index (drop = True )
229+ else :
230+ choosers = choosers .reset_index ()
231+ # creating a row for every school escorting bundle
221232 choosers = choosers .loc [choosers .index .repeat (choosers ["nbundles" ])]
222233
223234 bundles = pd .DataFrame ()
@@ -460,7 +471,11 @@ def school_escorting(
460471
461472 trace_hh_id = state .settings .trace_hh_id
462473
463- alts = simulate .read_model_alts (state , model_settings .ALTS , set_index = "Alt" )
474+ # FIXME setting index as "Alt" causes crash in estimation mode...
475+ # happens in joint_tour_frequency_composition too!
476+ # alts = simulate.read_model_alts(state, model_settings.ALTS, set_index="Alt")
477+ alts = simulate .read_model_alts (state , model_settings .ALTS , set_index = None )
478+ alts .index = alts ["Alt" ].values
464479
465480 choosers , participant_columns = determine_escorting_participants (
466481 households_merged , persons , model_settings
@@ -478,7 +493,9 @@ def school_escorting(
478493 for stage_num , stage in enumerate (school_escorting_stages ):
479494 stage_trace_label = trace_label + "_" + stage
480495 estimator = estimation .manager .begin_estimation (
481- state , "school_escorting_" + stage
496+ state ,
497+ model_name = "school_escorting_" + stage ,
498+ bundle_name = "school_escorting" ,
482499 )
483500
484501 model_spec_raw = state .filesystem .read_model_spec (
@@ -533,9 +550,26 @@ def school_escorting(
533550
534551 if estimator :
535552 estimator .write_model_settings (model_settings , model_settings_file_name )
536- estimator .write_spec (model_settings )
537- estimator .write_coefficients (coefficients_df , model_settings )
553+ estimator .write_spec (model_settings , tag = stage .upper () + "_SPEC" )
554+ estimator .write_coefficients (
555+ coefficients_df , file_name = stage .upper () + "_COEFFICIENTS"
556+ )
538557 estimator .write_choosers (choosers )
558+ estimator .write_alternatives (alts , bundle_directory = True )
559+
560+ # FIXME #interaction_simulate_estimation_requires_chooser_id_in_df_column
561+ # shuold we do it here or have interaction_simulate do it?
562+ # chooser index must be duplicated in column or it will be omitted from interaction_dataset
563+ # estimation requires that chooser_id is either in index or a column of interaction_dataset
564+ # so it can be reformatted (melted) and indexed by chooser_id and alt_id
565+ assert choosers .index .name == "household_id"
566+ assert "household_id" not in choosers .columns
567+ choosers ["household_id" ] = choosers .index
568+
569+ # FIXME set_alt_id - do we need this for interaction_simulate estimation bundle tables?
570+ estimator .set_alt_id ("alt_id" )
571+
572+ estimator .set_chooser_id (choosers .index .name )
539573
540574 log_alt_losers = state .settings .log_alt_losers
541575
@@ -580,47 +614,74 @@ def school_escorting(
580614
581615 if stage_num >= 1 :
582616 choosers ["Alt" ] = choices
583- choosers = choosers .join (alts , how = "left" , on = "Alt" )
617+ choosers = choosers .join (alts . set_index ( "Alt" ) , how = "left" , on = "Alt" )
584618 bundles = create_school_escorting_bundles_table (
585619 choosers [choosers ["Alt" ] > 1 ], tours , stage
586620 )
587621 escort_bundles .append (bundles )
588622
589623 escort_bundles = pd .concat (escort_bundles )
590- escort_bundles ["bundle_id" ] = (
591- escort_bundles ["household_id" ] * 10
592- + escort_bundles .groupby ("household_id" ).cumcount ()
593- + 1
594- )
595- escort_bundles .sort_values (
596- by = ["household_id" , "school_escort_direction" ],
597- ascending = [True , False ],
598- inplace = True ,
599- )
600624
601- school_escort_tours = school_escort_tours_trips .create_pure_school_escort_tours (
602- state , escort_bundles
603- )
604- chauf_tour_id_map = {
605- v : k for k , v in school_escort_tours ["bundle_id" ].to_dict ().items ()
606- }
607- escort_bundles ["chauf_tour_id" ] = np .where (
608- escort_bundles ["escort_type" ] == "ride_share" ,
609- escort_bundles ["first_mand_tour_id" ],
610- escort_bundles ["bundle_id" ].map (chauf_tour_id_map ),
611- )
612- assert (
613- escort_bundles ["chauf_tour_id" ].notnull ().all ()
614- ), f"chauf_tour_id is null for { escort_bundles [escort_bundles ['chauf_tour_id' ].isna ()]} . Check availability conditions."
625+ # Only want to create bundles and tours and trips if at least one household has school escorting
626+ if len (escort_bundles ) > 0 :
627+ escort_bundles ["bundle_id" ] = (
628+ escort_bundles ["household_id" ] * 10
629+ + escort_bundles .groupby ("household_id" ).cumcount ()
630+ + 1
631+ )
632+ escort_bundles .sort_values (
633+ by = ["household_id" , "school_escort_direction" ],
634+ ascending = [True , False ],
635+ inplace = True ,
636+ )
615637
616- tours = school_escort_tours_trips .add_pure_escort_tours (tours , school_escort_tours )
617- tours = school_escort_tours_trips .process_tours_after_escorting_model (
618- state , escort_bundles , tours
619- )
638+ school_escort_tours = school_escort_tours_trips .create_pure_school_escort_tours (
639+ state , escort_bundles
640+ )
641+ chauf_tour_id_map = {
642+ v : k for k , v in school_escort_tours ["bundle_id" ].to_dict ().items ()
643+ }
644+ escort_bundles ["chauf_tour_id" ] = np .where (
645+ escort_bundles ["escort_type" ] == "ride_share" ,
646+ escort_bundles ["first_mand_tour_id" ],
647+ escort_bundles ["bundle_id" ].map (chauf_tour_id_map ),
648+ )
620649
621- school_escort_trips = school_escort_tours_trips .create_school_escort_trips (
622- escort_bundles
623- )
650+ assert (
651+ escort_bundles ["chauf_tour_id" ].notnull ().all ()
652+ ), f"chauf_tour_id is null for { escort_bundles [escort_bundles ['chauf_tour_id' ].isna ()]} . Check availability conditions."
653+
654+ tours = school_escort_tours_trips .add_pure_escort_tours (
655+ tours , school_escort_tours
656+ )
657+ tours = school_escort_tours_trips .process_tours_after_escorting_model (
658+ state , escort_bundles , tours
659+ )
660+ school_escort_trips = school_escort_tours_trips .create_school_escort_trips (
661+ escort_bundles
662+ )
663+
664+ else :
665+ # create empty school escort tours & trips tables to be used downstream
666+ tours ["school_esc_outbound" ] = pd .NA
667+ tours ["school_esc_inbound" ] = pd .NA
668+ tours ["school_escort_direction" ] = pd .NA
669+ tours ["next_pure_escort_start" ] = pd .NA
670+ school_escort_tours = pd .DataFrame (columns = tours .columns )
671+ trip_cols = [
672+ "household_id" ,
673+ "person_id" ,
674+ "tour_id" ,
675+ "trip_id" ,
676+ "outbound" ,
677+ "depart" ,
678+ "purpose" ,
679+ "destination" ,
680+ "escort_participants" ,
681+ "chauf_tour_id" ,
682+ "primary_purpose" ,
683+ ]
684+ school_escort_trips = pd .DataFrame (columns = trip_cols )
624685
625686 school_escort_trips ["primary_purpose" ] = school_escort_trips [
626687 "primary_purpose"
0 commit comments