On this piece, I mix earlier work on city accessibility or walkability with open-source information on the placement of public defibrillator units. Moreover, I incorporate world inhabitants information and Uber’s H3 grid system to estimate the share of the inhabitants inside affordable attain to any system inside Budapest and Vienna.
![Towards Data Science](https://miro.medium.com/v2/resize:fill:48:48/1*CJe3891yB1A1mzMdqemkdg.jpeg)
12 hours in the past
The foundation of city accessibility, or walkability, lies in a graph-based computation measuring the Euclidean distance (remodeling it into strolling minutes, assuming fixed pace and no visitors jams and obstacles). The outcomes of such analyses can inform us how simple it’s to achieve particular kinds of facilities from each single location throughout the metropolis. To be extra exact, from each single node throughout the metropolis’s street community, however as a consequence of a lot of street crossings, this approximation is usually negligible.
On this present case research, I give attention to one specific kind of Level of Curiosity (POI): the placement of defibrillator units. Whereas the Austrian Authorities’s Open Information Portal shares official information on this, in Hungary, I might solely get hold of a less-then-half protection crowd-sourced information set — which, hopefully, will later develop each in absolute measurement and information protection.
Within the first part of my article, I’ll create the accessibility map for every metropolis, visualizing the time wanted to achieve the closest defibrillator items inside a spread of two.5km at a working pace of 15km/h. Then, I’ll cut up the cities into hexagon grids utilizing Uber’s H3 library to compute the common defibrillator-accessibility time for every grid cell. I additionally estimate the inhabitants stage at every hexagon cell following my earlier article. Lastly, I mix these and compute the fraction of the inhabitants reachable as a perform of reachability (working) time.
As a disclaimer, I wish to emphasize that I’m not a skilled medical skilled by any means — and I don’t intend to take a stand on the significance of defibrillator units in comparison with different technique of life assist. Nonetheless, constructing on frequent sense and concrete planning ideas, I assume that the simpler it’s to achieve such units, the higher.
As all the time, I like to start out by exploring the information varieties I exploit. First, I’ll accumulate the executive boundaries of the cities I research in — Budapest, Hungary, and Vienna, Austria.
Then, constructing on a earlier article of mine on course of rasterized inhabitants information, I add city-level inhabitants data from the WorldPop hub. Lastly, I incorporate official governmental information on defibrillator units in Vienna and my very own web-scraped model of the identical, although crowded sources and intrinsically incomplete, for Budapest.
1.1. Administrative boundaries
First, I question the admin boundaries of Budapest and Vienna from OpenStreetMap utilizing the OSMNx library:
import osmnx as ox # model: 1.0.1import matplotlib.pyplot as plt # model: 3.7.1
admin = {}cities = [‘Budapest’, ‘Vienna’]f, ax = plt.subplots(1,2, figsize = (15,5))
# visualize the admin boundariesfor idx, metropolis in enumerate(cities):admin[city] = ox.geocode_to_gdf(metropolis)admin[city].plot(ax=ax[idx],shade=’none’,edgecolor= ‘okay’, linewidth = 2) ax[idx].set_title(metropolis, fontsize = 16)
The results of this code block:
1.2. Inhabitants information
Second, following the steps on this article, I created the inhabitants grid in vector information format for each cities, constructing on the WorldPop on-line Demographic Database. With out repeating the steps, I simply learn within the output recordsdata of that course of containing inhabitants data for these cities.
Additionally, to make issues look good, I created a colormap from the colour of 2022, Very Peri, utilizing Matplotlib and a fast script from ChatGPT.
import matplotlib.pyplot as pltfrom matplotlib.colours import LinearSegmentedColormap
very_peri = ‘#8C6BF3’ second_color = ‘#6BAB55’
colours = [second_color, very_peri ]n_bins = 100cmap_name = “VeryPeri”colormap = LinearSegmentedColormap.from_list(cmap_name, colours, N=n_bins)
import geopandas as gpd # model: 0.9.0
demographics = {}f, ax = plt.subplots(1,2, figsize = (15,5))
for idx, metropolis in enumerate(cities):demographics[city] = gpd.read_file(metropolis.decrease() + ‘_population_grid.geojson’)[[‘population’, ‘geometry’]]admin[city].plot(ax=ax[idx], shade = ‘none’, edgecolor = ‘okay’, linewidth = 3)demographics[city].plot(column = ‘inhabitants’, cmap = colormap, ax=ax[idx], alpha = 0.9, markersize = 0.25)ax[idx].set_title(metropolis)ax[idx].set_title(‘Inhabitants densityn in ‘ + metropolis, fontsize = 16)ax[idx].axis(‘off’)
The results of this code block:
1.3. Defibrillator areas
Third, I collected locational information on the accessible defibrillators in each cities.
For Vienna, I downloaded this information set from the official open information portal of the Austrian authorities containing the purpose location of 1044 items:
Whereas such an official open information portal doesn’t exist in Budapest/Hungary, the Hungarian Nationwide Coronary heart Basis runs a crowd-sourced web site the place operators can replace the placement of their defibrillator items. Their country-wide database consists of 677 items; nevertheless, their disclaimer says they find out about not less than one thousand items working within the nation — and are ready for his or her homeowners to add them. With a easy internet crawler, I downloaded the placement of every of the 677 registered items and filtered the information set all the way down to these in Budapest, leading to a set of 148 items.
# parse the information for every citygdf_units= {}
gdf_units[‘Vienna’] = gpd.read_file(‘DEFIBRILLATOROGD’)gdf_units[‘Budapest’] = gpd.read_file(‘budapest_defibrillator.geojson’)
for metropolis in cities:gdf_units[city] = gpd.overlay(gdf_units[city], admin[city])
# visualize the unitsf, ax = plt.subplots(1,2, figsize = (15,5))
for idx, metropolis in enumerate(cities):admin[city].plot(ax=ax[idx],shade=’none’,edgecolor= ‘okay’, linewidth = 3)gdf_units[city].plot( ax=ax[idx], alpha = 0.9, shade = very_peri, markersize = 6.0)ax[idx].set_title(‘Places of defibrillatorndevices in ‘ + metropolis, fontsize = 16)ax[idx].axis(‘off’)
The results of this code block:
Subsequent, I wrapped up this nice article written by Nick Jones in 2018 on compute pedestrian accessibility:
import osimport pandana # model: 0.6import pandas as pd # model: 1.4.2import numpy as np # model: 1.22.4from shapely.geometry import Level # model: 1.7.1from pandana.loaders import osm
def get_city_accessibility(admin, POIs):
# walkability parameterswalkingspeed_kmh = 15walkingspeed_mm = walkingspeed_kmh * 1000 / 60distance = 2500
# bounding field as a listing of llcrnrlat, llcrnrlng, urcrnrlat, urcrnrlngminx, miny, maxx, maxy = admin.bounds.T[0].to_list()bbox = [miny, minx, maxy, maxx]
# setting the enter params, going for the closest POInum_pois = 1num_categories = 1bbox_string = ‘_’.be part of([str(x) for x in bbox])net_filename = ‘information/network_{}.h5’.format(bbox_string)if not os.path.exists(‘information’): os.makedirs(‘information’)
# precomputing nework distances
if os.path.isfile(net_filename):# if a road community file already exists, simply load the dataset from thatnetwork = pandana.community.Community.from_hdf5(net_filename)technique = ‘loaded from HDF5’else:# in any other case, question the OSM API for the road community throughout the specified bounding boxnetwork = osm.pdna_network_from_bbox(bbox[0], bbox[1], bbox[2], bbox[3])technique = ‘downloaded from OSM’
# determine nodes which can be linked to fewer than some threshold of different nodes inside a given distancelcn = community.low_connectivity_nodes(impedance=1000, rely=10, imp_name=’distance’)community.save_hdf5(net_filename, rm_nodes=lcn) #take away low-connectivity nodes and save to h5
# precomputes the vary queries (the reachable nodes inside this most distance)# so, so long as you employ a smaller distance, cached outcomes will probably be usednetwork.precompute(distance + 1)
# compute accessibilities on POIspois = POIs.copy()pois[‘lon’] = pois.geometry.apply(lambda g: g.x)pois[‘lat’] = pois.geometry.apply(lambda g: g.y)pois = pois.drop(columns = [‘geometry’])community.init_pois(num_categories=num_categories, max_dist=distance, max_pois=num_pois)
community.set_pois(class=’all’, x_col=pois[‘lon’], y_col=pois[‘lat’])
# searches for the n nearest facilities (of all sorts) to every node within the networkall_access = community.nearest_pois(distance=distance, class=’all’, num_pois=num_pois)
# remodel the outcomes right into a geodataframenodes = community.nodes_dfnodes_acc = nodes.merge(all_access[[1]], left_index = True, right_index = True).rename(columns = {1 : ‘distance’})nodes_acc[‘time’] = nodes_acc.distance / walkingspeed_mmxs = checklist(nodes_acc.x)ys = checklist(nodes_acc.y)nodes_acc[‘geometry’] = [Point(xs[i], ys[i]) for i in vary(len(xs))]nodes_acc = gpd.GeoDataFrame(nodes_acc)nodes_acc = gpd.overlay(nodes_acc, admin)
nodes_acc[[‘time’, ‘geometry’]].to_file(metropolis + ‘_accessibility.geojson’, driver = ‘GeoJSON’)
return nodes_acc[[‘time’, ‘geometry’]]
accessibilities = {}for metropolis in cities:accessibilities[city] = get_city_accessibility(admin[city], gdf_units[city])
for metropolis in cities:print(‘Variety of street community nodes in ‘ + metropolis + ‘: ‘ + str(len(accessibilities[city])))
This code block outputs the variety of street community nodes in Budapest (116,056) and in Vienna (148,212).
Now visualize the accessibility maps:
for metropolis in cities:f, ax = plt.subplots(1,1,figsize=(15,8))admin[city].plot(ax=ax, shade = ‘okay’, edgecolor = ‘okay’, linewidth = 3)accessibilities[city].plot(column = ‘time’, cmap = ‘RdYlGn_r’, legend = True, ax = ax, markersize = 2, alpha = 0.5)ax.set_title(‘Defibrillator accessibility in minutesn’ + metropolis, pad = 40, fontsize = 24) ax.axis(‘off’)
This code block outputs the next figures:
At this level, I’ve each the inhabitants and the accessibility information; I simply should carry them collectively. The one trick is that their spatial items differ:
Accessibility is measured and connected to every node throughout the street community of every cityPopulation information is derived from a raster grid, now described by the POI of every raster grid’s centroid
Whereas rehabilitating the unique raster grid could also be an choice, within the hope of a extra pronounced universality (and including a little bit of my private style), I now map these two kinds of level information units into the H3 grid system of Uber for many who haven’t used it earlier than, for now, its sufficient to know that it’s a sublime, environment friendly spacial indexing system utilizing hexagon tiles. And for extra studying, hit this hyperlink!
3.1. Creating H3 cells
First, put collectively a perform that splits a metropolis into hexagons at any given decision:
import geopandas as gpdimport h3 # model: 3.7.3from shapely.geometry import Polygon # model: 1.7.1import numpy as np
def split_admin_boundary_to_hexagons(admin_gdf, decision):coords = checklist(admin_gdf.geometry.to_list()[0].exterior.coords)admin_geojson = {“kind”: “Polygon”, “coordinates”: [coords]}hexagons = h3.polyfill(admin_geojson, decision, geo_json_conformant=True)hexagon_geometries = {hex_id : Polygon(h3.h3_to_geo_boundary(hex_id, geo_json=True)) for hex_id in hexagons}return gpd.GeoDataFrame(hexagon_geometries.objects(), columns = [‘hex_id’, ‘geometry’])
decision = 8 hexagons_gdf = split_admin_boundary_to_hexagons(admin[city], decision)hexagons_gdf.plot()
The results of this code block:
Now, see a couple of completely different resolutions:
for decision in [7,8,9]:
admin_h3 = {}for metropolis in cities:admin_h3[city] = split_admin_boundary_to_hexagons(admin[city], decision)
f, ax = plt.subplots(1,2, figsize = (15,5))
for idx, metropolis in enumerate(cities):admin[city].plot(ax=ax[idx], shade = ‘none’, edgecolor = ‘okay’, linewidth = 3)admin_h3[city].plot( ax=ax[idx], alpha = 0.8, edgecolor = ‘okay’, shade = ‘none’)ax[idx].set_title(metropolis + ‘ (decision = ‘+str(decision)+’)’, fontsize = 14)ax[idx].axis(‘off’)
The results of this code block:
Let’s maintain decision 9!
3.2. Map values into h3 cells
Now, I’ve each our cities in a hexagon grid format. Subsequent, I shall map the inhabitants and accessibility information into the hexagon cells primarily based on which grid cells every level geometry falls into. For this, the sjoin perform of GeoPandasa, doing a pleasant spatial joint, is an efficient alternative.
Moreover, as we’ve got greater than 100k street community nodes in every metropolis and hundreds of inhabitants grid centroids, most probably, there will probably be a number of POIs mapped into every hexagon grid cell. Due to this fact, aggregation will probably be wanted. Because the inhabitants is an additive amount, I’ll mixture inhabitants ranges throughout the identical hexagon by summing them up. Nonetheless, accessibility shouldn’t be intensive, so I’d as an alternative compute the common defibrillator accessibility time for every tile.
demographics_h3 = {}accessibility_h3 = {}
for metropolis in cities:
# do the spatial be part of, mixture on the inhabitants stage of every # hexagon, after which map these inhabitants values to the grid idsdemographics_dict = gpd.sjoin(admin_h3[city], demographics[city]).groupby(by = ‘hex_id’).sum(‘inhabitants’).to_dict()[‘population’]demographics_h3[city] = admin_h3[city].copy()demographics_h3[city][‘population’] = demographics_h3[city].hex_id.map(demographics_dict)
# do the spatial be part of, mixture on the inhabitants stage by averaging # accessiblity instances inside every hexagon, after which map these time rating # to the grid idsaccessibility_dict = gpd.sjoin(admin_h3[city], accessibilities[city]).groupby(by = ‘hex_id’).imply(‘time’).to_dict()[‘time’]accessibility_h3[city] = admin_h3[city].copy()accessibility_h3[city][‘time’] = accessibility_h3[city].hex_id.map(accessibility_dict)
# now present the resultsf, ax = plt.subplots(2,1,figsize = (15,15))
demographics_h3[city].plot(column = ‘inhabitants’, legend = True, cmap = colormap, ax=ax[0], alpha = 0.9, markersize = 0.25)accessibility_h3[city].plot(column = ‘time’, cmap = ‘RdYlGn_r’, legend = True, ax = ax[1])
ax[0].set_title(‘Inhabitants leveln in ‘ + metropolis, fontsize = 16)ax[1].set_title(‘Defibrillator reachability timen in ‘ + metropolis, fontsize = 16)
for ax_i in ax: ax_i.axis(‘off’)
The outcomes of this code block are the next figures:
On this ultimate step, I’ll estimate the fraction of the reachable inhabitants from the closest defibrillator unit inside a sure period of time. Right here, I nonetheless construct on the comparatively quick 15km/h working tempo and the two.5km distance restrict.
From the technical perspective, I merge the H3-level inhabitants and accessibility time information frames after which do a easy thresholding on the time dimension and a sum on the inhabitants dimension.
f, ax = plt.subplots(1,2, figsize = (15,5))
for idx, metropolis in enumerate(cities):
total_pop = demographics_h3[city].inhabitants.sum()merged = demographics_h3[city].merge(accessibility_h3[city].drop(columns =[‘geometry’]), left_on = ‘hex_id’, right_on = ‘hex_id’)
time_thresholds = vary(10)population_reached = [100*merged[merged.time<limit].inhabitants.sum()/total_pop for restrict in time_thresholds]
ax[idx].plot(time_thresholds, population_reached, linewidth = 3, shade = very_peri)ax[idx].set_xlabel(‘Reachability time (min)’, fontsize = 14, labelpad = 12)ax[idx].set_ylabel(‘Fraction of inhabitants reached (%)’, fontsize = 14, labelpad = 12)ax[idx].set_xlim([0,10])ax[idx].set_ylim([0,100])ax[idx].set_title(‘Fraction of inhabitants vs defibrillatornaccessibility in ‘ + metropolis, pad = 20, fontsize = 16)
The results of this code block are the next figures:
When decoding these outcomes, I want to emphasize that, on the one hand, defibrillator accessibility is probably not instantly linked to heart-attack survival fee; judging that impact is past each my experience and this mission’s scope. Additionally, the information used for Budapest is knowingly incomplete and crowded sources, versus the official Austrian information supply.
After the disclaimers, what can we see? On the one hand, we see that in Budapest, about 75–80% of the inhabitants can get to a tool inside 10 minutes, whereas in Vienna, we attain practically full protection in round 6–7 minutes already. Moreover, we have to learn these time values fastidiously: if we occur to be at an unlucky incident, we have to get to the system, decide it up, return (making the journey time double of the reachability time), set up it, and many others. in a state of affairs the place each minute could also be a matter of life and demise.
So the takeaways, from a improvement perspective, the takeaways are to make sure we’ve got full information after which use the accessibility and inhabitants maps, mix them, analyze them, and construct on them when deploying new units and new areas to maximise the efficient inhabitants reached.