Calculating Walk Scores with Python
By: Ablajan Sulaiman
Tags: GIS, Urban, Network, Walk Score, Pedestrians

Introduction

Walkability has been a major focus of urban analytics. Walkable neighborhoods are directly correlated with better health, a lighter environmental impact, cost-efficient living, and an overall better quality of life. It can be a measure to see how friendly an area is for walking by looking at what is around an area and how easy it is to access amenities such as school, hospital, library, stops, stations, arena, supermarket, stores within a specific walking time. Given the ever-growing population and the environmental and economic challenges in modern cities, measuring walk scores is essential for creating a more livable, healthy, and sustainable environment, and boosting economic growth.

Walkability Measures

Measures of walkability have improved substantially over the past decade. Currently, the most widely used measure is Walk Score by Redfin (2007), which assigns scores between 0 and 100. Walk Score measures the walkability of any address, Transit Score measures access to public transit, and Bike Score measures whether a location is good for biking. Numerous other walkability indexes and methods have been developed in health, transport and urban studies (Jiang and Claramunt, 2004; Pikora et al., 2003; Vargo et al. 2009; Ewing, R.; Handy, S. 2009; Maghelal and Capp, 2011; Duncan 2011; Razmik 2014; Lee, S.; Talen, E. 2014; Geraint Ellis, 2015; Migue et al. 2016; Stockton et al., 2016; Mohammad and Taheri, 2017; Dhanani et al., 2017; Moura et al. 2017; Yen et al (2017); Liyin 2017; Allison (2018); Ivan et al., 2019). Matthew (2019) explained measuring walk times from every address point within the City of Toronto limits to the closest Toronto Transit Commission (TTC) stops. The analysis used open-source python libraries such as NetworkX, Pandana to perform the network distance calculations on a new open data set called the “Pedestrian Network” to better understand walkable access to various amenities across Toronto. It includes data sources, creating Pandana pedestrian network, and calculating pedestrian walk times of 24 different types of amenities such as schools, libraries, hospitals, supermarkets, TTC stops, and convenience stores. The basic approach of this study is first to create a pedestrian network as a connected set of links and nodes, then calculate walking distance and minutes based on the walkability (distance and walking times) to these various amenities from every address location within the City. In the walkability analyses, the pedestrian network is a data model resembling a network graph (edges and nodes) weighted by linear distance. Shortest routes were calculated from every building centroid in the city to the nearest nth amenity at the maximum distance of 5000m. Walk times were calculated in the nearest minutes, using the prescribed 1.0m/per-second velocity used by Transportation services. We performed a query, at a max distance of 5000 meters for up to the 10 nearest points-of-interest (amenities) from any building location (or address points ), then calculated walking distance and walking minutes. We then aggregated all these walk times to the census tract level and calculated the minimum, maximum, standard deviation, median, and average walk times. In this tutorial, we will measure walk scores with python by using the walk times result of the Toronto walkability analysis.

Datasets

The following datasets from the City of Toronto’s Open Data are used in the pedestrian network to perform walkability analyses.

The Pedestrian Network



import zipfile

import io

from pyproj import Proj, CRS, transform

url = ‘

local_path = ‘./tmp’

print(‘Downloading zipped shapefile…’)

r = requests.get(url)

z = zipfile.ZipFile(io.BytesIO(r.content))

print(“Done”)

z.extractall(path=local_path) # extract to tmp folder

filenames = [y for y in sorted(z.namelist()) for ending in [‘dbf’, ‘prj’, ‘shp’, ‘shx’] if y.endswith(ending)]

print(filenames)

pednet= gpd.read_file(r’./tmp/pednet.shp’)

#reprojecting epsg 4386 (wgs84) to epsg 2019 (mtm nad 27)

crs = CRS(“EPSG:2019”)

pednet = gpd.GeoDataFrame(pednet, crs=crs, geometry=’geometry’)

pednet.head(5) #Downloading Pedestrian Network Dataimport zipfileimport iofrom pyproj import Proj, CRS, transformurl = ‘ https://ckan0.cf.opendata.inter.prod-toronto.ca/download_resource/3ce9a0a9-c66d-4324-820a-ce44dabb2b46' local_path = ‘./tmp’print(‘Downloading zipped shapefile…’)r = requests.get(url)z = zipfile.ZipFile(io.BytesIO(r.content))print(“Done”)z.extractall(path=local_path) # extract to tmp folderfilenames = [y for y in sorted(z.namelist()) for ending in [‘dbf’, ‘prj’, ‘shp’, ‘shx’] if y.endswith(ending)]print(filenames)pednet= gpd.read_file(r’./tmp/pednet.shp’)#reprojecting epsg 4386 (wgs84) to epsg 2019 (mtm nad 27)crs = CRS(“EPSG:2019”)pednet = gpd.GeoDataFrame(pednet, crs=crs, geometry=’geometry’)pednet.head(5)

Pedestrian Network Data

Pedestrian Network Description: It is comprised of a set of nodes (103622) and edges (126056). It has 125262 records segmented at 100m intervals. The highest node degree is 8 and it has network density of 2.344. In terms of connectivity, the dark color on the maps below indicate highly connected network edges while light color shows lower connectivity (code is available on our GitHub).

Highest Edge Connectivity: 11

Highest Node Degree: 8

Highest Node Connectivity: 0.000077

Highest Node Betweenness: 0.121572

Highest Edge Choice: 1.925886

Highest Edge Integration: 3.66

Address Points

The City of Toronto address point data contains over 500,000 address points within the city.



import zipfile

import io

from pyproj import Proj, CRS, transform

url = ‘

local_path = ‘./tmp’

print(‘Downloading zipped shapefile…’)

r = requests.get(url)

z = zipfile.ZipFile(io.BytesIO(r.content))

print(“Done”)

z.extractall(path=local_path) # extract to tmp folder

filenames = [y for y in sorted(z.namelist()) for ending in [‘dbf’, ‘prj’, ‘shp’, ‘shx’] if y.endswith(ending)]

print(filenames)

address_points= gpd.read_file(r’./tmp/ADDRESS_POINT_WGS84.shp’)

#reprojecting epsg 4386 (wgs84) to epsg 2019 (mtm nad 27)

crs = CRS(“EPSG:2019”)

address_points = gpd.GeoDataFrame(address_points, crs=crs, geometry=’geometry’)

address_points.head(5) # Downloading Address Pointsimport zipfileimport iofrom pyproj import Proj, CRS, transformurl = ‘ https://ckan0.cf.opendata.inter.prod-toronto.ca/download_resource/d918eb68-e077-4bb1-872b-59ebe265d016' local_path = ‘./tmp’print(‘Downloading zipped shapefile…’)r = requests.get(url)z = zipfile.ZipFile(io.BytesIO(r.content))print(“Done”)z.extractall(path=local_path) # extract to tmp folderfilenames = [y for y in sorted(z.namelist()) for ending in [‘dbf’, ‘prj’, ‘shp’, ‘shx’] if y.endswith(ending)]print(filenames)address_points= gpd.read_file(r’./tmp/ADDRESS_POINT_WGS84.shp’)#reprojecting epsg 4386 (wgs84) to epsg 2019 (mtm nad 27)crs = CRS(“EPSG:2019”)address_points = gpd.GeoDataFrame(address_points, crs=crs, geometry=’geometry’)address_points.head(5)

Address Points Data

Building Data



import zipfile

import io

from pyproj import Proj, CRS, transform

url = '

local_path = './tmp'

print('Downloading zipped shapefile...')

r = requests.get(url)

z = zipfile.ZipFile(io.BytesIO(r.content))

print("Done")

z.extractall(path=local_path) # extract to tmp folder

filenames = [y for y in sorted(z.namelist()) for ending in ['dbf', 'prj', 'shp', 'shx'] if y.endswith(ending)]

print(filenames)

building= gpd.read_file(r'./tmp/3DMassing_2019_WGS84.shp')

#reprojecting epsg 4386 (wgs84) to epsg 2019 (mtm nad 27)

crs = CRS("EPSG:2019")

building = gpd.GeoDataFrame(building, crs=crs, geometry='geometry')

building.head(5) # download building dataimport zipfileimport iofrom pyproj import Proj, CRS, transformurl = ' https://ckan0.cf.opendata.inter.prod-toronto.ca/download_resource/a5da8e3e-bb05-42f1-9f5e-646cb95d9690' local_path = './tmp'print('Downloading zipped shapefile...')r = requests.get(url)z = zipfile.ZipFile(io.BytesIO(r.content))print("Done")z.extractall(path=local_path) # extract to tmp folderfilenames = [y for y in sorted(z.namelist()) for ending in ['dbf', 'prj', 'shp', 'shx'] if y.endswith(ending)]print(filenames)building= gpd.read_file(r'./tmp/3DMassing_2019_WGS84.shp')#reprojecting epsg 4386 (wgs84) to epsg 2019 (mtm nad 27)crs = CRS("EPSG:2019")building = gpd.GeoDataFrame(building, crs=crs, geometry='geometry')building.head(5)

Downloading Building Data

Amenities

Amenities and Column Names

Let’s load the amenity data from Postgres database.

# load data from pg database total 24 layers

con = psycopg2.connect(database=”walk", user=”postgres”, password=’****',

host=”17**.***",port=”5432")

#service layers

foodconv = gpd.read_postgis(‘SELECT * FROM staging.food_convenience_store’,con,crs={‘init’: ‘epsg:2019’})

fast_food = gpd.read_postgis(‘SELECT * FROM staging.food_fastfood’,con,crs={‘init’: ‘epsg:2019’})

supermarket = gpd.read_postgis(‘SELECT * FROM staging.food_supermarket’,con,crs={‘init’: ‘epsg:2019’})

hospital = gpd.read_postgis(‘SELECT * FROM staging.health_hospital’,con,crs={‘init’: ‘epsg:2019’})

sxhclinic = gpd.read_postgis(‘SELECT * FROM staging.health_sexual_health_clinic’,con,crs={‘init’: ‘epsg:2019’})

wkclinic = gpd.read_postgis(‘SELECT * FROM staging.health_walkin’,con,crs={‘init’: ‘epsg:2019’})

arena = gpd.read_postgis(‘SELECT * FROM staging.publicservice_arena’,con,crs={‘init’: ‘epsg:2019’})

daycare = gpd.read_postgis(‘SELECT * FROM staging.publicservice_day_care_centre’,con,crs={‘init’: ‘epsg:2019’})

dropin = gpd.read_postgis(‘SELECT * FROM staging.publicservice_dropin’,con,crs={‘init’: ‘epsg:2019’})

ems = gpd.read_postgis(‘SELECT * FROM staging.publicservice_ems’,con,crs={‘init’: ‘epsg:2019’})

famcentre = gpd.read_postgis(‘SELECT * FROM staging.publicservice_family_resource_centre’,con,crs={‘init’: ‘epsg:2019’})

fire = gpd.read_postgis(‘SELECT * FROM staging.publicservice_fire_facility’,con,crs={‘init’: ‘epsg:2019’})

library = gpd.read_postgis(‘SELECT * FROM staging.publicservice_library’,con,crs={‘init’: ‘epsg:2019’})

police = gpd.read_postgis(‘SELECT * FROM staging.publicservice_police_facility’,con,crs={‘init’: ‘epsg:2019’})

recreation = gpd.read_postgis(‘SELECT * FROM staging.publicservice_recreation’,con,crs={‘init’: ‘epsg:2019’})

school = gpd.read_postgis(‘SELECT * FROM staging.publicservice_school’,con,crs={‘init’: ‘epsg:2019’})

thingstodo = gpd.read_postgis(‘SELECT * FROM staging.publicservice_thingstodo’,con,crs={‘init’: ‘epsg:2019’})

voteloc = gpd.read_postgis(‘SELECT * FROM staging.publicservice_voting_location’,con,crs={‘init’: ‘epsg:2019’})

bike_parking = gpd.read_postgis(‘SELECT * FROM staging.transit_bicycle_parking_on_street’,con,crs={‘init’: ‘epsg:2019’})

bike_station = gpd.read_postgis(‘SELECT * FROM staging.transit_bicycle_station_indoor’,con,crs={‘init’: ‘epsg:2019’})

ttc_station = gpd.read_postgis(‘SELECT * FROM staging.transit_ttc_station’,con,crs={‘init’: ‘epsg:2019’})

ttc_accessible = gpd.read_postgis(‘SELECT * FROM staging.transit_ttc_station_accessible’,con,crs={‘init’: ‘epsg:2019’})

ttc_stop = gpd.read_postgis(‘SELECT * FROM staging.transit_ttc_stop’,con,crs={‘init’: ‘epsg:2019’})

ts = gpd.read_postgis(‘SELECT * FROM staging.tscw’,con,crs={‘init’: ‘epsg:2019’})

The above-mentioned datasets are used for creating Pandana Network and calculating walk times of 24 different types of amenities such as schools, libraries, hospitals, supermarkets, TTC stops and convenience stores. Creating pandana pedestrian network and measuring walk times have already been stated in Matthew’s (2019) tutorial and posted on our GitHub repository, therefore, they are not repeated here. We will be using the walk times to calculate the walk scores of Toronto from any building location (or address points).

Walk Scores

Walk score represents an attempt to quantify how accessible a neighborhood is on foot. In the pedestrian network of Toronto, the walk score algorithm produced scores from 0 to 100 based on walk times. It measures the walkability of any building location (or address point) to nearby destinations. For each location, the pandana pedestrian network calculates the nearest pedestrian network routes to nearby amenities in the Toronto walkability analysis. POIs are calculated based on the walking minutes (distance by minutes) to various amenities via pedestrian sidewalks networks. A five-minute walk which covers about 500m, is a comfortable distance for pedestrians to access services such as a bus stop, schools, or supermarkets. Amenities within a 5-minute walk are given maximum points. A decay function is used to give points to more distant amenities, with fewer points given after a 30-minute walk. Average walk scores are calculated by aggregating walk scores of all 24 amenity layers such as schools, libraries, hospitals, supermarkets, TTC stops and convenience stores.

Walk Scores

Let’s load the walk times result of building locations and preprocess the data to calculate walk scores.

#load calculated walk times and spatially joined census, pedestrain, and building data.

password = open(‘password.txt’,’r’).readline()

username = ‘postgres’

walkability_engine = create_engine(‘postgresql://%s:%s@*ip**:5432/walkability’ %(username,password))

buildings_sumfin = gpd.read_postgis(“select * from results.buildings_summary_walktimes_fin”, walkability_engine, ‘geom’)

census_sumfin = gpd.read_postgis(“select * from results.census_summary_walktimes_fin”, walkability_engine, ‘geom’)

pednet_sumfin = gpd.read_postgis(“select * from results.pednet_summary_walktimes_fin”, walkability_engine, ‘geom’)

buildings = gpd.read_postgis(“select * from staging.buildings327”, walkability_engine, ‘geom’)

Let’s inspect building walk times columns to calculate walk scores. There are 48 walk times columns which include the first closest point (m_aminity_0) and the second closest point (m_aminity_1)for each amenity.

# keep walk times columns

buildingws = buildings[['m_fc_0','m_fc_1','m_ff_0','m_ff_1','m_sm_0','m_sm_1','m_hp_0','m_hp_1','m_sxhc_0','m_sxhc_1','m_wc_0','m_wc_1','m_sc_0','m_sc_1','m_ar_0','m_ar_1','m_dc_0','m_dc_1','m_di_0','m_di_1','m_ems_0','m_ems_1','m_frc_0','m_frc_1','m_fr_0','m_fr_1','m_lb_0','m_lb_1','m_plc_0','m_plc_1','m_rec_0','m_rec_1','m_thing_0','m_thing_1','m_vtl_0','m_vtl_1','m_bp_0','m_bp_1','m_bs_0','m_bs_1','m_ttcstop_0','m_ttcstop_1','m_ttcst_0','m_ttcst_1','m_ttcacc_0','m_ttcacc_1','m_ts_0','m_ts_1']]

buildingws["m_lb_0"].describe()

The closest POI Walk Times Description to Library (m_lb_0 Column)

“HiScore” is a python library for making scoring functions, which map objects (vectors of numerical attributes) to scores (a single numerical value). HiScore provides a new way to quickly create and improve intuitive scoring functions: by using reference sets, a set of representative objects that are assigned scores. We can create a scoring function by calling `hiscore.create` with this reference set:

import hiscore

#generate a scoring function by calling hiscore.create with this reference set

reference_set = {(0): 100, (5): 90, (10): 80, (15): 70, (20): 60, (25): 50, (30): 40, (40): 30, (50): 20, (60): 10, (70): 0}

score_function = hiscore.create(reference_set, [-1], minval=0, maxval=100)

list(zip(reference_set.keys(), np.round(score_function.calculate(reference_set.keys()))))

Score Reference Set

Let’s create walk score columns based on walk times columns in the data frame.

# Creating walk score columns

for col in buildingws.columns:

st = col.replace(‘m’, ‘ws’)

buildingws[st]= buildingws[col]

buildingws.head(2)

Now walk times of each column can be calculated in the data frame by using the HiScore reference set function.

buildingws[‘ws_fc_0’] = buildingws.apply(lambda row: np.round(score_function.calculate(row.ws_fc_0)) , axis=1)

Walk scores of ws_fc_0 column

The walk scores of building locations can be calculated using a loop as below:

#calculate walk scores in a loop. This method is slower than the previous one

for col in buildingws.columns:

st = col.replace(‘m’, ‘ws’)

buildingws[st]= np.round(score_function.calculate(buildingws[col]))

Walk Score Calculation

The above-mentioned walk score calculation method can be applied to the other result layers such as pednet_sumfin, buildings_sumfin, and census_sumfin. In the end, minimum, maximum, median, and average walk scores are calculated based on the aggregated minimum, maximum, median, and average walk times of all the amenities from any building location (or address points ).

x = buldingws.plot(figsize=(15, 15), column=’ws0_avg’, scheme=’quantiles’, legend=True,markersize = 1)

ttc_stop.plot(ax=ax, color=’red’, markersize =1, alpha=0.99, categorical =True,legend=True)

plt.title(‘Walk Scores based on Average Walk Minutes’)

Walk Scores based on Average Walk Minutes

Result

Walk scores are calculated by using 572 census tracts, pedestrian networks, and building locations in Toronto to see which parts of the city are the most pedestrian-friendly. The map of the City of Toronto’s Pedestrian Average Walk Score illustrates that: while the urban core of Toronto is highly pedestrian-friendly, many areas of the city are not. The census tracts (535001500, 535003402 and 535003401) in the downtown core, have the best scores at 84.5, 83.5, and 83 out of 100, respectively by census tracts.

Walk Scores by Census Tracts

The highest average walk score is 89.52 while the minimum average walk score is 1.04 by building locations in Toronto.

Walk Scores by Building Locations

Conclusion

Walkability WebApp

This tutorial is based on the walk times result of our previous work of the Toronto walkability analysis. Our code and brief tutorial are available on our GitHub (Our full report can be found here). I hope you enjoyed the tutorial. Please don’t hesitate to write comments and questions.

References: