diff --git a/README.md b/README.md index b37beb8..2ac7c66 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ uv sync --locked To run the model with test data, use the following command: ```bash -uv run activitysim run -c model/configs_mp -c model/configs -d model/data -o model/output +uv run activitysim run -c model/configs_mp -c model/configs -d model/data -o model/output --ext extensions ``` ## Contents diff --git a/extensions/__init__.py b/extensions/__init__.py new file mode 100644 index 0000000..ef70e01 --- /dev/null +++ b/extensions/__init__.py @@ -0,0 +1,2 @@ +from . import telework_arrangement +from . import telework_duration diff --git a/extensions/telework_arrangement.py b/extensions/telework_arrangement.py new file mode 100644 index 0000000..823749e --- /dev/null +++ b/extensions/telework_arrangement.py @@ -0,0 +1,151 @@ +# ActivitySim +# See full license in LICENSE.txt. +from __future__ import annotations + +import logging + +import pandas as pd + +from activitysim.core import ( + config, + estimation, + expressions, + simulate, + tracing, + workflow, +) +from activitysim.core.configuration.logit import LogitComponentSettings + +logger = logging.getLogger("activitysim") + + +class TeleworkArrangementSettings(LogitComponentSettings, extra="forbid"): + """ + Settings for the `telework_arrangement` component. + """ + + CHOOSER_FILTER_COLUMN_NAME: str = "is_worker" + """Column name in the dataframe to represent worker.""" + + HAS_IN_HOME_WORK_ACTIVITY_ALT: int = 0 + """The alternative index for having in-home work activity on the simulation day.""" + + +@workflow.step +def telework_arrangement( + state: workflow.State, + persons_merged: pd.DataFrame, + persons: pd.DataFrame, + model_settings: TeleworkArrangementSettings | None = None, + model_settings_file_name: str = "telework_arrangement.yaml", + trace_label: str = "telework_arrangement", +) -> None: + """ + This model predicts the telework arrangement on the simulation day for all workers. + The alternatives are whether or not a worker has in-home telework activities on the simulation day: + The result is a new column in the persons table, "has_in_home_work_activity": True or False + + Parameters + ---------- + state : workflow.State + persons_merged : DataFrame + This represents the 'choosers' table for this component. + persons : DataFrame + The original persons table is referenced so the telework arrangement column + can be appended to it. + model_settings : TeleworkArrangementSettings, optional + The settings used in this model component. If not provided, they are + loaded out of the configs directory YAML file referenced by + the `model_settings_file_name` argument. + model_settings_file_name : str, default "telework_arrangement.yaml" + This is where model setting are found if `model_settings` is not given + explicitly. The same filename is also used to write settings files to + the estimation data bundle in estimation mode. + trace_label : str, default "telework_arrangement" + This label is used for various tracing purposes. + """ + + if model_settings is None: + model_settings = TeleworkArrangementSettings.read_settings_file( + state.filesystem, + model_settings_file_name, + ) + + chooser_filter_column_name = model_settings.CHOOSER_FILTER_COLUMN_NAME + choosers = persons_merged[persons_merged[chooser_filter_column_name]] + + logger.info("Running %s with %d persons", trace_label, len(choosers)) + + estimator = estimation.manager.begin_estimation(state, "telework_arrangement") + + constants = config.get_model_constants(model_settings) + + expressions.annotate_preprocessors( + state, + df=choosers, + locals_dict=constants, + skims=None, + model_settings=model_settings, + trace_label=trace_label, + ) + + model_spec = state.filesystem.read_model_spec(file_name=model_settings.SPEC) + coefficients_df = state.filesystem.read_model_coefficients(model_settings) + model_spec = simulate.eval_coefficients( + state, model_spec, coefficients_df, estimator + ) + nest_spec = config.get_logit_model_settings(model_settings) + + if estimator: + estimator.write_model_settings(model_settings, model_settings_file_name) + estimator.write_spec(model_settings) + estimator.write_coefficients(coefficients_df, model_settings) + estimator.write_choosers(choosers) + + choices = simulate.simple_simulate( + state, + choosers=choosers, + spec=model_spec, + nest_spec=nest_spec, + locals_d=constants, + trace_label=trace_label, + trace_choice_name="telework_arrangement", + estimator=estimator, + compute_settings=model_settings.compute_settings, + ) + + has_in_home_work_activity_alt = model_settings.HAS_IN_HOME_WORK_ACTIVITY_ALT + choices = choices == has_in_home_work_activity_alt + + if estimator: + estimator.write_choices(choices) + choices = estimator.get_survey_values( + choices, + "persons", + "has_in_home_work_activity", + ) + estimator.write_override_choices(choices) + estimator.end_estimation() + + persons["has_in_home_work_activity"] = ( + choices.reindex(persons.index).fillna(0).astype(bool) + ) + + state.add_table("persons", persons) + + tracing.print_summary( + "telework_arrangement.has_in_home_work_activity", + persons.has_in_home_work_activity, + value_counts=True, + ) + + if state.settings.trace_hh_id: + state.tracing.trace_df(persons, label=trace_label, warn_if_empty=True) + + expressions.annotate_tables( + state, + locals_dict=constants, + skims=None, + model_settings=model_settings, + trace_label=trace_label, + ) diff --git a/extensions/telework_duration.py b/extensions/telework_duration.py new file mode 100644 index 0000000..6522bbc --- /dev/null +++ b/extensions/telework_duration.py @@ -0,0 +1,287 @@ +# ActivitySim +# See full license in LICENSE.txt. +from __future__ import annotations + +import logging +from typing import Literal + +import pandas as pd + +from activitysim.core import ( + config, + estimation, + expressions, + logit, + simulate, + tracing, + workflow, +) +from activitysim.core.configuration.logit import LogitComponentSettings + +logger = logging.getLogger("activitysim") + + +class TeleworkDurationSettings(LogitComponentSettings): + """ + Settings for the `telework_duration` component. + """ + + CHOICE_MODEL: Literal["PROBABILISTIC", "MNL"] = "PROBABILISTIC" + """Choice model type to use for telework duration.""" + + CHOOSER_FILTER_COLUMN_NAME: str = "has_in_home_work_activity" + """Column name in chooser table to represent workers with in-home work activity on the simulation day.""" + + DURATION_CATEGORY_COLUMN_NAME: str = "telework_duration_category" + """Persons column for the chosen telework duration category.""" + + DURATION_HOURS_COLUMN_NAME: str = "telework_duration_hours" + """Persons column for telework duration in hours.""" + + ALTS: str = "telework_duration_alts.csv" + """Alternatives file with duration category to hour mapping.""" + + ALT_NAME_COLUMN: str = "alt" + """Alternatives file column containing category names.""" + + ALT_DURATION_COLUMN: str = "duration_hours" + """Alternatives file column containing duration values in hours.""" + + SPEC: str = "telework_duration.csv" + """MNL utility specification file.""" + + COEFFICIENTS: str | None = "telework_duration_coeffs.csv" + """MNL coefficients file.""" + + LOGIT_TYPE: Literal["MNL", "NL"] = "MNL" + """Logit type when running MNL mode.""" + + NESTS: dict | None = None + """Nest settings for NL mode, if ever used.""" + + PROBS_SPEC: str = "telework_duration_probs.csv" + """Probabilistic choice lookup table.""" + + PROBS_JOIN_COLS: list[str] | None = None + """Columns to join choosers to probability table.""" + + CONSTANTS: dict = {} + """Named constants usable in preprocessors and expressions.""" + + preprocessor: dict | list[dict] | None = None + """Chooser preprocessor settings.""" + + +def _load_alternatives(state: workflow.State, model_settings: TeleworkDurationSettings): + alts = simulate.read_model_alts(state, model_settings.ALTS, set_index=None) + + alt_name_col = model_settings.ALT_NAME_COLUMN + alt_duration_col = model_settings.ALT_DURATION_COLUMN + if alt_name_col not in alts.columns or alt_duration_col not in alts.columns: + raise RuntimeError( + "telework_duration alternatives file must include " + f"'{alt_name_col}' and '{alt_duration_col}' columns" + ) + + alts = alts[[alt_name_col, alt_duration_col]].copy() + alts[alt_name_col] = alts[alt_name_col].astype(str) + alts = alts.drop_duplicates(subset=[alt_name_col]) + return alts + + +def _simulate_probabilistic( + state: workflow.State, + choosers: pd.DataFrame, + model_settings: TeleworkDurationSettings, + trace_label: str, +) -> pd.Series: + probs = pd.read_csv( + state.filesystem.get_config_file_path(model_settings.PROBS_SPEC), comment="#" + ) + probs_join_cols = model_settings.PROBS_JOIN_COLS or [] + + if probs_join_cols: + chooser_probs = pd.merge( + choosers.reset_index(), + probs, + on=probs_join_cols, + how="left", + ).set_index(choosers.index.name) + else: + if probs.shape[0] != 1: + raise RuntimeError( + "telework_duration probabilistic mode requires a single-row PROBS_SPEC " + "when PROBS_JOIN_COLS is not provided" + ) + chooser_probs = pd.concat([probs] * len(choosers), ignore_index=True) + chooser_probs.index = choosers.index + + prob_cols = [c for c in probs.columns if c not in probs_join_cols] + if not prob_cols: + raise RuntimeError( + "telework_duration probabilistic mode found no probability columns" + ) + + chooser_probs = chooser_probs[prob_cols].fillna(0) + row_sums = chooser_probs.sum(axis=1) + if (row_sums <= 0).any(): + raise RuntimeError( + "telework_duration probabilistic mode found choosers with no positive " + "probability mass" + ) + + chooser_probs = chooser_probs.div(row_sums, axis=0) + + choices, _ = logit.make_choices( + state, + chooser_probs, + trace_label=trace_label, + trace_choosers=choosers, + ) + + category_choices = pd.Series(prob_cols).loc[choices].astype(str) + category_choices.index = choices.index + + return category_choices + + +@workflow.step +def telework_duration( + state: workflow.State, + persons_merged: pd.DataFrame, + persons: pd.DataFrame, + model_settings: TeleworkDurationSettings | None = None, + model_settings_file_name: str = "telework_duration.yaml", + trace_label: str = "telework_duration", +) -> None: + """ + Simulate daily in-home work duration for workers with in-home work activity. + + This model applies only to workers where `has_in_home_work_activity` is True + from the telework arrangement model. It supports either: + - probabilistic sampling from `PROBS_SPEC`, or + - MNL simulation from `SPEC` and `COEFFICIENTS`. + """ + + if model_settings is None: + model_settings = TeleworkDurationSettings.read_settings_file( + state.filesystem, + model_settings_file_name, + ) + + chooser_filter_col = model_settings.CHOOSER_FILTER_COLUMN_NAME + + choosers = persons_merged[persons_merged[chooser_filter_col]] + + logger.info("Running %s with %d persons", trace_label, len(choosers)) + + category_col = model_settings.DURATION_CATEGORY_COLUMN_NAME + duration_col = model_settings.DURATION_HOURS_COLUMN_NAME + alts = _load_alternatives(state, model_settings) + category_dtype = pd.api.types.CategoricalDtype( + categories=alts[model_settings.ALT_NAME_COLUMN].tolist() + [""], + ordered=False, + ) + + # Default values for non-eligible persons. + persons[category_col] = pd.Series( + pd.Categorical([""] * len(persons), dtype=category_dtype), + index=persons.index, + ) + persons[duration_col] = 0.0 + + if choosers.empty: + state.add_table("persons", persons) + tracing.print_summary(category_col, persons[category_col], value_counts=True) + tracing.print_summary(duration_col, persons[duration_col], value_counts=True) + return + + estimator = estimation.manager.begin_estimation(state, "telework_duration") + constants = config.get_model_constants(model_settings) + + expressions.annotate_preprocessors( + state, + df=choosers, + locals_dict=constants, + skims=None, + model_settings=model_settings, + trace_label=trace_label, + ) + + choice_model = model_settings.CHOICE_MODEL + + if choice_model == "MNL": + model_spec = state.filesystem.read_model_spec(file_name=model_settings.SPEC) + coefficients_df = state.filesystem.read_model_coefficients(model_settings) + model_spec = simulate.eval_coefficients( + state, model_spec, coefficients_df, estimator + ) + nest_spec = config.get_logit_model_settings(model_settings) + + if estimator: + estimator.write_model_settings(model_settings, model_settings_file_name) + estimator.write_spec(model_settings) + estimator.write_coefficients(coefficients_df, model_settings) + estimator.write_choosers(choosers) + + raw_choices = simulate.simple_simulate( + state, + choosers=choosers, + spec=model_spec, + nest_spec=nest_spec, + locals_d=constants, + trace_label=trace_label, + trace_choice_name=category_col, + estimator=estimator, + compute_settings=model_settings.compute_settings, + ) + category_choices = pd.Series( + model_spec.columns[raw_choices.values], index=raw_choices.index + ).astype(category_dtype) + else: + if estimator: + estimator.write_model_settings(model_settings, model_settings_file_name) + estimator.write_spec(model_settings, tag="PROBS_SPEC") + estimator.write_choosers(choosers) + category_choices = _simulate_probabilistic( + state, + choosers, + model_settings, + trace_label, + ).astype(category_dtype) + + alt_to_duration = alts.set_index(model_settings.ALT_NAME_COLUMN)[ + model_settings.ALT_DURATION_COLUMN + ] + + if estimator: + estimator.write_choices(category_choices) + category_choices = estimator.get_survey_values( + category_choices, + "persons", + category_col, + ) + category_choices = category_choices.astype(category_dtype) + estimator.write_override_choices(category_choices) + estimator.end_estimation() + + duration_choices = category_choices.map(alt_to_duration).fillna(0.0).astype(float) + + persons.loc[category_choices.index, category_col] = category_choices + persons.loc[duration_choices.index, duration_col] = duration_choices + + state.add_table("persons", persons) + + tracing.print_summary(category_col, persons[category_col], value_counts=True) + tracing.print_summary(duration_col, persons[duration_col], value_counts=True) + + if state.settings.trace_hh_id: + state.tracing.trace_df(persons, label=trace_label, warn_if_empty=True) + + expressions.annotate_tables( + state, + locals_dict=constants, + skims=None, + model_settings=model_settings, + trace_label=trace_label, + ) diff --git a/model/configs/annotate_persons.csv b/model/configs/annotate_persons.csv index 6162b53..ae7a2b6 100644 --- a/model/configs/annotate_persons.csv +++ b/model/configs/annotate_persons.csv @@ -34,5 +34,7 @@ school_segment highschool,school_segment,"np.where(is_highschool, SCHOOL_SEGMENT school_segment university,school_segment,"np.where(is_university, SCHOOL_SEGMENT_UNIV, school_segment).astype(np.int8)" #,, is_worker,is_worker,"persons.pemploy.isin([PEMPLOY_FULL, PEMPLOY_PART])" +#temp out of home worker variable will be overwritten in work_from_home model,, +is_out_of_home_worker,is_out_of_home_worker,is_worker #,, home_zone_id,home_zone_id,"reindex(households.home_zone_id, persons.household_id)" diff --git a/model/configs/cdap_fixed_relative_proportions.csv b/model/configs/cdap_fixed_relative_proportions.csv index 788f398..4c36f03 100644 --- a/model/configs/cdap_fixed_relative_proportions.csv +++ b/model/configs/cdap_fixed_relative_proportions.csv @@ -1,6 +1,8 @@ Description,Expression,M,N,H -Full-time worker,ptype == 1,0.79647,0.09368,0.10985 -Part-time worker,ptype == 2,0.61678,0.25757,0.12565 +Full-time worker,(ptype == 1) & ~work_from_home,0.79647,0.09368,0.10985 +Full-time worker,(ptype == 1) & work_from_home,0,0.460276126,0.539723874 +Part-time worker,(ptype == 2) & ~work_from_home,0.61678,0.25757,0.12565 +Part-time worker,(ptype == 2) & work_from_home,0,0.672120453,0.327879547 University student,ptype == 3,0.69229,0.15641,0.1513 Non-working adult,ptype == 4,0,0.67169,0.32831 Retired,ptype == 5,0,0.54295,0.45705 diff --git a/model/configs/cdap_indiv_and_hhsize1.csv b/model/configs/cdap_indiv_and_hhsize1.csv index 0a666e6..1888d13 100644 --- a/model/configs/cdap_indiv_and_hhsize1.csv +++ b/model/configs/cdap_indiv_and_hhsize1.csv @@ -49,3 +49,5 @@ University student interaction with off-peak accessibility to retail,(ptype == 3 Driving-age child who is in school interaction with off-peak accessibility to retail,(ptype == 6) * auOpRetail,,coef_child_who_is_in_school_or_too_young_for_school_interaction_with_off_peak_accessibility_to_retail_N, Pre-driving-age child who is in school interaction with off-peak accessibility to retail,(ptype == 7) * auOpRetail,,coef_child_who_is_in_school_or_too_young_for_school_interaction_with_off_peak_accessibility_to_retail_N, Pre-driving-age child who is too young for school interaction with off-peak accessibility to retail,(ptype == 8) * auOpRetail,,coef_child_who_is_in_school_or_too_young_for_school_interaction_with_off_peak_accessibility_to_retail_N, +Full time worker works from home, (ptype == 1) & (work_from_home),-999,0,0 +Part time worker works from home, (ptype == 2) & (work_from_home),-999,0,0 \ No newline at end of file diff --git a/model/configs/settings.yaml b/model/configs/settings.yaml index 7337c65..e7a0666 100644 --- a/model/configs/settings.yaml +++ b/model/configs/settings.yaml @@ -46,6 +46,7 @@ input_table_list: - pemploy - pstudent - ptype + - naics_code # # land_use (table index 'zone_id') # @@ -175,11 +176,15 @@ models: - initialize_landuse - initialize_households - compute_accessibility + - work_from_home - school_location - workplace_location - auto_ownership_simulate - free_parking + - telecommute_frequency - cdap_simulate + - telework_arrangement + - telework_duration - mandatory_tour_frequency - mandatory_tour_scheduling - non_mandatory_tour_frequency diff --git a/model/configs/telecommute_frequency.csv b/model/configs/telecommute_frequency.csv new file mode 100644 index 0000000..37b799e --- /dev/null +++ b/model/configs/telecommute_frequency.csv @@ -0,0 +1,28 @@ +Label,Description,Expression,No_Telecommute,1_day_week,2_days_week,3_days_week,4_days_week +util_HasChildren0to5,Has children 0 to 5 years old,@df.num_young_children>0,,coef_HasChildren0to5_1day,coef_HasChildren0to5_234day,coef_HasChildren0to5_234day,coef_HasChildren0to5_234day +util_HasChildren6to12,Has children 6 to 12 years old,@df.num_children_6_to_12>0,,coef_HasChildren6to12_1day,coef_HasChildren6to12_23day,coef_HasChildren6to12_23day,coef_HasChildren6to12_4day +util_OneAdultInHH,One adult in hh,@df.num_adults==1,,coef_OneAdultInHH_1day,coef_OneAdultInHH_23day,coef_OneAdultInHH_23day,coef_OneAdultInHH_4day +util_Female,female,@df.female,,coef_Female_1234day,coef_Female_1234day,coef_Female_1234day,coef_Female_1234day +util_PartTimeWorker,Part-time worker,@df.pemploy==2,,coef_PartTimeWorker_1234day,coef_PartTimeWorker_1234day,coef_PartTimeWorker_1234day,coef_PartTimeWorker_1234day +util_Income60to100k,Income 60-100k,"@df.income.between(60000, 100000)",,coef_Income60to100k_1day,coef_Income60to100k_23day,coef_Income60to100k_23day,coef_Income60to100k_4day +util_Income100to150k,Income 100-150k,"@df.income.between(100000, 150000)",,coef_Income100to150k_1day,coef_Income100to150k_234day,coef_Income100to150k_234day,coef_Income100to150k_234day +util_Income150kplus,Income 150k+,@df.income > 150000,,coef_Income150kplus_1day,coef_Income150kplus_23day,coef_Income150kplus_23day,coef_Income150kplus_4day +util_0Autos,0 Autos,@df.auto_ownership==0,,coef_0Autos_1day,coef_0Autos_234day,coef_0Autos_234day,coef_0Autos_234day +util_1Auto,1 Auto,@df.auto_ownership==1,,coef_1Auto_1day,coef_1Auto_234day,coef_1Auto_234day,coef_1Auto_234day +util_3plusAutos,3+ Autos,@df.auto_ownership>=3,,coef_3plusAutos_1day,coef_3plusAutos_23day,coef_3plusAutos_23day,coef_3plusAutos_4day +util_DistanceToWork,Distance to work,@df.distance_to_work,,coef_DistanceToWork_1day,coef_DistanceToWork_234day,coef_DistanceToWork_234day,coef_DistanceToWork_234day +util_accomodation,Accomodation industry,@df.naics_code==721,,coef_accomodation_1234day,coef_accomodation_1234day,coef_accomodation_1234day,coef_accomodation_1234day +util_agriculture,Agriculture industry,@df.naics_code==11,,coef_agriculture_1234day,coef_agriculture_1234day,coef_agriculture_1234day,coef_agriculture_1234day +util_business_srv,Business services industry,@df.naics_code==54,,coef_business_srv_1day,coef_business_srv_23day,coef_business_srv_23day,coef_business_srv_4day +util_construction,Construction industry,@df.naics_code==23,,coef_construction_1day,coef_construction_234day,coef_construction_234day,coef_construction_234day +util_education,Education industry,@df.naics_code==61,,coef_education_1234day,coef_education_1234day,coef_education_1234day,coef_education_1234day +util_entertainment,Entertainment industry,@df.naics_code==71,,coef_entertainment_1day,coef_entertainment_23day,coef_entertainment_23day,coef_entertainment_4day +util_food_srv,Food services industry,@df.naics_code==722,,coef_food_srv_1234day,coef_food_srv_1234day,coef_food_srv_1234day,coef_food_srv_1234day +util_government,Government industry,@df.naics_code==92,,coef_government_1day,coef_government_234day,coef_government_234day,coef_government_234day +util_healthcare,Healthcare industry,@df.naics_code==62,,coef_healthcare_1234day,coef_healthcare_1234day,coef_healthcare_1234day,coef_healthcare_1234day +util_manufacturing,Manufacturing industry,"@df.naics_code.isin([31,32,33])",,coef_manufacturing_1day,coef_manufacturing_234day,coef_manufacturing_234day,coef_manufacturing_234day +util_mgmt_srv,Management services industry,@df.naics_code==55,,coef_mgmt_srv_1day,coef_mgmt_srv_23day,coef_mgmt_srv_23day,coef_mgmt_srv_4day +util_military,Miliary industry,@df.naics_code==9000,,coef_military_1day,coef_military_234day,coef_military_234day,coef_military_234day +util_retail,Retail industry,"@df.naics_code.isin([44,45])",,coef_retail_1day,coef_retail_23day,coef_retail_23day,coef_retail_4day +util_asc,Alternative specific constant,1,,asc_1day,asc_23day,asc_23day,asc_4day +util_split_2_3_days_constant, Constant for splitting 2_3_days telecommute frequency,@np.log(0.5),,,1,1, diff --git a/model/configs/telecommute_frequency.yaml b/model/configs/telecommute_frequency.yaml new file mode 100644 index 0000000..162066e --- /dev/null +++ b/model/configs/telecommute_frequency.yaml @@ -0,0 +1,9 @@ + +# borrowed from free parking model + +SPEC: telecommute_frequency.csv +COEFFICIENTS: telecommute_frequency_coeffs.csv + +#LOGIT_TYPE: NL +LOGIT_TYPE: MNL + diff --git a/model/configs/telecommute_frequency_coeffs.csv b/model/configs/telecommute_frequency_coeffs.csv new file mode 100644 index 0000000..262b771 --- /dev/null +++ b/model/configs/telecommute_frequency_coeffs.csv @@ -0,0 +1,85 @@ +coefficient_name,value,constrain +coef_Services_1day,0.0,F +coef_SalesOffice_1day,0.0,F +coef_ResourceConstruct_1day,0.0,F +coef_TransportMat_1day,0.0,F +coef_HasChildren0to5_1day,0.0,T +coef_HasChildren6to12_1day,0.0,T +coef_OneAdultInHH_1day,0.2307987638695857,F +coef_2plusAdultsInHH_1day,0.0,F +coef_Female_1234day,0.0,T +coef_PartTimeWorker_1234day,-0.1647657054647985,F +coef_PaysToPark_1day,0.0,F +coef_Income60to100k_1day,0.3549582138983352,F +coef_Income100to150k_1day,0.0,T +coef_Income150kplus_1day,0.0,T +coef_0Autos_1day,0.0,T +coef_1Auto_1day,-0.2037126296304744,F +coef_3plusAutos_1day,-0.2366548258122986,F +coef_DistanceToWork_1day,0.0065817284180437,F +coef_Services_23day,0.0,F +coef_SalesOffice_23day,0.0,F +coef_ResourceConstruct_23day,0.0,F +coef_TransportMat_23day,0.0,F +coef_HasChildren0to5_234day,-0.3282697175018726,F +coef_HasChildren6to12_23day,0.0,T +coef_OneAdultInHH_23day,0.0,T +coef_2plusAdultsInHH_23day,0.0,F +coef_PaysToPark_23day,0.0,F +coef_Income60to100k_23day,0.0,T +coef_Income100to150k_234day,0.0,T +coef_Income150kplus_23day,0.0,T +coef_0Autos_234day,0.0,T +coef_1Auto_234day,0.0,T +coef_3plusAutos_23day,-0.2673431713437322,F +coef_DistanceToWork_234day,0.011270292473366,F +coef_HasChildren6to12_4day,0.0,T +coef_OneAdultInHH_4day,0.0,T +coef_2plusAdultsInHH_4day,0.0,F +coef_PaysToPark_4day,0.0,F +coef_Income60to100k_4day,0.0,T +coef_Income100to150k_4day,0.0,T +coef_Income150kplus_4day,0.0,T +coef_3plusAutos_4day,-0.5086281851316395,F +coef_accomodation_1234day,-1.1672033007413072,F +coef_agriculture_1234day,0.0,T +coef_business_srv_1day,0.8048779013830238,F +coef_construction_1day,0.0,T +coef_education_1234day,-0.0387511815872183,F +coef_entertainment_1day,0.0,T +coef_food_srv_1day,0.0,F +coef_government_1day,0.4503818768273094,F +coef_healthcare_1day,0.0,F +coef_manufacturing_1day,0.0,T +coef_mgmt_srv_1day,0.0,T +coef_military_1day,0.0,T +coef_retail_1day,-0.8320341753416274,F +coef_accomodation_23day,0.0,F +coef_agriculture_23day,0.0,F +coef_business_srv_23day,0.9228435480770156,F +coef_construction_234day,0.0,T +coef_education_23day,0.0,F +coef_entertainment_23day,0.0,T +coef_food_srv_1234day,-1.6057467385857538,F +coef_government_234day,0.0,T +coef_healthcare_1234day,-0.2529974908266847,F +coef_manufacturing_234day,0.0,T +coef_mgmt_srv_23day,0.5723496481424273,F +coef_military_234day,-0.512646790150494,F +coef_retail_23day,-0.7576291501959187,F +coef_accomodation_4day,0.0,F +coef_agriculture_4day,0.0,F +coef_business_srv_4day,1.117777186998694,F +coef_construction_4day,0.0,F +coef_education_4day,0.0,F +coef_entertainment_4day,0.0,T +coef_food_srv_4day,0.0,F +coef_government_4day,0.0,F +coef_healthcare_4day,0.0,F +coef_manufacturing_4day,0.0,F +coef_mgmt_srv_4day,0.970302573177628,F +coef_military_4day,0.0,F +coef_retail_4day,-2.2338993784519388,F +asc_1day,-2.7588707215613267,F +asc_23day,-1.7439620734289143,F +asc_4day,-2.157902150190833,F diff --git a/model/configs/telework_arrangement.csv b/model/configs/telework_arrangement.csv new file mode 100644 index 0000000..4710458 --- /dev/null +++ b/model/configs/telework_arrangement.csv @@ -0,0 +1,22 @@ +Label,Description,Expression,has_in_home_work_activity,no_in_home_work_activity +util_acs,alternative specific constant,@1,coef_acs_has_in_home_work_activity, +util_part_time_worker,partime worker,ptype==2,coef_parttime_has_in_home, +util_univ_student,university student,ptype==3,coef_univ_student_has_in_home, +util_driving_age_student,driving age student,ptype==6,-999, +util_has_preschool_at_home,has preschool children at home on the day,has_preschool_kid_at_home,coef_has_preschool_at_home_has_in_home, +util_income_100_150,income 100-150k,(income_in_thousands>=100)&(income_in_thousands<150),coef_income_100_150, +util_income_150p,income 150k plus,income_in_thousands>=150,coef_income_150p, +util_age_above_35,age above 35,age>=35,coef_age_above_35, +util_workplace_mc_logsum,workplace mode choice logsum,"@np.where(df.workplace_zone_id>-1, df.workplace_modechoice_logsum, 0)",coef_workplace_mc_logsum, +# interaction terms,,,, +util_telecommute_1_day_cdap_N,telecommute 1 day and cdap non-mandatory,"(telecommute_frequency==""1_day_week"") & (cdap_activity==""N"")",coef_telecommute_1_day_cdap_N, +util_telecommute_2_3_days_cdap_N,telecommute 2-3 days and cdap non-mandatory,"(telecommute_frequency==""2_3_days_week"") & (cdap_activity==""N"")",coef_telecommute_2_3_4p_days_cdap_N, +util_telecommute_4p_days_cdap_N,telecommute 4p days and cdap non-mandatory,"(telecommute_frequency==""4_days_week"") & (cdap_activity==""N"")",coef_telecommute_2_3_4p_days_cdap_N, +util_telecommute_1_day_cdap_H,telecommute 1 day and cdap home,"(telecommute_frequency==""1_day_week"") & (cdap_activity==""H"")",coef_telecommute_1_day_cdap_H, +util_telecommute_2_3_days_cdap_H,telecommute 2-3 days and cdap home,"(telecommute_frequency==""2_3_days_week"") & (cdap_activity==""H"")",coef_telecommute_2_3_days_cdap_H, +util_telecommute_4p_days_cdap_H,telecommute 4p days and cdap home,"(telecommute_frequency==""4_days_week"") & (cdap_activity==""H"")",coef_telecommute_4p_days_cdap_H, +util_work_from_home_cdap_N,work from home and cdap non-mandatory,"(work_from_home==True) & (cdap_activity==""N"")",coef_work_from_home_cdap_N, +util_work_from_home_cdap_H,work from home and cdap home,"(work_from_home==True) & (cdap_activity==""H"")",coef_work_from_home_cdap_H, +# industry,,,, +util_industry_business_srv,srv industry_business,@df.naics_code==54,coef_industry_business_srv, +util_cdap_N_non_mandatory_accessibility,CDAP N and Disaggreage non-mandatory accessibility at home location,"(cdap_activity==""N"")*auPkRetail",coef_cdap_N_non_mandatory_accessibility, diff --git a/model/configs/telework_arrangement.yaml b/model/configs/telework_arrangement.yaml new file mode 100644 index 0000000..20cd8c1 --- /dev/null +++ b/model/configs/telework_arrangement.yaml @@ -0,0 +1,6 @@ +SPEC: telework_arrangement.csv +COEFFICIENTS: telework_arrangement_coeffs.csv + +LOGIT_TYPE: MNL + +HAS_IN_HOME_WORK_ACTIVITY_ALT: 0 \ No newline at end of file diff --git a/model/configs/telework_arrangement_coeffs.csv b/model/configs/telework_arrangement_coeffs.csv new file mode 100644 index 0000000..478c700 --- /dev/null +++ b/model/configs/telework_arrangement_coeffs.csv @@ -0,0 +1,19 @@ +coefficient_name,value,constrain +coef_acs_has_in_home_work_activity,-1.28,F +coef_age_above_35,-0.155,F +coef_cdap_N_non_mandatory_accessibility,0.012,F +coef_dummy_missing_workplace,-0.0295,F +coef_has_preschool_at_home_has_in_home,0.384,F +coef_income_100_150,0.187,F +coef_income_150p,0.293,F +coef_industry_business_srv,0.65,F +coef_parttime_has_in_home,-0.497,F +coef_telecommute_1_day_cdap_H,0.684,F +coef_telecommute_1_day_cdap_N,0.298,F +coef_telecommute_2_3_4p_days_cdap_N,1.73,F +coef_telecommute_2_3_days_cdap_H,1.57,F +coef_telecommute_4p_days_cdap_H,1.87,F +coef_univ_student_has_in_home,-0.798,F +coef_work_from_home_cdap_H,1.95,F +coef_work_from_home_cdap_N,2.25,F +coef_workplace_mc_logsum,-0.00867,F diff --git a/model/configs/telework_duration.csv b/model/configs/telework_duration.csv new file mode 100644 index 0000000..305ee71 --- /dev/null +++ b/model/configs/telework_duration.csv @@ -0,0 +1,9 @@ +Label,Description,Expression,3-5 hours,5-7 hours,7-9 hours,9+ hours +util_asc,alternative specific constant,@1,coef_acs_3_5,coef_acs_5_7,,coef_asc_9_plus +util_part_time,part-time worker,@df.ptype==2,coef_part_time_3_5,coef_part_time_5_7,,coef_part_time_9_plus +util_university_student,university student,@df.ptype==3,coef_university_student_3_5,coef_university_student_5_7,, +util_work_from_home,work from home,work_from_home,coef_work_from_home_3_7,coef_work_from_home_3_7,,coef_work_from_home_9_plus +util_hybrid,hybrid worker,(cdap_activity=='M')&(ptype<3),,,coef_hybrid_7_plus,coef_hybrid_7_plus +util_income_100_150,income 100-150k,(income_in_thousands>=100)&(income_in_thousands<150),coef_income_100_150_3_7,coef_income_100_150_3_7,, +util_income_150_plus,income 150k+,(income_in_thousands>=150),coef_income_150_plus_3_5,coef_income_150_plus_5_7,, +util_workplace_mc_logsum,workplace mode choice logsum,"@np.where(df.workplace_zone_id > -1, df.workplace_modechoice_logsum, 0)",coef_workplace_mc_logsum_3_7,coef_workplace_mc_logsum_3_7,, \ No newline at end of file diff --git a/model/configs/telework_duration.yaml b/model/configs/telework_duration.yaml new file mode 100644 index 0000000..025843e --- /dev/null +++ b/model/configs/telework_duration.yaml @@ -0,0 +1,24 @@ +# CHOICE_MODEL: PROBABILISTIC +CHOICE_MODEL: MNL + +# Worker filters: apply only to workers with in-home work activity. +CHOOSER_FILTER_COLUMN_NAME: has_in_home_work_activity + +# Output columns on persons table. +DURATION_CATEGORY_COLUMN_NAME: telework_duration_category +DURATION_HOURS_COLUMN_NAME: telework_duration_hours + +# Shared alternatives for both model types. +ALTS: telework_duration_alts.csv +ALT_NAME_COLUMN: alt +ALT_DURATION_COLUMN: duration_hours + +# Probabilistic configuration. +PROBS_SPEC: telework_duration_probs.csv +PROBS_JOIN_COLS: null + +# MNL configuration (used when CHOICE_MODEL: MNL). +SPEC: telework_duration.csv +COEFFICIENTS: telework_duration_coeffs.csv +LOGIT_TYPE: MNL + diff --git a/model/configs/telework_duration_alts.csv b/model/configs/telework_duration_alts.csv new file mode 100644 index 0000000..58006f8 --- /dev/null +++ b/model/configs/telework_duration_alts.csv @@ -0,0 +1,5 @@ +alt,duration_hours +3-5 hours,4 +5-7 hours,6 +7-9 hours,8 +9+ hours,10 diff --git a/model/configs/telework_duration_coeffs.csv b/model/configs/telework_duration_coeffs.csv new file mode 100644 index 0000000..e9474fc --- /dev/null +++ b/model/configs/telework_duration_coeffs.csv @@ -0,0 +1,18 @@ +coefficient_name,value,constrain +coef_acs_3_5,-1.32,F +coef_acs_5_7,-1.40,F +coef_asc_9_plus,-1.47,F +coef_dummy_missing_workplace_3_5,1.4,F +coef_dummy_missing_workplace_5_7,0.995,F +coef_hybrid_7_plus,-0.323,F +coef_income_100_150_3_7,-0.189,F +coef_income_150_plus_3_5,-0.201,F +coef_income_150_plus_5_7,-0.342,F +coef_part_time_3_5,1.45,F +coef_part_time_5_7,1.28,F +coef_part_time_9_plus,-0.849,F +coef_university_student_3_5,1.74,F +coef_university_student_5_7,1.42,F +coef_work_from_home_3_7,-0.297,F +coef_work_from_home_9_plus,-0.341,F +coef_workplace_mc_logsum_3_7,0.138,F diff --git a/model/configs/work_from_home.csv b/model/configs/work_from_home.csv new file mode 100644 index 0000000..59e7682 --- /dev/null +++ b/model/configs/work_from_home.csv @@ -0,0 +1,23 @@ +Label,Description,Expression,work_at_home,work_away_from_home +util_work_from_home_constant,Constant for Working from home,1,coef_work_from_home_constant, +util_part_time_worker,Part time worker,@df.ptype==2,coef_part_time_worker, +#util_access_to_workplaces,Accessibility to workplaces of the home zone,@df.workplace_location_accessibility,,coef_access_to_workplaces +util_access_to_employments,Accessibility to employments of the home zone,@df.auPkTotal,,coef_access_to_workplaces +util_age_35_to_44,Age Group - 35 yrs to 44 yrs,"@df.age.between(35, 44)",coef_age_35_to_44, +util_age_45_to_54,Age Group - 45 yrs to 54 yrs,"@df.age.between(45, 54)",coef_age_45_to_54, +util_age_55_to_64,Age Group - 55 yrs to 64 yrs,"@df.age.between(55, 64)",coef_age_55_to_64, +util_age_65_79,Age 65-79,"@df.age.between(65, 79)",coef_age_65_79, +util_age_80_plus,Age 80 plus,@df.age > 79,coef_age_80_plus, +util_inc_lt_15,Household income less than 15k,@df.income<15000,coef_inc_lt_15, +util_inc_150_250,Household income between 150k-249999k,"@df.income.between(150000,249999)",coef_inc_150_250, +util_ind_accom,Industry type is accomodation,@df.naics_code==721,coef_ind_accom, +util_ind_bus_srv,Industry type is business services,@df.naics_code==54,coef_ind_bus_srv, +util_ind_construct,Industry type is construction,@df.naics_code==23,coef_ind_construct, +util_ind_edu,Industry type is education,@df.naics_code==61,coef_ind_edu, +util_ind_enter,Industry type is entertainment,@df.naics_code==71,coef_ind_enter, +util_ind_food_srv,Industry type is food services,@df.naics_code==722,coef_ind_food_srv, +util_ind_gov,Industry type is government,@df.naics_code==92,coef_ind_gov, +util_ind_health,Industry type is healthcare,@df.naics_code==62,coef_ind_health, +util_ind_manu,Industry type is manufacturing,"@df.naics_code.isin([31,32,33])",coef_ind_manu, +util_ind_mgmt_srv,Industry type is management services,@df.naics_code==55,coef_ind_mgmt_srv, +util_ind_mil,Industry type is military,@df.naics_code==9000,coef_ind_mil, \ No newline at end of file diff --git a/model/configs/work_from_home.yaml b/model/configs/work_from_home.yaml new file mode 100644 index 0000000..de48357 --- /dev/null +++ b/model/configs/work_from_home.yaml @@ -0,0 +1,12 @@ + +# borrowed from free parking model + +SPEC: work_from_home.csv +COEFFICIENTS: work_from_home_coeffs.csv + +#LOGIT_TYPE: NL +LOGIT_TYPE: MNL + +WORK_FROM_HOME_ALT: 0 + +DEST_CHOICE_COLUMN_NAME: workplace_zone_id \ No newline at end of file diff --git a/model/configs/work_from_home_coeffs.csv b/model/configs/work_from_home_coeffs.csv new file mode 100644 index 0000000..4cbc233 --- /dev/null +++ b/model/configs/work_from_home_coeffs.csv @@ -0,0 +1,22 @@ +coefficient_name,value,constrain +coef_work_from_home_constant,-0.386629856,F +coef_part_time_worker,-1.4513809,F +coef_access_to_workplaces,0.1,T +coef_age_35_to_44,0.353530241,F +coef_age_45_to_54,0.468133972,F +coef_age_55_to_64,0.495172418,F +coef_age_65_79,1.243578944,F +coef_age_80_plus,0.770722144,F +coef_inc_lt_15,0.613583177,F +coef_inc_150_250,0.419020695,F +coef_ind_accom,-1.639185004,F +coef_ind_bus_srv,0.721333596,F +coef_ind_construct,-0.738399459,F +coef_ind_edu,-0.508510677,F +coef_ind_enter,1.027744147,F +coef_ind_food_srv,-1.204685952,F +coef_ind_gov,-0.75445486,F +coef_ind_health,-0.441707723,F +coef_ind_manu,-0.566600437,F +coef_ind_mgmt_srv,0.386847827,F +coef_ind_mil,-2.257002339,F \ No newline at end of file diff --git a/model/configs/workplace_location.yaml b/model/configs/workplace_location.yaml index e20c14f..e5d8c5d 100644 --- a/model/configs/workplace_location.yaml +++ b/model/configs/workplace_location.yaml @@ -16,6 +16,7 @@ OUT_PERIOD: 8 DEST_CHOICE_COLUMN_NAME: workplace_zone_id # comment out DEST_CHOICE_LOGSUM_COLUMN_NAME if not desired in persons table DEST_CHOICE_LOGSUM_COLUMN_NAME: workplace_location_logsum +MODE_CHOICE_LOGSUM_COLUMN_NAME: workplace_modechoice_logsum # comment out DEST_CHOICE_LOGSUM_COLUMN_NAME if saved alt logsum table DEST_CHOICE_SAMPLE_TABLE_NAME: workplace_location_sample @@ -40,7 +41,7 @@ MODEL_SELECTOR: workplace CHOOSER_SEGMENT_COLUMN_NAME: income_segment # boolean column to filter choosers (True means keep) -CHOOSER_FILTER_COLUMN_NAME: is_worker +CHOOSER_FILTER_COLUMN_NAME: is_out_of_home_worker # FIXME - these are assigned to persons in annotate_persons. we need a better way to manage this # FIXME - these are not needed for this model and should be re/factored out SEGMENT_IDS: diff --git a/model/configs_mp/settings.yaml b/model/configs_mp/settings.yaml index b129f81..6af121d 100644 --- a/model/configs_mp/settings.yaml +++ b/model/configs_mp/settings.yaml @@ -45,11 +45,15 @@ models: ### mp_accessibility step - compute_accessibility ### mp_households step + - work_from_home - school_location - workplace_location - auto_ownership_simulate - free_parking + - telecommute_frequency - cdap_simulate + - telework_arrangement + - telework_duration - mandatory_tour_frequency - mandatory_tour_scheduling - non_mandatory_tour_frequency @@ -82,7 +86,7 @@ multiprocess_steps: # don't slice any tables not explicitly listed above in slice.tables exclude: true - name: mp_households - begin: school_location + begin: work_from_home slice: tables: [households, persons] - name: mp_summarize