def __init__(self, size_term_selector): # do this once so they can request siae_terms for various segments (tour_type or purpose) land_use = inject.get_table('land_use') size_terms = inject.get_injectable('size_terms') self.destination_size_terms = \ tour_destination_size_terms(land_use, size_terms, size_term_selector)
def __init__(self, size_term_selector): # do this once so they can request siae_terms for various segments (tour_type or purpose) land_use = inject.get_table('land_use') size_terms = inject.get_injectable('size_terms') self.destination_size_terms = \ tour_destination_size_terms(land_use, size_terms, size_term_selector)
def atwork_subtour_destination( tours, persons_merged, skim_dict, skim_stack, land_use, size_terms, chunk_size, trace_hh_id): persons_merged = persons_merged.to_frame() tours = tours.to_frame() subtours = tours[tours.tour_category == 'atwork'] # - if no atwork subtours if subtours.shape[0] == 0: tracing.no_results('atwork_subtour_destination') return # interaction_sample_simulate insists choosers appear in same order as alts subtours = subtours.sort_index() destination_size_terms = tour_destination_size_terms(land_use, size_terms, 'atwork') destination_sample = atwork_subtour_destination_sample( subtours, persons_merged, skim_dict, destination_size_terms, chunk_size, trace_hh_id) destination_sample = atwork_subtour_destination_logsums( persons_merged, destination_sample, skim_dict, skim_stack, chunk_size, trace_hh_id) choices = atwork_subtour_destination_simulate( subtours, persons_merged, destination_sample, skim_dict, destination_size_terms, chunk_size, trace_hh_id) subtours['destination'] = choices assign_in_place(tours, subtours[['destination']]) pipeline.replace_table("tours", tours) tracing.print_summary('subtour destination', subtours.destination, describe=True) if trace_hh_id: tracing.trace_df(tours, label='atwork_subtour_destination', columns=['destination'])
def __init__(self, size_term_selector): # do this once so they can request size_terms for various segments (tour_type or purpose) land_use = inject.get_table('land_use') self.land_use = land_use size_terms = inject.get_injectable('size_terms') self.destination_size_terms = \ tour_destination_size_terms(self.land_use, size_terms, size_term_selector) assert not self.destination_size_terms.isna().any(axis=None)
def atwork_subtour_destination(tours, persons_merged, skim_dict, skim_stack, land_use, size_terms, chunk_size, trace_hh_id): persons_merged = persons_merged.to_frame() tours = tours.to_frame() subtours = tours[tours.tour_category == 'atwork'] # - if no atwork subtours if subtours.shape[0] == 0: tracing.no_results('atwork_subtour_destination') return # interaction_sample_simulate insists choosers appear in same order as alts subtours = subtours.sort_index() destination_size_terms = tour_destination_size_terms( land_use, size_terms, 'atwork') destination_sample = atwork_subtour_destination_sample( subtours, persons_merged, skim_dict, destination_size_terms, chunk_size, trace_hh_id) destination_sample = atwork_subtour_destination_logsums( persons_merged, destination_sample, skim_dict, skim_stack, chunk_size, trace_hh_id) choices = atwork_subtour_destination_simulate(subtours, persons_merged, destination_sample, skim_dict, destination_size_terms, chunk_size, trace_hh_id) subtours['destination'] = choices assign_in_place(tours, subtours[['destination']]) pipeline.replace_table("tours", tours) tracing.print_summary('subtour destination', subtours.destination, describe=True) if trace_hh_id: tracing.trace_df(tours, label='atwork_subtour_destination', columns=['destination'])
def run_trip_destination(trips, tours_merged, estimator, chunk_size, trace_hh_id, trace_label, fail_some_trips_for_testing=False): """ trip destination - main functionality separated from model step so it can be called iteratively Run the trip_destination model, assigning destinations for each (intermediate) trip (last trips already have a destination - either the tour primary destination or Home) Set trip destination and origin columns, and a boolean failed flag for any failed trips (destination for flagged failed trips will be set to -1) Parameters ---------- trips tours_merged want_sample_table chunk_size trace_hh_id trace_label Returns ------- """ model_settings_file_name = 'trip_destination.yaml' model_settings = config.read_model_settings(model_settings_file_name) preprocessor_settings = model_settings.get('preprocessor', None) logsum_settings = config.read_model_settings( model_settings['LOGSUM_SETTINGS']) logsum_column_name = model_settings.get('DEST_CHOICE_LOGSUM_COLUMN_NAME') want_logsums = logsum_column_name is not None sample_table_name = model_settings.get('DEST_CHOICE_SAMPLE_TABLE_NAME') want_sample_table = config.setting( 'want_dest_choice_sample_tables') and sample_table_name is not None land_use = inject.get_table('land_use') size_terms = inject.get_injectable('size_terms') network_los = inject.get_injectable('network_los') trips = trips.sort_index() trips['next_trip_id'] = np.roll(trips.index, -1) trips.next_trip_id = trips.next_trip_id.where( trips.trip_num < trips.trip_count, 0) # - initialize trip origin and destination to those of half-tour # (we will sequentially adjust intermediate trips origin and destination as we choose them) tour_destination = reindex(tours_merged.destination, trips.tour_id).astype(np.int64) tour_origin = reindex(tours_merged.origin, trips.tour_id).astype(np.int64) trips['destination'] = np.where(trips.outbound, tour_destination, tour_origin) trips['origin'] = np.where(trips.outbound, tour_origin, tour_destination) trips['failed'] = False if estimator: # need to check or override non-intermediate trip destination # should check consistency of survey trips origin, destination with parent tour and subsequent/prior trip? # FIXME if not consistent, do we fail or override? (seems weird to override them to bad values?) # expect all the same trips survey_trips = estimator.get_survey_table('trips').sort_index() assert survey_trips.index.equals(trips.index) first = (survey_trips.trip_num == 1) last = (survey_trips.trip_num == trips.trip_count) # expect survey's outbound first trip origin to be same as half tour origin assert ( survey_trips.origin[survey_trips.outbound & first] == tour_origin[survey_trips.outbound & first]).all() # expect outbound last trip destination to be same as half tour destination assert (survey_trips.destination[survey_trips.outbound & last] == tour_destination[survey_trips.outbound & last]).all() # expect inbound first trip origin to be same as half tour destination assert (survey_trips.origin[~survey_trips.outbound & first] == tour_destination[~survey_trips.outbound & first]).all() # expect inbound last trip destination to be same as half tour origin assert (survey_trips.destination[~survey_trips.outbound & last] == tour_origin[~survey_trips.outbound & last]).all() # - filter tours_merged (AFTER copying destination and origin columns to trips) # tours_merged is used for logsums, we filter it here upfront to save space and time tours_merged_cols = logsum_settings['TOURS_MERGED_CHOOSER_COLUMNS'] redundant_cols = model_settings.get( 'REDUNDANT_TOURS_MERGED_CHOOSER_COLUMNS', []) if redundant_cols: tours_merged_cols = [ c for c in tours_merged_cols if c not in redundant_cols ] tours_merged = tours_merged[tours_merged_cols] # - skims skim_hotel = SkimHotel(model_settings, network_los, trace_label) # - size_terms and alternatives alternatives = tour_destination_size_terms(land_use, size_terms, 'trip') # DataFrameMatrix alows us to treat dataframe as virtual a 2-D array, indexed by zone_id, purpose # e.g. size_terms.get(df.dest_zone_id, df.purpose) # returns a series of size_terms for each chooser's dest_zone_id and purpose with chooser index size_term_matrix = DataFrameMatrix(alternatives) # don't need size terms in alternatives, just zone_id index alternatives = alternatives.drop(alternatives.columns, axis=1) alternatives.index.name = model_settings['ALT_DEST_COL_NAME'] sample_list = [] # - process intermediate trips in ascending trip_num order intermediate = trips.trip_num < trips.trip_count if intermediate.any(): first_trip_num = trips[intermediate].trip_num.min() last_trip_num = trips[intermediate].trip_num.max() # iterate over trips in ascending trip_num order for trip_num in range(first_trip_num, last_trip_num + 1): nth_trips = trips[intermediate & (trips.trip_num == trip_num)] nth_trace_label = tracing.extend_trace_label( trace_label, 'trip_num_%s' % trip_num) locals_dict = {'network_los': network_los} locals_dict.update(config.get_model_constants(model_settings)) # - annotate nth_trips if preprocessor_settings: expressions.assign_columns( df=nth_trips, model_settings=preprocessor_settings, locals_dict=locals_dict, trace_label=nth_trace_label) logger.info("Running %s with %d trips", nth_trace_label, nth_trips.shape[0]) # - choose destination for nth_trips, segmented by primary_purpose choices_list = [] for primary_purpose, trips_segment in nth_trips.groupby( 'primary_purpose'): choices, destination_sample = choose_trip_destination( primary_purpose, trips_segment, alternatives, tours_merged, model_settings, want_logsums, want_sample_table, size_term_matrix, skim_hotel, estimator, chunk_size, trace_hh_id, trace_label=tracing.extend_trace_label( nth_trace_label, primary_purpose)) choices_list.append(choices) if want_sample_table: assert destination_sample is not None sample_list.append(destination_sample) destinations_df = pd.concat(choices_list) if fail_some_trips_for_testing: if len(destinations_df) > 0: destinations_df = destinations_df.drop( destinations_df.index[0]) failed_trip_ids = nth_trips.index.difference(destinations_df.index) if failed_trip_ids.any(): logger.warning( "%s sidelining %s trips without viable destination alternatives" % (nth_trace_label, failed_trip_ids.shape[0])) next_trip_ids = nth_trips.next_trip_id.reindex(failed_trip_ids) trips.loc[failed_trip_ids, 'failed'] = True trips.loc[failed_trip_ids, 'destination'] = -1 trips.loc[next_trip_ids, 'origin'] = trips.loc[failed_trip_ids].origin.values if len(destinations_df) == 0: assert failed_trip_ids.all() logger.warning( f"all {len(nth_trips)} {primary_purpose} trip_num {trip_num} trips failed" ) if len(destinations_df) > 0: # - assign choices to this trip's destinations # if estimator, then the choices will already have been overridden by trip_destination_simulate # because we need to overwrite choices before any failed choices are suppressed assign_in_place(trips, destinations_df.choice.to_frame('destination')) if want_logsums: assert 'logsum' in destinations_df.columns assign_in_place( trips, destinations_df.logsum.to_frame(logsum_column_name)) # - assign choice to next trip's origin destinations_df.index = nth_trips.next_trip_id.reindex( destinations_df.index) assign_in_place(trips, destinations_df.choice.to_frame('origin')) del trips['next_trip_id'] if len(sample_list) > 0: save_sample_df = pd.concat(sample_list) else: # this could happen if no intermediate trips, or if no saved sample desired save_sample_df = None return trips, save_sample_df
def add_size_tables(): """ inject tour_destination_size_terms tables for each model_selector (e.g. school, workplace) Size tables are pandas dataframes with locations counts for model_selector by zone and segment tour_destination_size_terms if using shadow pricing, we scale size_table counts to sample population (in which case, they have to be created while single-process) Scaling is problematic as it breaks household result replicability across sample sizes It also changes the magnitude of the size terms so if they are used as utilities in expression files, their importance will diminish relative to other utilities as the sample size decreases. Scaling makes most sense for a full sample in conjunction with shadow pricing, where shadow prices can be adjusted iteratively to bring modelled counts into line with desired (size table) counts. """ use_shadow_pricing = bool(config.setting('use_shadow_pricing')) shadow_settings = config.read_model_settings('shadow_pricing.yaml') shadow_pricing_models = shadow_settings.get('shadow_pricing_models') if shadow_pricing_models is None: logger.warning('shadow_pricing_models list not found in shadow_pricing settings') return # probably ought not scale if not shadow_pricing (breaks partial sample replicability) # but this allows compatability with existing CTRAMP behavior... scale_size_table = shadow_settings.get('SCALE_SIZE_TABLE', False) # shadow_pricing_models is dict of {<model_selector>: <model_name>} # since these are scaled to model size, they have to be created while single-process for model_selector, model_name in shadow_pricing_models.items(): model_settings = config.read_model_settings(model_name) assert model_selector == model_settings['MODEL_SELECTOR'] assert 'SEGMENT_IDS' in model_settings, f"missing SEGMENT_IDS setting in {model_name} model_settings" segment_ids = model_settings['SEGMENT_IDS'] chooser_table_name = model_settings['CHOOSER_TABLE_NAME'] chooser_segment_column = model_settings['CHOOSER_SEGMENT_COLUMN_NAME'] choosers_df = inject.get_table(chooser_table_name).to_frame() if 'CHOOSER_FILTER_COLUMN_NAME' in model_settings: choosers_df = \ choosers_df[choosers_df[model_settings['CHOOSER_FILTER_COLUMN_NAME']] != 0] # - raw_desired_size land_use = inject.get_table('land_use') size_terms = inject.get_injectable('size_terms') raw_size = tour_destination_size_terms(land_use, size_terms, model_selector) assert set(raw_size.columns) == set(segment_ids.keys()) if use_shadow_pricing or scale_size_table: # - scale size_table counts to sample population # scaled_size = zone_size * (total_segment_modeled / total_segment_desired) # segment scale factor (modeled / desired) keyed by segment_name segment_scale_factors = {} for c in raw_size: # number of zone demographics desired destination choices segment_desired_size = raw_size[c].astype(np.float64).sum() # number of synthetic population choosers in segment segment_chooser_count = \ (choosers_df[chooser_segment_column] == segment_ids[c]).sum() segment_scale_factors[c] = \ segment_chooser_count / np.maximum(segment_desired_size, 1) logger.info("add_desired_size_tables %s segment %s " "desired %s modeled %s scale_factor %s" % (chooser_table_name, c, segment_desired_size, segment_chooser_count, segment_scale_factors[c])) # FIXME - should we be rounding? scaled_size = (raw_size * segment_scale_factors).round() else: scaled_size = raw_size logger.debug(f"add_size_table {size_table_name(model_selector)} ({scaled_size.shape}) for {model_selector}") assert scaled_size.index.is_monotonic_increasing, \ f"size table {size_table_name(model_selector)} not is_monotonic_increasing" inject.add_table(size_table_name(model_selector), scaled_size)
def run_trip_destination( trips, tours_merged, chunk_size, trace_hh_id, trace_label): """ trip destination - main functionality separated from model step so it can be called iteratively Run the trip_destination model, assigning destinations for each (intermediate) trip (last trips already have a destination - either the tour primary destination or Home) Set trip destination and origin columns, and a boolean failed flag for any failed trips (destination for flagged failed trips will be set to -1) Parameters ---------- trips tours_merged chunk_size trace_hh_id trace_label Returns ------- """ model_settings = config.read_model_settings('trip_destination.yaml') preprocessor_settings = model_settings.get('preprocessor', None) logsum_settings = config.read_model_settings(model_settings['LOGSUM_SETTINGS']) land_use = inject.get_table('land_use') size_terms = inject.get_injectable('size_terms') # - initialize trip origin and destination to those of half-tour # (we will sequentially adjust intermediate trips origin and destination as we choose them) tour_destination = reindex(tours_merged.destination, trips.tour_id).astype(int) tour_origin = reindex(tours_merged.origin, trips.tour_id).astype(int) trips['destination'] = np.where(trips.outbound, tour_destination, tour_origin) trips['origin'] = np.where(trips.outbound, tour_origin, tour_destination) trips['failed'] = False trips = trips.sort_index() trips['next_trip_id'] = np.roll(trips.index, -1) trips.next_trip_id = trips.next_trip_id.where(trips.trip_num < trips.trip_count, 0) # - filter tours_merged (AFTER copying destination and origin columns to trips) # tours_merged is used for logsums, we filter it here upfront to save space and time tours_merged_cols = logsum_settings['TOURS_MERGED_CHOOSER_COLUMNS'] if 'REDUNDANT_TOURS_MERGED_CHOOSER_COLUMNS' in model_settings: redundant_cols = model_settings['REDUNDANT_TOURS_MERGED_CHOOSER_COLUMNS'] tours_merged_cols = [c for c in tours_merged_cols if c not in redundant_cols] tours_merged = tours_merged[tours_merged_cols] # - skims skims = wrap_skims(model_settings) # - size_terms and alternatives alternatives = tour_destination_size_terms(land_use, size_terms, 'trip') # DataFrameMatrix alows us to treat dataframe as virtual a 2-D array, indexed by TAZ, purpose # e.g. size_terms.get(df.dest_taz, df.purpose) # returns a series of size_terms for each chooser's dest_taz and purpose with chooser index size_term_matrix = DataFrameMatrix(alternatives) # don't need size terms in alternatives, just TAZ index alternatives = alternatives.drop(alternatives.columns, axis=1) alternatives.index.name = model_settings['ALT_DEST'] # - process intermediate trips in ascending trip_num order intermediate = trips.trip_num < trips.trip_count if intermediate.any(): first_trip_num = trips[intermediate].trip_num.min() last_trip_num = trips[intermediate].trip_num.max() # iterate over trips in ascending trip_num order for trip_num in range(first_trip_num, last_trip_num + 1): nth_trips = trips[intermediate & (trips.trip_num == trip_num)] nth_trace_label = tracing.extend_trace_label(trace_label, 'trip_num_%s' % trip_num) # - annotate nth_trips if preprocessor_settings: expressions.assign_columns( df=nth_trips, model_settings=preprocessor_settings, locals_dict=config.get_model_constants(model_settings), trace_label=nth_trace_label) logger.info("Running %s with %d trips", nth_trace_label, nth_trips.shape[0]) # - choose destination for nth_trips, segmented by primary_purpose choices_list = [] for primary_purpose, trips_segment in nth_trips.groupby('primary_purpose'): choices = choose_trip_destination( primary_purpose, trips_segment, alternatives, tours_merged, model_settings, size_term_matrix, skims, chunk_size, trace_hh_id, trace_label=tracing.extend_trace_label(nth_trace_label, primary_purpose)) choices_list.append(choices) destinations = pd.concat(choices_list) failed_trip_ids = nth_trips.index.difference(destinations.index) if failed_trip_ids.any(): logger.warning("%s sidelining %s trips without viable destination alternatives" % (nth_trace_label, failed_trip_ids.shape[0])) next_trip_ids = nth_trips.next_trip_id.reindex(failed_trip_ids) trips.loc[failed_trip_ids, 'failed'] = True trips.loc[failed_trip_ids, 'destination'] = -1 trips.loc[next_trip_ids, 'origin'] = trips.loc[failed_trip_ids].origin.values # - assign choices to these trips destinations and to next trips origin assign_in_place(trips, destinations.to_frame('destination')) destinations.index = nth_trips.next_trip_id.reindex(destinations.index) assign_in_place(trips, destinations.to_frame('origin')) del trips['next_trip_id'] return trips
def add_size_tables(): """ inject tour_destination_size_terms tables for each model_selector (e.g. school, workplace) Size tables are pandas dataframes with locations counts for model_selector by zone and segment tour_destination_size_terms if using shadow pricing, we scale size_table counts to sample population (in which case, they have to be created while single-process) Scaling is problematic as it breaks household result replicability across sample sizes It also changes the magnitude of the size terms so if they are used as utilities in expression files, their importance will diminish relative to other utilities as the sample size decreases. Scaling makes most sense for a full sample in conjunction with shadow pricing, where shadow prices can be adjusted iteratively to bring modelled counts into line with desired (size table) counts. """ use_shadow_pricing = bool(config.setting('use_shadow_pricing')) shadow_settings = config.read_model_settings('shadow_pricing.yaml') shadow_pricing_models = shadow_settings['shadow_pricing_models'] # probably ought not scale if not shadow_pricing (breaks partial sample replicability) # but this allows compatability with existing CTRAMP behavior... scale_size_table = shadow_settings.get('SCALE_SIZE_TABLE', False) if shadow_pricing_models is None: logger.warning('shadow_pricing_models list not found in shadow_pricing settings') return # shadow_pricing_models is dict of {<model_selector>: <model_name>} # since these are scaled to model size, they have to be created while single-process for model_selector, model_name in iteritems(shadow_pricing_models): model_settings = config.read_model_settings(model_name) assert model_selector == model_settings['MODEL_SELECTOR'] segment_ids = model_settings['SEGMENT_IDS'] chooser_table_name = model_settings['CHOOSER_TABLE_NAME'] chooser_segment_column = model_settings['CHOOSER_SEGMENT_COLUMN_NAME'] choosers_df = inject.get_table(chooser_table_name).to_frame() if 'CHOOSER_FILTER_COLUMN_NAME' in model_settings: choosers_df = \ choosers_df[choosers_df[model_settings['CHOOSER_FILTER_COLUMN_NAME']] != 0] # - raw_desired_size land_use = inject.get_table('land_use') size_terms = inject.get_injectable('size_terms') raw_size = tour_destination_size_terms(land_use, size_terms, model_selector) assert set(raw_size.columns) == set(segment_ids.keys()) if use_shadow_pricing or scale_size_table: inject.add_table('raw_' + size_table_name(model_selector), raw_size) # - scale size_table counts to sample population # scaled_size = zone_size * (total_segment_modeled / total_segment_desired) # segment scale factor (modeled / desired) keyed by segment_name segment_scale_factors = {} for c in raw_size: # number of zone demographics desired destination choices segment_desired_size = raw_size[c].astype(np.float64).sum() # number of synthetic population choosers in segment segment_chooser_count = \ (choosers_df[chooser_segment_column] == segment_ids[c]).sum() segment_scale_factors[c] = \ segment_chooser_count / np.maximum(segment_desired_size, 1) logger.info("add_desired_size_tables %s segment %s " "desired %s modeled %s scale_factor %s" % (chooser_table_name, c, segment_desired_size, segment_chooser_count, segment_scale_factors[c])) # FIXME - should we be rounding? scaled_size = (raw_size * segment_scale_factors).round() else: scaled_size = raw_size inject.add_table(size_table_name(model_selector), scaled_size)
def atwork_subtour_destination(tours, persons_merged, skim_dict, skim_stack, land_use, size_terms, chunk_size, trace_hh_id): trace_label = 'atwork_subtour_destination' model_settings_file_name = 'atwork_subtour_destination.yaml' model_settings = config.read_model_settings(model_settings_file_name) destination_column_name = 'destination' logsum_column_name = model_settings.get('DEST_CHOICE_LOGSUM_COLUMN_NAME') want_logsums = logsum_column_name is not None sample_table_name = model_settings.get('DEST_CHOICE_SAMPLE_TABLE_NAME') want_sample_table = config.setting( 'want_dest_choice_sample_tables') and sample_table_name is not None persons_merged = persons_merged.to_frame() tours = tours.to_frame() subtours = tours[tours.tour_category == 'atwork'] # interaction_sample_simulate insists choosers appear in same order as alts subtours = subtours.sort_index() # - if no atwork subtours if subtours.shape[0] == 0: tracing.no_results('atwork_subtour_destination') return estimator = estimation.manager.begin_estimation( 'atwork_subtour_destination') if estimator: estimator.write_coefficients( simulate.read_model_coefficients(model_settings)) # estimator.write_spec(model_settings, tag='SAMPLE_SPEC') estimator.write_spec(model_settings, tag='SPEC') estimator.set_alt_id(model_settings["ALT_DEST_COL_NAME"]) estimator.write_table(inject.get_injectable('size_terms'), 'size_terms', append=False) estimator.write_table(inject.get_table('land_use').to_frame(), 'landuse', append=False) estimator.write_model_settings(model_settings, model_settings_file_name) destination_size_terms = tour_destination_size_terms( land_use, size_terms, 'atwork') destination_sample_df = atwork_subtour_destination_sample( subtours, persons_merged, model_settings, skim_dict, destination_size_terms, estimator=estimator, chunk_size=chunk_size, trace_label=tracing.extend_trace_label(trace_label, 'sample')) destination_sample_df = atwork_subtour_destination_logsums( persons_merged, destination_sample_df, model_settings, skim_dict, skim_stack, chunk_size=chunk_size, trace_label=tracing.extend_trace_label(trace_label, 'logsums')) choices_df = atwork_subtour_destination_simulate( subtours, persons_merged, destination_sample_df, want_logsums, model_settings, skim_dict, destination_size_terms, estimator=estimator, chunk_size=chunk_size, trace_label=tracing.extend_trace_label(trace_label, 'simulate')) if estimator: estimator.write_choices(choices_df['choice']) choices_df['choice'] = estimator.get_survey_values( choices_df['choice'], 'tours', 'destination') estimator.write_override_choices(choices_df['choice']) estimator.end_estimation() subtours[destination_column_name] = choices_df['choice'] assign_in_place(tours, subtours[[destination_column_name]]) if want_logsums: subtours[logsum_column_name] = choices_df['logsum'] assign_in_place(tours, subtours[[logsum_column_name]]) pipeline.replace_table("tours", tours) if want_sample_table: # FIXME - sample_table assert len(destination_sample_df.index.unique()) == len(choices_df) destination_sample_df.set_index(model_settings['ALT_DEST_COL_NAME'], append=True, inplace=True) pipeline.extend_table(sample_table_name, destination_sample_df) tracing.print_summary(destination_column_name, subtours[destination_column_name], describe=True) if trace_hh_id: tracing.trace_df(tours, label='atwork_subtour_destination', columns=['destination'])