Real Estate Development Models ============================== .. note:: This package builds on the existing `developer model `_ included in the UrbanSim library. Some of the key changes are highlighted here, but it may be helpful to refer to the old version's `documentation `_ for a more detailed comparison. The real estate development models included in this module are designed to implement pencil out pro formas, which generally measure the cash inflows and outflows of a potential investment (in this case, real estate development) with the outcome being some measure of profitability or return on investment. Pro formas would normally be performed in a spreadsheet program (e.g. Excel), but are implemented in vectorized Python implementations so that many (think millions) of pro formas can be performed at a time. The functionality is split into two modules - the square foot pro forma and the developer model - as there are many use cases that call for the pro formas without the developer model. The ``sqftproforma`` module computes real estate feasibility for a set of parcels dependent on allowed uses, prices, and building costs, but does not actually *build* anything (both figuratively and literally). The ``develop`` module decides how much to build, then picks among the set of feasible buildings attempting to meet demand, and adds the new buildings to the set of current buildings. Thus ``develop`` is primarily useful in the context of an urban forecast. An example of the sample code required to generate the set of feasible buildings is shown below. This code comes from the ``utils`` module of the `urbansim_parcels `_ San Diego demo. Notice that the SqFtProForma is first initialized and a DataFrame of parcels is tested for feasibliity (each individual parcel is tested for feasibility). Each *use* (e.g. retail, office, residential, etc) is assigned a price per parcel, typically from empirical data of currents rents and prices in the city but can be the result of forecast rents and prices as well. The ``lookup`` function is then called with a specific building ``form`` and the pro forma returns whether that form is profitable for each parcel. A large number of assumptions enter in to the computation of profitability and these are set in the `SqFtProForma <#developer.sqftproforma.SqFtProForma>`_ module, and include such things as the set of ``uses`` to model, the mix of ``uses`` into ``forms``, the impact of parking requirements, parking costs, building costs at different heights (taller buildings typically requiring more expensive construction methods), the profit ratio required, the building efficiency, parcel coverage, and cap rate to name a few. See the API documentation for the complete list and detailed descriptions. The newest version of this model allows for loading configurations from a YAML file; the examples in the ``urbansim_parcels`` repository include a `YAML file `_ with default configurations. Note that unit mixes don't typically enter in to the square foot pro forma (hence the name). After discussions with numerous real estate developers, we found that most developers thought first and foremost in terms of price and cost per square foot and the arbitrage between, and second in terms of the translation to unit sizes and mixes in a given market (also larger and smaller units of a given unit type will typically lower and raise their prices as stands to reason). Since getting data on unit mixes in the current building stock is extremely difficult, most feasibility computations here happen on a square foot basis and the ``developer`` model below handles the translation to units. :: cfg = misc.config('proforma.yaml') pf = (sqftproforma.SqFtProForma.from_yaml(str_or_buffer=cfg) if cfg else sqftproforma.SqFtProForma.from_defaults()) for use in pf.uses: parcels[use] = parcel_price_callback(use) feasibility = lookup_by_form(df, parcel_use_allowed_callback, pf, **kwargs) orca.add_table('feasibility', feasibility) d = {} for form in pf.config.forms: print "Computing feasibility for form %s" % form d[form] = pf.lookup(form, df[parcel_use_allowed_callback(form)]) feasibility = pd.concat(d.values(), keys=d.keys(), axis=1) sim.add_table("feasibility", feasibility) The ``develop`` module is responsible for picking among feasible buildings in order to meet demand. An example usage of the model is shown below - which is also lifted from the `urbansim_parcels `_ San Diego demo. This module provides a simple utility to compute the number of units (or amount of floorspace) to build. The developer model itself is agnostic to which parcels the user passes it, and the user is responsible for knowing at which level of geography demand is assumed to operate. The developer model then chooses which buildings to "build." Previous workflows have typically involved selecting buildings based on random choice weighted by profitability, with an expected vacancy rate acting as a control on the number of buildings built. In this version, there are new features that allow a bit more control over this process (see `Callback Access to Development Selection`_). The only remaining steps are then "bookkeeping" in the sense that some additional fields might need to be added (``year_built`` or a conversion from developer ``forms`` to ``building_type_ids``). Finally the new buildings and old buildings need to be merged in such a way that the old ids are preserved and not duplicated (new ids are assigned at the max of the old ids+1 and then incremented from there). If using a development pipeline, utility functions in `urbansim_parcels` provide a way to properly add new buildings to the pipeline, rather than directly to the building table. :: cfg = misc.config('developer.yaml') target_units = (num_units_to_build or compute_units_to_build(len(agents), buildings[supply_fname].sum(), target_vacancy)) dev = develop.Developer.from_yaml(feasibility.to_frame(), forms, target_units, parcel_size, ave_unit_size, current_units, year, str_or_buffer=cfg) new_buildings = dev.pick(profit_to_prob_func, custom_selection_func) if year is not None: new_buildings["year_built"] = year if form_to_btype_callback is not None: new_buildings["building_type_id"] = new_buildings["form"].\ apply(form_to_btype_callback) all_buildings = dev.merge(buildings.to_frame(buildings.local_columns), new_buildings[buildings.local_columns]) sim.add_table("buildings", all_buildings) .. toctree:: :maxdepth: 2 Major Changes ~~~~~~~~~~~~~ Input/Output ^^^^^^^^^^^^ There are now methods in the ``SqFtProForma`` and ``Developer`` classes to load with configurations in a similar way to other `UrbanSim models `_. The pro forma and developer model configurations can be saved as YAML files and loaded again at another time. Use the ``to_yaml`` and ``from_yaml`` methods to save files to disk and load them back as configurations. The ``SqFtProForma`` class also has a ``from_defaults`` method to load default values. Here’s an example of loading a pro forma model from defaults and saving custom settings back to YAML: :: proforma = SqFtProForma.from_defaults() proforma.to_yaml('proforma.yaml') # Make changes manually to YAML file new_proforma = SqFtProForma.from_yaml('modified_proforma.yaml') Construction Financing ^^^^^^^^^^^^^^^^^^^^^^ One missing piece in profit calculations in the previous version of the model is the fact that typically, real estate developers must secure financing to fund the construction of buildings, and the cost of interest and fees must be taken into account when calculating the final profitability of the building. Several new attributes have been added to the SqFtProForma object. Default values in dictionary format are below. For more information on each of these attributes, see `API documentation <#developer.sqftproforma.SqFtProForma>`_. :: 'construction_months': { 'industrial': [12.0, 14.0, 18.0, 24.0], 'office': [12.0, 14.0, 18.0, 24.0], 'residential': [12.0, 14.0, 18.0, 24.0], 'retail': [12.0, 14.0, 18.0, 24.0]}, 'construction_sqft_for_months': [10000, 20000, 50000, np.inf], 'loan_to_cost_ratio': .7, 'drawdown_factor': .6, 'interest_rate': .05, 'loan_fees': .02 In this version of the pro forma model, "total development cost" is derived from the total building cost, along with the attributes above: :: # total_construction_costs calculated above loan_amount = total_construction_costs * self.loan_to_cost_ratio interest = (loan_amount * self.drawdown_factor * (self.interest_rate / 12 * months)) points = loan_amount * self.loan_fees total_financing_costs = interest + points total_development_costs = (total_construction_costs + total_financing_costs) Callback Access to Profit Calculation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In the core of the pro forma module, profitability for a set of potential development sites (typically parcels) is calculated in a set of steps: #. Read DataFrame of development sites. #. Based on key columns in DataFrame, generate NumPy array of costs, with sites as columns and potential floor-to-area ratios (FARs) as rows. #. Generate NumPy array of revenues with the same structure as costs. #. Subtract costs from revenues to calculate profit (for every site, for every FAR). #. For each site, pick the most profitable FAR, and return values for that FAR, including development costs, construction time, etc. In the previous version of this model, the only control the user had into this calculation was through values in the initial DataFrame. In this new version, users can now pass callback functions to the ``lookup()`` method to adjust the calculation at different stages, using the following parameters: - ``modify_df``: Modify the input DataFrame. This can of course be done before passing into the pick() method, but this allows columns to be calculated using attributes of the Developer object. - ``modify_costs``, ``modify_revenues``, and ``modify_profit``: Modify the NumPy ndarrays that represent costs and revenues for each sites and FAR. There are specific parameters that each of these callback functions must include; see `documentation <#developer.sqftproforma.SqFtProForma.lookup>`_ for details. For example, let's look at one result based on a test parcel dataset: :: pf = SqFtProForma.from_defaults() pf.lookup('residential', df) This simple lookup produces this result (table is simplified for example): ====== ========== ========== ========== parcel cost revenue profit ====== ========== ========== ========== a 4,922,490 8,960,000 4,037,510 b 13,821,510 26,880,000 13,058,490 c 28,805,616 53,760,000 24,954,384 ====== ========== ========== ========== Let's say we believe that revenues will be additionally reduced by 20% due to an external factor, for all sites in this region. We can apply a simple intervention to this calculation using a callback. :: def revenue_callback(self, form, df, revenues): revenues = revenues * .8 return revenues pf.lookup('residential', df, modify_revenues=revenue_callback) This gives us an identical cost column, but changes to the revenue and profit: ====== ========== ========== ========== parcel cost revenue profit ====== ========== ========== ========== a 4,922,490 7,168,000 2,245,510 b 13,821,510 21,504,000 7,682,490 c 28,805,616 43,008,000 14,202,384 ====== ========== ========== ========== Callback Access to Development Selection ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The previous version of the developer model has the following rules for selecting parcels to build, based on a number of ``target_units`` to build, and a probability associated with each development site, which can be generated using a custom function: #. If number of profitable sites is less than ``target_units``, build all sites #. If ``target_units`` is 0 or less, build no sites #. If number of profitable sites is greater than ``target_units``, select a number of sites equal to ``target_units`` based on probability (calculated from profitability unless otherwise defined) In this version, users can pass a callback function to circumvent this entire process and select sites based on entirely custom logic, using information in the DataFrame passed to the ``pick()`` method. The following example passes a function that selects all developments that have a profit per square foot larger than 10: :: dev = develop.Developer.from_yaml(feasibility.to_frame(), forms, target_units, parcel_size, ave_unit_size, current_units, year, str_or_buffer=cfg) def custom_selection(self, df, p): min_profit_per_sqft = 10 print("BUILDING ALL BUILDINGS WITH PROFIT > ${:.2f} / sqft" .format(min_profit_per_sqft)) profitable = df.loc[df.max_profit_per_size > min_profit_per_sqft] build_idx = profitable.index.values return build_idx new_buildings = dev.pick(custom_selection_func=custom_selection) As with the previous set of callback functions, there is a strict set of parameters that are required. See `documentation <#developer.develop.Developer.pick>`_ for details. Square Foot Pro Forma API ~~~~~~~~~~~~~~~~~~~~~~~~~ .. automodule:: developer.sqftproforma :members: Developer Model API ~~~~~~~~~~~~~~~~~~~ .. automodule:: developer.develop :members: