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 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)

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.

'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:

  1. Read DataFrame of development sites.
  2. Based on key columns in DataFrame, generate NumPy array of costs, with sites as columns and potential floor-to-area ratios (FARs) as rows.
  3. Generate NumPy array of revenues with the same structure as costs.
  4. Subtract costs from revenues to calculate profit (for every site, for every FAR).
  5. 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 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:

  1. If number of profitable sites is less than target_units, build all sites
  2. If target_units is 0 or less, build no sites
  3. 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 for details.

Square Foot Pro Forma API

class developer.sqftproforma.SqFtProForma(parcel_sizes, fars, uses, residential_uses, forms, profit_factor, building_efficiency, parcel_coverage, cap_rate, parking_rates, sqft_per_rate, parking_configs, costs, heights_for_costs, parking_sqft_d, parking_cost_d, height_per_story, max_retail_height, max_industrial_height, construction_months, construction_sqft_for_months, loan_to_cost_ratio, drawdown_factor, interest_rate, loan_fees, residential_to_yearly=True, forms_to_test=None, only_built=True, pass_through=None, simple_zoning=False, parcel_filter=None)[source]

Initialize the square foot based pro forma.

This pro forma has no representation of units - it does not differentiate between the rent attained by 1BR, 2BR, or 3BR and change the rents accordingly. This is largely because it is difficult to get information on the unit mix in an existing building in order to compute its acquisition cost. Thus rents and costs per sqft are used for new and current buildings which assumes there is a constant return on increasing and decreasing unit sizes, an extremely useful simplifying assumption above the project scale (i.e. city of regional scale)

Parameters:

parcel_sizes : list

A list of parcel sizes to test. Interestingly, right now the parcel sizes cancel in this style of pro forma computation so you can set this to something reasonable for debugging purposes - e.g. [10000]. All sizes can be feet or meters as long as they are consistently used.

fars : list

A list of floor area ratios to use. FAR is a multiple of the parcel size that is the total building bulk that is allowed by zoning on the site. In this case, all of these ratios will be tested regardless of zoning and the zoning test will be performed later.

uses : list

A list of space uses to use within a building. These are mixed into forms. Generally speaking, you should only have uses for which you have an estimate (or observed) values for rents in the building. By default, uses are retail, industrial, office, and residential.

forms : dict

A dictionary where keys are names for the form and values are also dictionaries where keys are uses and values are the proportion of that use used in this form. The values of the dictionary should sum to 1.0. For instance, a form called “residential” might have a dict of space allocations equal to {“residential”: 1.0} while a form called “mixedresidential” might have a dictionary of space allocations equal to {“retail”: .1, “residential” .9] which is 90% residential and 10% retail.

parking_rates : dict

A dict of rates per thousand square feet where keys are the uses from the list specified in the attribute above. The ratios are typically in the range 0.5 - 3.0 or similar. So for instance, a key-value pair of “retail”: 2.0 would be two parking spaces per 1,000 square feet of retail. This is a per square foot pro forma, so the more typically parking ratio of spaces per residential unit must be converted to square feet for use in this pro forma.

sqft_per_rate : float

The number of square feet per unit for use in the parking_rates above. By default this is set to 1,000 but can be overridden.

parking_configs : list

An expert parameter and is usually unchanged. By default it is set to [‘surface’, ‘deck’, ‘underground’] and very semantic differences in the computation are performed for each of these parking configurations. Generally speaking it will break things to change this array, but an item can be removed if that parking configuration should not be tested.

parking_sqft_d : dict

A dictionary where keys are the three parking configurations listed above and values are square foot uses of parking spaces in that configuration. This is to capture the fact that surface parking is usually more space intensive than deck or underground parking.

parking_cost_d : dict

The parking cost for each parking configuration. Keys are the name of the three parking configurations listed above and values are dollars PER SQUARE FOOT for parking in that configuration. Used to capture the fact that underground and deck are far more expensive than surface parking.

heights_for_costs : list

A list of “break points” as heights at which construction becomes more expensive. Generally these are the heights at which construction materials change from wood, to concrete, to steel. Costs are also given as lists by use for each of these break points and are considered to be valid up to the break point. A list would look something like [15, 55, 120, np.inf].

costs : dict

The keys are uses from the attribute above and the values are a list of floating point numbers of same length as the height_for_costs attribute. A key-value pair of “residential”: [160.0, 175.0, 200.0, 230.0] would say that the residential use if $160/sqft up to 15ft in total height for the building, $175/sqft up to 55ft, $200/sqft up to 120ft, and $230/sqft beyond. A final value in the height_for_costs array of np.inf is typical.

height_per_story : float

The per-story height for the building used to turn an FAR into an actual height.

max_retail_height : float

The maximum height of retail buildings to consider.

max_industrial_height : float

The maximum height of industrial buildings to consider.

construction_months : dict

Analogous to ‘costs’, but for building construction time. The keys are uses from the attribute above and the values are a list of floating-point numbers of same length as the construction_sqft_for_months attribute. A key-value pair of “residential”: [12.0, 14.0, 18.0, 24.0] along with the default values for construction_sqft_for_months below would say that buildings with 10,000 sq. ft. or less take 12 months, those between 10,000 and 20,000 sq. ft. take 14 months, etc.

construction_sqft_for_months:

Analogous to heights_for_costs, but for building construction time. A list of “break points” as building square footage at which construction takes a different length of time. Default values are [10000, 20000, 50000, np.inf].

loan_to_cost_ratio : float

The proportion of construction loans to the total construction cost.

drawdown_factor : float

The factor by which financing cost is reduced by applying interest only to funds withdrawn in phases.

interest_rate : float

The interest rate for construction loans

loan_fees : float

The percentage of loan size that is added to costs as other fees

profit_factor : float

The ratio of profit a developer expects to make above the break even rent. Should be greater than 1.0, e.g. a 10% profit would be a profit factor of 1.1.

building_efficiency : float

The efficiency of the building. This turns total FAR into the amount of space which gets a square foot rent. The entire building gets the cost of course.

parcel_coverage : float

The ratio of the building footprint to the parcel size. Also used to turn an FAR into a height to cost properly.

cap_rate : float

The rate an investor is willing to pay for a cash flow per year. This means $1/year is equivalent to 1/cap_rate present dollars. This is a macroeconomic input that is widely available on the internet.

residential_to_yearly : boolean (optional)

Whether to use the cap rate to convert the residential price from total sales price per sqft to rent per sqft

forms_to_test : list of strings (optional)

Pass the list of the names of forms to test for feasibility - if set to None will use all the forms available in config

only_built : boolean (optional)

Only return those buildings that are profitable

pass_through : list of strings (optional)

Will be passed to the feasibility lookup function - is used to pass variables from the parcel dataframe to the output dataframe, usually for debugging

simple_zoning: boolean (optional)

This can be set to use only max_dua for residential and max_far for non-residential. This can be handy if you want to deal with zoning outside of the developer model.

parcel_filter : string (optional)

A filter to apply to the parcels data frame to remove parcels from consideration - is typically used to remove parcels with buildings older than a certain date for historical preservation, but is generally useful

classmethod from_defaults()[source]

Create a SqftProForma instance from default values.

Returns:SqFtProForma
classmethod from_yaml(yaml_str=None, str_or_buffer=None)[source]

Create a SqftProForma instance from a saved YAML configuration. Arguments are mutally exclusive.

Parameters:

yaml_str : str, optional

A YAML string from which to load model.

str_or_buffer : str or file like, optional

File name or buffer from which to load YAML.

Returns:

SqFtProForma

get_ave_cost_sqft(form, parking_config)[source]

Get the average cost per sqft for the pro forma for a given form

Parameters:

form : string

Get a series representing the average cost per sqft for each form in the config

parking_config : string

The parking configuration to get debug info for

Returns:

cost : series

A series where the index is the far and the values are the average cost per sqft at which the building is “break even” given the configuration parameters that were passed at run time.

get_debug_info(form, parking_config)[source]

Get the debug info after running the pro forma for a given form and parking configuration

Parameters:

form : string

The form to get debug info for

parking_config : string

The parking configuration to get debug info for

Returns:

debug_info : dataframe

A dataframe where the index is the far with many columns representing intermediate steps in the pro forma computation. Additional documentation will be added at a later date, although many of the columns should be fairly self-expanatory.

lookup(form, df, modify_df=None, modify_revenues=None, modify_costs=None, modify_profits=None, **kwargs)[source]

This function does the developer model lookups for all the actual input data.

Parameters:

form : string

One of the forms specified in the configuration file

df : DataFrame

Pass in a single data frame which is indexed by parcel_id and has the following columns

modify_df : function

Function to modify lookup DataFrame before profit calculations. Must have (self, form, df) as parameters.

modify_revenues : function

Function to modify revenue ndarray during profit calculations. Must have (self, form, df, revenues) as parameters.

modify_costs : function

Function to modify cost ndarray during profit calculations. Must have (self, form, df, costs) as parameters.

modify_profits : function

Function to modify profit ndarray during profit calculations. Must have (self, form, df, profits) as parameters.

Input Dataframe Columns

rent : dataframe

A set of columns, one for each of the uses passed in the configuration. Values are yearly rents for that use. Typical column names would be “residential”, “retail”, “industrial” and “office”

land_cost : series

A series representing the CURRENT yearly rent for each parcel. Used to compute acquisition costs for the parcel.

parcel_size : series

A series representing the parcel size for each parcel.

max_far : series

A series representing the maximum far allowed by zoning. Buildings will not be built above these fars.

max_height : series

A series representing the maximum height allowed by zoning. Buildings will not be built above these heights. Will pick between the min of the far and height, will ignore on of them if one is nan, but will not build if both are nan.

max_dua : series, optional

A series representing the maximum dwelling units per acre allowed by zoning. If max_dua is passed, the average unit size should be passed below to translate from dua to floor space.

ave_unit_size : series, optional

This is required if max_dua is passed above, otherwise it is optional. This is the same as the parameter to Developer.pick() (it should be the same series).

Returns:

index : Series, int

parcel identifiers

building_sqft : Series, float

The number of square feet for the building to build. Keep in mind this includes parking and common space. Will need a helpful function to convert from gross square feet to actual usable square feet in residential units.

building_cost : Series, float

The cost of constructing the building as given by the ave_cost_per_sqft from the cost model (for this FAR) and the number of square feet.

total_cost : Series, float

The cost of constructing the building plus the cost of acquisition of the current parcel/building.

building_revenue : Series, float

The NPV of the revenue for the building to be built, which is the number of square feet times the yearly rent divided by the cap rate (with a few adjustment factors including building efficiency).

max_profit_far : Series, float

The FAR of the maximum profit building (constrained by the max_far and max_height from the input dataframe).

max_profit :

The profit for the maximum profit building (constrained by the max_far and max_height from the input dataframe).

to_dict

Return a dict representation of a SqftProForma instance.

to_yaml(str_or_buffer=None)[source]

Save a model representation to YAML.

Parameters:

str_or_buffer : str or file like, optional

By default a YAML string is returned. If a string is given here the YAML will be written to that file. If an object with a .write method is given the YAML will be written to that object.

Returns:

j : str

YAML is string if str_or_buffer is not given.

class developer.sqftproforma.SqFtProFormaReference(parcel_sizes, fars, forms, profit_factor, parcel_coverage, parking_rates, sqft_per_rate, parking_configs, costs, heights_for_costs, parking_sqft_d, parking_cost_d, height_per_story, max_retail_height, max_industrial_height, construction_sqft_for_months, construction_months, **kwargs)[source]

Generate reference table for square foot pro forma analysis. Table is saved as the reference_dict attribute.

Developer Model API

class developer.develop.Developer(feasibility, forms, target_units, parcel_size, ave_unit_size, current_units, year=None, bldg_sqft_per_job=400.0, min_unit_size=400, max_parcel_size=200000, drop_after_build=True, residential=True, num_units_to_build=None)[source]

Pass the dataframe that is returned by feasibility here

Can also be a dictionary where keys are building forms and values are the individual data frames returned by the proforma lookup routine.

Parameters:

feasibility : DataFrame or dict

Results from SqftProForma lookup method

forms : string or list

One or more of the building forms from the pro forma specification - e.g. “residential” or “mixedresidential” - these are configuration parameters passed previously to the pro forma. If more than one form is passed the forms compete with each other (based on profitability) for which one gets built in order to meet demand.

parcel_size : series

The size of the parcels. This was passed to feasibility as well, but should be passed here as well. Index should be parcel_ids.

ave_unit_size : series

The average residential unit size around each parcel - this is indexed by parcel, but is usually a disaggregated version of a zonal or accessibility aggregation.

current_units : series

The current number of units on the parcel. Is used to compute the net number of units produced by the developer model. Many times the developer model is redeveloping units (demolishing them) and is trying to meet a total number of net units produced.

year : int

The year of the simulation - will be assigned to ‘year_built’ on the new buildings

bldg_sqft_per_job : float (default 400.0)

The average square feet per job for this building form.

min_unit_size : float

Values less than this number in ave_unit_size will be set to this number. Deals with cases where units are currently not built.

max_parcel_size : float

Parcels larger than this size will not be considered for development - usually large parcels should be specified manually in a development projects table.

drop_after_build : bool

Whether or not to drop parcels from consideration after they have been chosen for development. Usually this is true so as to not develop the same parcel twice.

residential: bool

If creating non-residential buildings set this to false and developer will fill in job_spaces rather than residential_units

num_units_to_build: optional, int

If num_units_to_build is passed, build this many units rather than computing it internally by using the length of agents adn the sum of the relevant supply columin - this trusts the caller to know how to compute this.

classmethod from_yaml(feasibility, forms, target_units, parcel_size, ave_unit_size, current_units, year=None, yaml_str=None, str_or_buffer=None)[source]
Parameters:

yaml_str : str, optional

A YAML string from which to load model.

str_or_buffer : str or file like, optional

File name or buffer from which to load YAML.

Returns:

Developer object

keep_form_with_max_profit(forms=None)[source]

This converts the dataframe, which shows all profitable forms, to the form with the greatest profit, so that more profitable forms outcompete less profitable forms.

Parameters:

forms: list of strings

List of forms which compete which other. Can leave some out.

Returns:

Nothing. Goes from a multi-index to a single index with only the

most profitable form.

pick(profit_to_prob_func=None, custom_selection_func=None)[source]

Choose the buildings from the list that are feasible to build in order to match the specified demand.

Parameters:

profit_to_prob_func: function

As there are so many ways to turn the development feasibility into a probability to select it for building, the user may pass a function which takes the feasibility dataframe and returns a series of probabilities. If no function is passed, the behavior of this method will not change

custom_selection_func: func

User passed function that decides how to select buildings for development after probabilities are calculated. Must have parameters (self, df, p) and return a numpy array of buildings to build (i.e. df.index.values)

Returns:

None if there are no feasible buildings

new_buildings : dataframe

DataFrame of buildings to add. These buildings are rows from the DataFrame that is returned from feasibility.

to_dict

Return a dict representation of a SqftProForma instance.

to_yaml(str_or_buffer=None)[source]

Save a model representation to YAML.

Parameters:

str_or_buffer : str or file like, optional

By default a YAML string is returned. If a string is given here the YAML will be written to that file. If an object with a .write method is given the YAML will be written to that object.

Returns:

j : str

YAML is string if str_or_buffer is not given.