def plot(table_names=None): """ Plot relationships between columns and tables using Graphviz. Parameters ---------- table_names : iterable of str, optional Names of UrbanSim registered tables to plot. Defaults to all registered tables. Returns ------- graph : pygraphviz.AGraph PyGraphviz graph object. """ if not table_names: # Default to all registered tables. table_names = simulation.list_tables() graph = AGraph(directed=True) graph.graph_attr['fontname'] = 'Sans' graph.graph_attr['fontsize'] = 28 graph.node_attr['shape'] = 'box' graph.node_attr['fontname'] = 'Sans' graph.node_attr['fontcolor'] = 'blue' graph.edge_attr['weight'] = 2 # Add each registered table as a subgraph with columns as nodes. for table_name in table_names: subgraph = graph.add_subgraph(name='cluster_' + table_name, label=table_name, fontcolor='red') table = simulation.get_table(table_name) for column_name in table.columns: full_name = table_name + '.' + column_name subgraph.add_node(full_name, label=column_name) # Iterate over computed columns to build nodes. for key, wrapped_col in simulation._COLUMNS.items(): table_name = key[0] column_name = key[1] # Combine inputs from argument names and argument default values. args = list(wrapped_col._argspec.args) if wrapped_col._argspec.defaults: default_args = list(wrapped_col._argspec.defaults) else: default_args = [] inputs = args[:len(args) - len(default_args)] + default_args # Create edge from each input column to the computed column. for input_name in inputs: full_name = table_name + '.' + column_name graph.add_edge(input_name, full_name) graph.layout(prog='dot') return graph
def run_developer(forms, agents, buildings, supply_fname, parcel_size, ave_unit_size, total_units, feasibility, year=None, target_vacancy=.1, form_to_btype_callback=None, add_more_columns_callback=None, max_parcel_size=2000000, residential=True, bldg_sqft_per_job=400.0, min_unit_size=400, remove_developed_buildings=True, unplace_agents=['households', 'jobs']): """ Run the developer model to pick and build buildings Parameters ---------- forms : string or list of strings Passed directly dev.pick agents : DataFrame Wrapper Used to compute the current demand for units/floorspace in the area buildings : DataFrame Wrapper Used to compute the current supply of units/floorspace in the area supply_fname : string Identifies the column in buildings which indicates the supply of units/floorspace parcel_size : Series Passed directly to dev.pick ave_unit_size : Series Passed directly to dev.pick - average residential unit size total_units : Series Passed directly to dev.pick - total current residential_units / job_spaces feasibility : DataFrame Wrapper The output from feasibility above (the table called 'feasibility') year : int The year of the simulation - will be assigned to 'year_built' on the new buildings target_vacancy : float The target vacancy rate - used to determine how much to build form_to_btype_callback : function Will be used to convert the 'forms' in the pro forma to 'building_type_id' in the larger model add_more_columns_callback : function Takes a dataframe and returns a dataframe - is used to make custom modifications to the new buildings that get added max_parcel_size : float Passed directly to dev.pick - max parcel size to consider min_unit_size : float Passed directly to dev.pick - min unit size that is valid residential : boolean Passed directly to dev.pick - switches between adding/computing residential_units and job_spaces bldg_sqft_per_job : float Passed directly to dev.pick - specified the multiplier between floor spaces and job spaces for this form (does not vary by parcel as ave_unit_size does) remove_redeveloped_buildings : optional, boolean (default True) Remove all buildings on the parcels which are being developed on unplace_agents : optional : list of strings (default ['households', 'jobs']) For all tables in the list, will look for field building_id and set it to -1 for buildings which are removed - only executed if remove_developed_buildings is true Returns ------- Writes the result back to the buildings table and returns the new buildings with available debugging information on each new building """ dev = developer.Developer(feasibility.to_frame()) target_units = dev.\ compute_units_to_build(len(agents), buildings[supply_fname].sum(), target_vacancy) print "{:,} feasible buildings before running developer".format( len(dev.feasibility)) new_buildings = dev.pick(forms, target_units, parcel_size, ave_unit_size, total_units, max_parcel_size=max_parcel_size, min_unit_size=min_unit_size, drop_after_build=True, residential=residential, bldg_sqft_per_job=bldg_sqft_per_job) sim.add_table("feasibility", dev.feasibility) if new_buildings is None: return if len(new_buildings) == 0: return new_buildings if year is not None: new_buildings["year_built"] = year if not isinstance(forms, list): # form gets set only if forms is a list new_buildings["form"] = forms if form_to_btype_callback is not None: new_buildings["building_type_id"] = new_buildings.\ apply(form_to_btype_callback, axis=1) new_buildings["stories"] = new_buildings.stories.apply(np.ceil) ret_buildings = new_buildings if add_more_columns_callback is not None: new_buildings = add_more_columns_callback(new_buildings) print "Adding {:,} buildings with {:,} {}".\ format(len(new_buildings), int(new_buildings[supply_fname].sum()), supply_fname) print "{:,} feasible buildings after running developer".format( len(dev.feasibility)) old_buildings = buildings.to_frame(buildings.local_columns) new_buildings = new_buildings[buildings.local_columns] if remove_developed_buildings: redev_buildings = old_buildings.parcel_id.isin(new_buildings.parcel_id) l = len(old_buildings) drop_buildings = old_buildings[redev_buildings] old_buildings = old_buildings[np.logical_not(redev_buildings)] l2 = len(old_buildings) if l2-l > 0: print "Dropped {} buildings because they were redeveloped".\ format(l2-l) for tbl in unplace_agents: agents = sim.get_table(tbl) cols = agents.local_columns if "building_id" not in cols: # if it's a unit-level model, need to add building_id # explicitly cols += ["building_id"] agents = agents.to_frame(cols) displaced_agents = agents.building_id.isin(drop_buildings.index) print "Unplaced {} before: {}".format(tbl, len(agents.query( "building_id == -1"))) agents.building_id[displaced_agents] = -1 print "Unplaced {} after: {}".format(tbl, len(agents.query( "building_id == -1"))) sim.add_table(tbl, agents) all_buildings = dev.merge(old_buildings, new_buildings) sim.add_table("buildings", all_buildings) if "residential_units" in sim.list_tables() and residential: # need to add units to the units table as well old_units = sim.get_table("residential_units") old_units = old_units.to_frame(old_units.local_columns) new_units = pd.DataFrame({ "unit_residential_price": 0, "num_units": 1, "deed_restricted": 0, "unit_num": np.concatenate([np.arange(i) for i in \ new_buildings.residential_units.values]), "building_id": np.repeat(new_buildings.index.values, new_buildings.residential_units.\ astype('int32').values) }).sort(columns=["building_id", "unit_num"]).reset_index(drop=True) print "Adding {:,} units to the residential_units table".\ format(len(new_units)) all_units = dev.merge(old_units, new_units) all_units.index.name = "unit_id" sim.add_table("residential_units", all_units) return ret_buildings # pondered returning ret_buildings, new_units but users can get_table # the units if they want them - better to avoid breaking the api return ret_buildings