Code
import osmnx as ox
import matplotlib.pyplot as plt
import geopandas as gpd
import seaborn as sns
import numpy as np
import pandas as pd
import pandana as pndaKhoo Wei Yang
December 31, 2025
Walkability is a core urban design element. Pedestrian infrastructure often lays the foundation for basic mobility in urban spaces, it is also the bridge that connects other modes of transport. In Kuala Lumpur, moving around by car has become the default, if not the only mode of mobility. However, walking should not be an afterthought.
Walking is not just another means of getting around alternative to cars. Besides being the most practical and sustainable form of mobility, walking promotes regular physical activity for improved health outcomes (Lee and Buchner 2008), reduced environmental pollution, among other benefits.
Streets built for walking are essential to a livable city. Urbanist Jane Jacobs believed in the importance of sidewalks and pedestrian activity as generators of diversity, suggesting that uses of sidewalk in shaping safety, and contact among people (Jacobs 1992). In her conception, well-designed, walkable streets are not only key to making people walk, but a living part of the city.
Higher walkability in cities has shown positive effects on health, quality of life and the environment (Baobeid, Koç, and Al-Ghamdi 2021). Neighborhoods with higher walkability ratings tend to have higher property values and more vibrant local businesses (Chernobai and Ma 2022).
However, walkability can be a difficult object to measure, test and design. A range of elements compose a walkable city. For pedestrians, some immediately perceptible built elements such as the presence of sidewalks and crosswalks, shades, street lighting and traffic-calming measures come to mind. Besides infrastructure completeness, overall design and layout of built environment of a city and its amenities also influences the foot-accessibility to destinations such as schools and shops. These factors can vary in different parts of the city, from one street to the next, and experienced differently from one person to the other.
For instance, Kuala Lumpur is known for its walkability problems, but just how un-walkable it is? Asking different people who access and/or live in different parts of the city might yield different answers. Our knowledge of spatial attributes of the city is inconsistent, making it challenging to create an overall picture of city walkability and a single, definitive goal of walkability for the city.
To systematically understand a city’s walkability, we must first conceptualise walkability. Despite thrown about by practitioners, researchers, policy-makers and the public, there is no agreed upon definition of the concept (Tobin et al. 2022).
In the urban design literature, walkability is often understood in terms of urban morphology — the physical form and structure of urban areas. A few spatial measures commonly used to understand urban walkability are (1) permeability and (2) connectivity.
This article seek to explore and illustrate the use of urban morphology of Greater Kuala Lumpur to understand walkability of the city. To do this, I collect and analyse the street network data of Greater KL through OSMnx and pandana, python packages that enable is to model and analyse geospatial data from OpenStreetMap, an open-source map database.
Streets of a city represent a type of spatial network, which can be represented in graph. Graphs are made up of edges and nodes (vertices). Intersections are nodes that connect at least two edges, which represent streets.
Intersection density measures the number of intersections in street network per unit area. We use street intersections to proxy permeability. Permeability describes the extent to which urban forms permit mobility in different directions.
Figure 1 shows three city street networks with varying levels of permeability. An area with higher intersection density requires less out of direction travel to get from point A to point B, so distances are shorter and more conducive to taking trips without a car (City of Eugene, n.d.).
Figure 2 depicts the graph representation of the street networks of Kuala Lumpur. At first glance, we observe that the street designs are closely adapted to its terrain. Unlike the gridiron plan of New York and Kyoto, Kuala Lumpur follows an organic layout that is responsive to its topography, leading to curvilinear streets and irregular lots. The extent of street network is also limited by some areas with forest reserves and/or hill fomrations.
To obtain the intersection density of the city, we divide the entire street network into equally sized area. We then count up the number of intersection in each area. Figure 3 shows the intersection per hexagonal area.
# setting plot style parameters
COLOR = 'white'
plt.rcParams['text.color'] = COLOR
plt.rcParams['axes.labelcolor'] = COLOR
plt.rcParams['xtick.color'] = COLOR
plt.rcParams['ytick.color'] = COLOR
fig, ax = plt.subplots(figsize=(20,15))
ax.set_aspect('equal')
fig.set_facecolor('black')
ax.set_facecolor('black')
hb = ax.hexbin(
x=nodes_s['x'],
y=nodes_s['y'],
gridsize=[75,65],
cmap='viridis',
mincnt=1,
vmax=70,
)
cb = plt.colorbar(hb, ax=ax, shrink=0.4, ticks=[1, 20, 40, 60, 80, 100])
cb.ax.tick_params(color='none', labelsize=20)
cb.ax.set_yticklabels(['1', '20', '40', '60', '80', '>= 100'])
cb.set_label('Intersections per hexagon', fontsize=20, fontweight='bold')
plt.tight_layout()
plt.savefig('/output/intersection_hexbin.png')
From Figure 3, we see that intersection-dense areas are concentrated around the Central Business District (CBD), surrounded by Jalan Tun Razak. This area include places such as Pasar Seni, Kampung Baru, Bukit Bintang and the like. Small disconnected spots of highly intersected areas can also be found in Bangsar-Universiti Malaya, and Kepong area.
The uneven intersection densities across the city mean that certain areas are not as permeable as others, making getting around in those areas more difficult by walking. Whereas accessing places in areas with higher permeability is easier by foot. Apart from that, moving from one place to the next in the same area is also easier by foot, further incentivising walking within these areas. It is no wonder why the CBD area is known for its tourism and dense office buildings.
Figure 4 shows the distribution of the number of intersections across all binned intersection (each observation is one bin attached to a value of intersection counts). As a whole, the city doesn’t appear to be evenly permeable — a majority of unit areas found in the city street network contains zero to ten intersections. Each equally-sized area of the city has, on average, roughly 8.27 intersections.
Knowing how permeable a city is might not tell us a great deal about walkability. The spatial distribution of places of interest (POIs) also plays an important role. It is crucial that the design of urban spaces can ensure that sociable places and key amenities like parks, libraries, shops, schools, and hospitals are within reach of walking distance. But what does this mean exactly?
Carlos Moreno popularised the “15-minute city” concept, an urban design goal that emphasises proximity. A short definition of the concept reads:
The 15-minute city represents an urban model in which the essential needs of residents are accessible on foot or by bicycle within a short perimeter in high-density areas.
The model suggests that travel time contributes to urban walkability. People are more likely to walk if destinations are a stone throw away. When key amenities are strategically placed within 15-minute radii, people are more likely to walk. A study conducted by MIT researchers found strong correlation between the accessibility of amenities within a 15-minute walk from home and the usage of those amenities (Abbiasov et al. 2022). Simply by having places that are close to each other can improve visits to and usage of those places. A high variety of places that are close to each other incentivises people to walk in taking journeys and performing their daily activities.
Proximity is a function of network distance — the shortest path or the lowest cost-time to travel between two locations, considering the network’s structure and constraints. By analysing the city’s street as networks, we can measure how walkable to each POI is within the city.
Each street of a city is an edge in a graph. Walking at a constant speed, each street has a travel time based on its length, we give each edge a travel time as its weight. Then we can compute how long it takes to walk 5, 10, 15 minutes away from each node.
In a graph, we can calculate how much time is spent walking down each edge to reach a node and in reverse, find how far we can reach within a map from that node.
To find how much time it takes to walk to any nearest POI in a city, we can reverse the calculation, by calculating how far you can reach away from the POI. However, it would be difficult to approximate how far you can walk from any POI without a reference point on the network. We can take any intersection (nodes on our network) as reference.
As the POIs of our interest may not lie directly on the network (for example, the exact coordinate of a hospital can be a little far from the streets, even though it is walkable from the street), we can approximate their position on the network by proxying it to the nearest intersection to the POI.
Thus, we can find how many intersections can be reach within a defined time if we start from the POI (now proxied at an intersection), and we will have an approximatation of how walkable is it to the nearest POI within any area in the city.
We define the POIs of our interest as key amenities and sociable third places, examples of which are shown in the code below.
# select points of interest based on osm tags
tags = {
'amenity':['school',
'college',
'university',
'library',
'hospital',
'public_service',
'post_office',
'townhall',
'community_centre'],
'shop':['mall'],
'leisure':['park']
}
# Get amentities from place
pois = ox.features.features_from_place(cityname, tags=tags)
# Project pois
pois = pois.to_crs(epsg=crs)Generating contraction hierarchies with 8 threads.
Setting CH node vector of size 69463
Setting CH edge vector of size 194200
Range graph removed 198912 edges of 388400
. 10% . 20% . 30% . 40% . 50% . 60% . 70% . 80% . 90% . 100%
# plot the walking time to nearest POIs
fig, ax = plt.subplots(figsize=(12,15))
ax.set_axis_off()
ax.set_aspect('equal')
fig.set_facecolor((0,0,0))
# plot distance to nearest POI
sc = ax.scatter(
x=nodes['x'],
y=nodes['y'],
c=distances[1],
s=1,
cmap='viridis_r',
)
cb = fig.colorbar(sc, ax=ax, shrink=0.4, ticks=[0, 300, 600, 900])
cb.ax.tick_params(color='none', labelsize=20)
cb.ax.set_yticklabels(['0', '5', '10', '>= 15'])
cb.set_label('Walking time to nearest POI (minutes)', fontsize=20, fontweight='bold')
plt.tight_layout()
plt.savefig('/output/walk_access.png')
We collect the coordinates of amenity POIs like schools, hospitals, libraries, public services, townhalls, community centres, malls and parks throughout the city. We then look at the nearest 10 POIs from any POI.
Figure 5 shows the amount of time it takes to walk to the nearest ten POIs from any point on the street network. The brighter areas indicate that more POIs can be reached within shorter walking time. Being in the dimmer areas require 15 minutes or more to reach the nearest POI (in all directions). Brighter areas also signifies higher densities of POIs, making it easier to get from one to another in those areas.
Walkable POIs are distributed in patchwork throughout the city. Areas where walking to another POI takes more than 15 minutes are littered across and in between areas where the same walking takes shorter time. These darker patches could be geographically or topologically determined (e.g., forest reserves or river systems). Visually gauging the base graph ( Figure 2 ), we can conclude that this is the case generally.
Walking is also complemented by other modes of transport. Most public transport journeys start and end with walk (UITP 2024). Pedestrian-accessible public transport systems reduce the frequency of changing between modes, making journeys in the city frictionless.
To reduce car usage, sustainable public transport policies should promote pedestrian access to public transport. Currently, public transport connectivity outside the KL city centre is addressed by providing “Park-and-ride” option, allowing commuters to drive from nearby residential suburbs to Light Rail, Mass Rapid Transit or Land Commuter stations. This solution works to reduce the inbound vehicle traffic to city centre, but served to divert traffic elsewhere.
The solution only address the short-term problem of traffic management (and mostly inbound traffic). As more and more of the city outskirts develop into residential suburbs, and further into urban areas of their own right, traffic flow worsens in these areas, even creating problems of their own (Chan 2025).
Direct foot access to public transport is key to increase public transport usage, this means making access to public transport points like metro and bus stations walkable. Like the exercise above, we can assess the walkability of public transport points by finding the walking time on the street networks towards each public transport point. To do this, we can rely on isochrones.
| geometry | monorail | name | name:en | name:zh | network | public_transport | railway | wheelchair | name:ms | ... | name:ar | wikimedia_commons | old_network | addr:suburb | internet_access:fee | name:zh-Latn-pinyin | note | name:uk | passenger | type | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| element | id | |||||||||||||||||||||
| node | 243306384 | POINT (101.62853 3.20871) | NaN | KA07 Kepong Sentral | Kepong Sentral | 甲洞中央 | NaN | station | station | yes | Kepong Sentral | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 1485787800 | POINT (101.78779 2.93958) | NaN | KB07 UKM | UKM | 马来西亚国立大学 | KTM Komuter | station | station | yes | UKM | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 3018417115 | POINT (101.70566 2.75259) | yes | KLIA Terminal 1 (Main Building) | KLIA Terminal 1 (Main Building) | 吉隆坡国际机场1号主航站楼 | KLIA Aerotrain | station | station | yes | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 3018417116 | POINT (101.7128 2.74743) | yes | KLIA Terminal 1 (Satellite Building) | NaN | KLIA卫星航站楼A | KLIA Aerotrain | station | station | yes | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 4052254321 | POINT (101.63772 3.46853) | NaN | KA12 Batang Kali | Batang Kali | 峇冬加里 | NaN | station | station | yes | Batang Kali | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| way | 1069157299 | POINT (101.66058 2.98311) | NaN | PY37 Putra Permai | Putra Permai | 布特拉柏迈 | Putrajaya Line | station | station | NaN | Putra Permai | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 1073736705 | POINT (101.57999 3.20624) | NaN | KA08 Sungai Buloh | Sungai Buloh | 双溪毛糯 | KTM ETS | station | station | yes | Sungai Buloh | ... | NaN | NaN | NaN | NaN | NaN | Shuāngxī Máonuò | NaN | NaN | NaN | NaN | |
| 1081572645 | POINT (101.68138 3.23792) | NaN | KC05 Batu Caves | Batu Caves | 黑风洞 | KTM Komuter | station | station | yes | Batu Caves | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 1084713840 | POINT (101.44967 3.04309) | NaN | KD14 Klang | Klang | 巴生 | KTM Komuter | station | station | yes | Klang | ... | NaN | NaN | NaN | NaN | NaN | NaN | Built in 1890 and it is still serving the town. | NaN | NaN | NaN | |
| 1180021471 | POINT (101.79053 2.98267) | NaN | KB06 Kajang | Kajang | 加影 | Seremban Line | station | station | limited | Kajang | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
102 rows × 63 columns
Isochrones visually represent the travel time to a point in a network. Isochrones are typically computed by generating shortest-path trees on graphs, then generating a convex hull around the accessible POI. Put differently, each concentric ring of the isochrone represent the further extent one can reach by walking within a fixed duration. The wider the isochrones, the further one can walk out of direction from that point within the same time, making it more accessible by walking. For example, taking five minutes to walk to a train station
We draw isochrones around a random sample of LRT, MRT and KTM stations in Greater Kuala Lumpur to produce the average out of direction travel distance, based on 5 minutes, 10 minutes and 15 minutes on foot, assuming the same walk speed. To do this, we use the openreouteservice API provided by HeiGIT. We visualise this in Figure 6.
# iterate over all stations
walk_time = [300, 600, 900]
colors_map = [ "#2ca02c", "#1f76b4a0", "#ff7f0e",] # example hex colors
for _, station in stations.sample(50).iterrows():
lon, lat = station.geometry.x, station.geometry.y
# plot isochrones using openrouteservice
iso = client.isochrones(
locations=[[lon, lat]],
profile="foot-walking",
range=walk_time,
)
iso_gdf = gpd.GeoDataFrame(
[{"geometry": shape(f["geometry"]), "value": f["properties"]["value"]} for f in iso["features"]],
crs="EPSG:4326"
)
# plot each isochrone with its corresponding color
for val, color in zip(walk_time, colors_map):
subset = iso_gdf[iso_gdf["value"] == val]
folium.GeoJson(
subset,
style_function=lambda feature, c=color: {
"fillColor": c,
"color": c,
"fillOpacity": 0.3,
"weight": 1
}
).add_to(m)
folium.Marker([lat, lon], popup=station["name"]).add_to(m)
time.sleep(2) # avoid rate limitNow that we have an idea of what walkability looks like in Greater KL using urban morphological measures, let’s put together an index to compare across localities in Greater KL. We use the same measures of permeability based on overall intersection density, and accessibility based on POI proximity to derive a simple composite score of walkability. We compare walkability of localities of the Greater KL conurbation and create a harmonised index using this score.
Figure 7 shows the ranking of localities by walkability.
def calculate_poi_accessibility(localities, tags):
"""
Calculates reachable POIs within 10 mins walk.
"""
WALK_TIME_MIN = 10
MAX_DIST_SEC = WALK_TIME_MIN * 60
all_walktime = []
for locality in localities:
try:
# 1. Get Street Network and project it
G = ox.graph_from_place(locality, network_type="walk")
G_proj = ox.project_graph(G)
nodes_proj, edges_proj = ox.graph_to_gdfs(G_proj)
# 2. Get POIs and project to match G
pois = ox.features_from_place(locality, tags=tags)
if pois.empty:
continue
pois_proj = pois.to_crs(nodes_proj.crs)
# 3. Add travel times
# Note: add_edge_speeds expects unprojected usually,
# or use G_proj but ensure 'length' is in meters.
G_proj = ox.routing.add_edge_speeds(G_proj, fallback=4.5)
G_proj = ox.routing.add_edge_travel_times(G_proj)
nodes = ox.graph_to_gdfs(G_proj, edges=False)[['x', 'y']]
edges = ox.graph_to_gdfs(G_proj, nodes=False).reset_index()[['u', 'v', 'travel_time']]
print(edges['travel_time'].describe())
# 4. Pandana Network
network = pnda.Network(
node_x=nodes['x'],
node_y=nodes['y'],
edge_from=edges['u'],
edge_to=edges['v'],
edge_weights=edges[['travel_time']]
)
# 5. Pre-calculate nearest nodes for POIs to avoid redundant lookups
# We use the projected coordinates
# Extract centroids from the pois' geometries
centroids = pois_proj.centroid
poi_nearest_nodes = ox.nearest_nodes(G_proj, X=centroids.x, Y=centroids.y)
# Link POIs to the network
network.set_pois(
category='my_pois',
maxdist=MAX_DIST_SEC,
maxitems=300,
x_col=centroids.x,
y_col=centroids.y
)
# 6. Calculate reachability
# This returns travel times to the Nth nearest POI
reachable_pois_df = network.nearest_pois(
distance=MAX_DIST_SEC,
category='my_pois',
num_pois=300
)
is_under_limit = reachable_pois_df < MAX_DIST_SEC
node_counts = is_under_limit.sum(axis=1)
poi_reachable_counts = pd.Series(poi_nearest_nodes).map(node_counts)
poi_scores_df = pd.DataFrame({
'locality': locality,
'POI_ID': pois.index,
'Reachable_POIs_Count': poi_reachable_counts.values - 1 # Subtract 1 for self-reachability
})
all_walktime.append(poi_scores_df)
except Exception as e:
print(f"Error in {locality}: {e}")
return all_walktimedef get_intersection_density(localities):
"""
Get intersection density stats from OSMnx for all localities.
"""
all_intersection_stats = []
for locality in localities:
try:
# get street network and project it
G = ox.graph_from_place(locality, network_type="walk")
G_proj = ox.project_graph(G)
nodes_proj, edges_proj = ox.graph_to_gdfs(G_proj)
# get geocode gdf for area calculation
gdf = ox.geocode_to_gdf(locality)
gdf_proj = ox.projection.project_gdf(gdf)
area_m2 = gdf_proj.geometry.area.iloc[0]
# get graph stats
graph_stats = ox.basic_stats(G_proj, area = area_m2)
intersection_stats = pd.DataFrame({
'locality': [locality],
'area_m2': [area_m2],
'intersection_count' : [graph_stats['intersection_count']],
'intersection_density_km' : [graph_stats['intersection_density_km']],
'street_density_km' : [graph_stats['street_density_km']]
})
all_intersection_stats.append(intersection_stats)
except Exception as e:
print(f"Error in {locality}: {e}")
return all_intersection_statsgkl_localities = [
"Kuala Lumpur, Malaysia",
"Putrajaya, Malaysia",
"Petaling Jaya, Selangor, Malaysia",
"Shah Alam, Selangor, Malaysia",
"Subang Jaya, Selangor, Malaysia",
"Klang, Selangor, Malaysia",
"Ampang Jaya, Selangor, Malaysia",
"Kajang, Selangor, Malaysia",
"Selayang, Selangor, Malaysia",
"Rawang, Selangor, Malaysia",
"Sepang, Selangor, Malaysia",
"Kuala Langat, Selangor, Malaysia",
"Kuala Selangor, Selangor, Malaysia"
]
# select points of interest based on osm tags
tags = {
'amenity':['school',
'college',
'university',
'library',
'hospital',
'public_service',
'post_office',
'townhall',
'community_centre'],
'shop':['mall'],
'leisure':['park']
}| locality | area_m2 | intersection_count | intersection_density_km | street_density_km | |
|---|---|---|---|---|---|
| 0 | Kuala Lumpur, Malaysia | 2.430915e+08 | 59148 | 243.315754 | 22863.352916 |
| 1 | Putrajaya, Malaysia | 4.910234e+07 | 6305 | 128.405277 | 16388.227971 |
| 2 | Petaling Jaya, Selangor, Malaysia | 9.795429e+07 | 19819 | 202.329056 | 20876.207334 |
| 3 | Shah Alam, Selangor, Malaysia | 3.014190e+08 | 37923 | 125.814915 | 16179.382908 |
| 4 | Subang Jaya, Selangor, Malaysia | 1.636344e+08 | 25758 | 157.411912 | 18422.921063 |
| 5 | Klang, Selangor, Malaysia | 5.548601e+08 | 38044 | 68.565027 | 9303.656254 |
| 6 | Ampang Jaya, Selangor, Malaysia | 1.393673e+08 | 10836 | 77.751379 | 9155.561476 |
| 7 | Kajang, Selangor, Malaysia | 8.067826e+08 | 38420 | 47.621257 | 6635.298135 |
| 8 | Selayang, Selangor, Malaysia | 5.239606e+08 | 22224 | 42.415400 | 6281.163785 |
| 9 | Sepang, Selangor, Malaysia | 5.517062e+08 | 21049 | 38.152555 | 6421.064389 |
| 10 | Kuala Langat, Selangor, Malaysia | 8.541135e+08 | 19510 | 22.842397 | 4702.276361 |
| 11 | Kuala Selangor, Selangor, Malaysia | 1.195929e+09 | 24995 | 20.900064 | 3832.956385 |
| locality | mean_reach | max_reach | min_reach | total_pois | |
|---|---|---|---|---|---|
| 0 | Ampang Jaya, Selangor, Malaysia | 117.403141 | 183 | 0 | 191 |
| 1 | Kajang, Selangor, Malaysia | 124.234615 | 238 | 2 | 520 |
| 2 | Klang, Selangor, Malaysia | 175.025641 | 254 | 4 | 312 |
| 3 | Kuala Langat, Selangor, Malaysia | 37.459459 | 74 | 1 | 185 |
| 4 | Kuala Lumpur, Malaysia | 259.095792 | 299 | 0 | 1117 |
| 5 | Kuala Selangor, Selangor, Malaysia | 57.924171 | 90 | 3 | 211 |
| 6 | Petaling Jaya, Selangor, Malaysia | 166.797784 | 261 | 0 | 361 |
| 7 | Putrajaya, Malaysia | 73.892857 | 99 | 0 | 112 |
| 8 | Selayang, Selangor, Malaysia | 62.021583 | 107 | 0 | 278 |
| 9 | Sepang, Selangor, Malaysia | 54.053030 | 93 | 1 | 264 |
| 10 | Shah Alam, Selangor, Malaysia | 156.615514 | 299 | 1 | 593 |
| 11 | Subang Jaya, Selangor, Malaysia | 101.607004 | 160 | 9 | 514 |
import scipy
from scipy.stats import zscore
# now we compute walkability scores for localities
walkability_score = pd.merge(permeability_score, accessibility_score, on = "locality")
def compute_walkability_index(df):
"""
Calculate harmonized walkablity scores and index from permeability and accessibilty measure.
"""
# we use log1p (log(1+x)) to avoid errors if total_pois is 0
# this rewards a city for having 100 vs 10 POIs more than 1000 vs 910
df['log_poi_variety'] = np.log1p(df['total_pois'])
# z scaling (The Python 'scale()')
# We standardize ALL three components so they contribute equally
# ddof=0 matches R's default population standard deviation calculation
df['z_intersection'] = zscore(df['intersection_density_km'], nan_policy='omit')
df['z_reach'] = zscore(df['mean_reach'], nan_policy='omit')
df['z_variety'] = zscore(df['log_poi_variety'], nan_policy='omit')
# 3. Harmonization with Weighting
# Logic: 40% Connectivity, 40% Amenity Reach, 20% Total Variety
df['walkability_index'] = (
(df['z_intersection'] * 0.4) +
(df['z_reach'] * 0.4) +
(df['z_variety'] * 0.2)
)
# 4. Final 0-100 Score for Interpretation
min_idx = df['walkability_index'].min()
max_idx = df['walkability_index'].max()
df['walkability_score'] = 100 * (df['walkability_index'] - min_idx) / (max_idx - min_idx)
return df.sort_values(by='walkability_score', ascending=False)
# calculate harmonized measure of intersection density and mean number of pois accessible out of direction
| locality | area_m2 | intersection_count | intersection_density_km | street_density_km | mean_reach | max_reach | min_reach | total_pois | log_poi_variety | z_intersection | z_reach | z_variety | walkability_index | walkability_score | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Kuala Lumpur, Malaysia | 2.430915e+08 | 59148 | 243.315754 | 22863.352916 | 259.095792 | 299 | 0 | 1117 | 7.019297 | 2.067593 | 2.305858 | 2.078190 | 2.165018 | 100.000000 |
| 2 | Petaling Jaya, Selangor, Malaysia | 9.795429e+07 | 19819 | 202.329056 | 20876.207334 | 166.797784 | 261 | 0 | 361 | 5.891644 | 1.484581 | 0.823626 | 0.192024 | 0.961688 | 63.292471 |
| 3 | Shah Alam, Selangor, Malaysia | 3.014190e+08 | 37923 | 125.814915 | 16179.382908 | 156.615514 | 299 | 1 | 593 | 6.386879 | 0.396214 | 0.660107 | 1.020378 | 0.626604 | 53.070751 |
| 4 | Subang Jaya, Selangor, Malaysia | 1.636344e+08 | 25758 | 157.411912 | 18422.921063 | 101.607004 | 160 | 9 | 514 | 6.244167 | 0.845662 | -0.223285 | 0.781671 | 0.405285 | 46.319421 |
| 5 | Klang, Selangor, Malaysia | 5.548601e+08 | 38044 | 68.565027 | 9303.656254 | 175.025641 | 254 | 4 | 312 | 5.746203 | -0.418132 | 0.955759 | -0.051247 | 0.204802 | 40.203687 |
| 7 | Kajang, Selangor, Malaysia | 8.067826e+08 | 38420 | 47.621257 | 6635.298135 | 124.234615 | 238 | 2 | 520 | 6.255750 | -0.716044 | 0.140096 | 0.801045 | -0.070170 | 31.815687 |
| 6 | Ampang Jaya, Selangor, Malaysia | 1.393673e+08 | 10836 | 77.751379 | 9155.561476 | 117.403141 | 183 | 0 | 191 | 5.257495 | -0.287461 | 0.030388 | -0.868683 | -0.276566 | 25.519597 |
| 1 | Putrajaya, Malaysia | 4.910234e+07 | 6305 | 128.405277 | 16388.227971 | 73.892857 | 99 | 0 | 112 | 4.727388 | 0.433060 | -0.668352 | -1.755366 | -0.445190 | 20.375725 |
| 8 | Selayang, Selangor, Malaysia | 5.239606e+08 | 22224 | 42.415400 | 6281.163785 | 62.021583 | 107 | 0 | 278 | 5.631212 | -0.790095 | -0.858995 | -0.243587 | -0.708353 | 12.347944 |
| 9 | Sepang, Selangor, Malaysia | 5.517062e+08 | 21049 | 38.152555 | 6421.064389 | 54.053030 | 93 | 1 | 264 | 5.579730 | -0.850731 | -0.986964 | -0.329698 | -0.801018 | 9.521227 |
| 11 | Kuala Selangor, Selangor, Malaysia | 1.195929e+09 | 24995 | 20.900064 | 3832.956385 | 57.924171 | 90 | 3 | 211 | 5.356586 | -1.096137 | -0.924796 | -0.702939 | -0.948961 | 5.008212 |
| 10 | Kuala Langat, Selangor, Malaysia | 8.541135e+08 | 19510 | 22.842397 | 4702.276361 | 37.459459 | 74 | 1 | 185 | 5.225747 | -1.068509 | -1.253443 | -0.921788 | -1.113138 | 0.000000 |
The morphological measures are only able to capture the abstract, morphological features of walkability. They can tell the network distance of a place and the theoretical time it takes to walk there navigating the city streets. They remain limited by their assumptions about street networks and make no statements on how humans actually interact with pedestrian environments.
In reality, what makes people walk in the city is influenced by a myriad of factors. It is not only important to ask what makes walking easy, but also what makes walking enjoyable? These are subjective factors of walking often related to the immediate experience of walking in the city, and not captured by morphological measures like permeability or accessibility.
Safety and comfort are two key street attributes that influence subjective walkability (Loukaitou-Sideris 2006). Safety and comfort are sometimes attributes of the physical environment: a steep walk up a hill, potholes and existence of continuous paved walkways can affect people’s preference of walking. The design of built environments like dedicated pedestrian paths, the width of these paths, separation of travel modes sharing the same space, the existence of shield from weather elements, et cetera, all contribute to the walkability of an urban space.
Streetscapes are important: compare living streets along a park to major trunk roads filled with bustling automobiles. One is perceived to be more walkable than the other.
The perceptual and social elements are also important: vibrant neighborhoods with many other pedestrians are perceived to be safer, lined shopfronts also served as signifier of safety as people hang out around these spaces (Jacobs 1992).
It is beyond the length of this article to cover assessment methods of streetscapes and subjective walkability. However, it remains key that urban design account the subjective elements of walkability with the same importance as the “objective” forms of walkability.
Urban spaces are political, no less its streets. The street is the primary urban public space that is shared among various actors: vehicles, people, road users, shop owners, et cetera. Hence, public spaces can be seen as a kind of commons — scarce resource that we must share among each other.
Like any commons, the use of this public space is contested. Optimising streets for cars require modifications or design decisions at the expense of other users, like pedestrians. For example, to accommodate car traffic, streets would have to expand multiple car lanes, raising speed limits and braking up connected footpaths. The same can be said about the effect of optimising streets for pedestrians on car mobility. The design of streets is also influenced by the built environment: the buildings that line streets constrain the available functional public space.
What sets streets apart from pastures as commons is that, the manner in which the commons is shared and distributed involves deliberate human interventions and decision-making.
Past mixture of automotive policy, urban planning and physical development policies were geared towards real estate development and financialization, as well as institutions that prop up car and private property ownership. In a way, we can see street networks as spaces that reflect its political economy. Their structures and morphology, besides from being shaped by physical limits, are also a function of institutional constraints such as property regimes and political priorities.
Making streets that work for people means reclaiming the social purpose of public spaces that streets represent. Urban mobility that rely on cars can exclude a sizeable share of the city-dwellers that have no means to drive, such as the elderly, children and the differently-abled. By making streets walkable, we also make urban mobility more equitable. Un-walkable streets are also less friendly to tourists and businesses by constraining the foot-access to diverse sociable places. Making streets walkable means making cities livable. It is time that we reimagine a form of mobility that is fairer and friendlier for Malaysia.