Análisis de datos exploratorios geoespaciales con GeoPandas y DuckDB

En este artículo, le mostraré cómo utilizar dos bibliotecas populares de Python para realizar algunos análisis geoespaciales de datos de accidentes de tráfico en el Reino Unido.

Fui uno de los primeros en adoptar DuckDB, la rápida base de datos OLAP, después de que estuvo disponible, pero recientemente me di cuenta de que, a través de una extensión, ofrecía una gran cantidad de funciones geoespaciales potencialmente útiles.

Geopandas era nuevo para mí. Es una biblioteca de Python que hace que trabajar con datos geográficos sea como trabajar con pandas normales, pero con geometría (puntos, líneas, polígonos) integrada. Puede leer/escribir formatos SIG estándar (GeoJSON, Shapefile, GeoPackage), manipular atributos y geometrías juntas y visualizar capas rápidamente con Matplotlib.

Deseoso de probar las capacidades de ambos, me puse a investigar un miniproyecto útil que fuera a la vez interesante y una experiencia de aprendizaje útil.

En pocas palabras, decidí intentar utilizar ambas bibliotecas para determinar cuál es la ciudad más segura del Reino Unido para conducir o caminar. Es posible que puedas hacer todo lo que voy a mostrar usando Geopandas por tu cuenta, pero desafortunadamente, no estoy tan familiarizado con él como con DuckDB, por eso usé ambos.

Algunas reglas básicas.

Los datos de accidentes y víctimas que utilizaré provienen de una fuente oficial del gobierno del Reino Unido que cubre todo el país. Sin embargo, me centraré en los datos de sólo seis de las ciudades más grandes del Reino Unido: Londres, Edimburgo, Cardiff, Glasgow, Birmingham y Manchester.

Mi método para determinar la ciudad "más segura" será calcular el número total de víctimas relacionadas con el tráfico en cada ciudad durante un período de cinco años y dividir este número por el área de cada ciudad en km². Este será mi “índice de seguridad”, y cuanto menor sea ese número, más segura será la ciudad.

Obteniendo los datos de los límites de nuestra ciudad

Esta fue posiblemente la parte más desafiante de todo el proceso.

En lugar de tratar una “ciudad” como un único polígono administrativo, lo que conduce a diversas anomalías en términos de áreas de la ciudad, modelé cada una según su huella de área construida (BUA). Hice esto usando la capa de datos del mapa de Áreas Urbanizadas de la Oficina de Estadísticas Nacionales (ONS) y luego agregué todas las partes de la BUA que se encuentran dentro de un límite administrativo sensato para esa ubicación. La máscara proviene de los límites oficiales de la ONS y se elige para reflejar cada área urbana más amplia:

Londres → Región de Londres (regiones, diciembre de 2023). Manchester → la unión de los 10 distritos de autoridades locales (LAD) del Gran Manchester para mayo de 2024. Birmingham → la unión de los 7 LAD de las autoridades combinadas de West Midlands. Glasgow → la unión de los 8 ayuntamientos de la región de la ciudad de Glasgow. Edimburgo y Cardiff → su single LAD.

Cómo se construyen los polígonos en código

Descargué datos de límites directamente desde ArcGIS FeatureServer de la Oficina de Estadísticas Nacionales (ONS) del Reino Unido en formato GeoJSON utilizando la biblioteca de solicitudes. Para cada ciudad, primero creamos una máscara a partir de las capas de administración oficiales de ONS: la Región de Londres (diciembre de 2023) para Londres; la unión de distritos de autoridades locales (LAD, mayo de 2024) del Gran Manchester (10 LAD), el núcleo de West Midlands (7 LAD) y la región de la ciudad de Glasgow (8 ayuntamientos); y el sencillo LAD para Edimburgo y Cardiff.

A continuación, consulto la capa Áreas construidas de ONS (BUA 2022) para ver los polígonos que intersectan el cuadro delimitador de la máscara, manteniendo solo aquellos que intersectan la máscara, y disuelvo (fusiono) los resultados para crear un único polígono multiparte por ciudad (“agregado BUA 2022”). Los datos se almacenan y representan en EPSG:4326 (WGS84), es decir, la latitud y la longitud se expresan en grados. Al informar áreas, las reproyectamos en EPSG:27700 (OSGB) y calculamos el área en kilómetros cuadrados para evitar distorsiones.

Los datos de los límites de cada ciudad se descargan en un archivo GeoJSON y se cargan en Python utilizando las bibliotecas de geopandas y solicitudes.

Para mostrar que los datos de límites que tenemos son correctos, las capas de ciudades individuales se combinan en un único Geodataframe, se reproyectan en un sistema de referencia de coordenadas consistente (EPSG:4326) y se trazan sobre un contorno limpio del Reino Unido derivado del conjunto de datos de Natural Earth (a través de un espejo de GitHub). Para centrarnos únicamente en el continente, recortamos el contorno del Reino Unido hasta el cuadro delimitador de las ciudades, excluyendo los territorios distantes de ultramar. También se calcula el área de cada ciudad.

Licencia de datos límite

Todos los conjuntos de datos de límites que he utilizado son datos abiertos con términos de reutilización permisivos.

Londres, Edimburgo, Cardiff, Glasgow, Birmingham y Manchester

Fuente: Oficina de Estadísticas Nacionales (ONS): condados y autoridades unitarias (mayo de 2023), BGC y regiones del Reino Unido (diciembre de 2023) EN BGC. Licencia: Licencia de Gobierno Abierto v3.0 (OGL). Términos: Usted es libre de usar, modificar y compartir los datos (incluso comercialmente) siempre que proporcione la atribución.

Esquema del Reino Unido

Fuente: Natural Earth — Admin 0 — Países. Licencia: Dominio Público (Sin restricciones). Citación: Natural Earth. Administrador 0 – Países. Dominio público. Disponible en: https://www.naturalearthdata.com

Código de límites de la ciudad

Aquí está el código Python que puede usar para descargar los archivos de datos de cada ciudad y verificar los límites en un mapa.

Antes de ejecutar el código principal, asegúrese de haber instalado las siguientes bibliotecas. Puedes usar pip u otro método de tu elección para esto.

geopandas matplotlib solicita pandas duckdb jupyter solicitudes de importación, json importar geopandas como gpd importar pandas como pd importar matplotlib.pyplot como plt # ———- Puntos finales ONS ———- LAD24_FS = "https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/Local_Authority_Districts_May_2024_Boundaries_UK_BGC/FeatureServer/0/query" REGION23_FS = "https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/Regions_December_2023_Boundaries_EN_BGC/FeatureServer/0/query" BUA_FS = "https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/BUA_2022_GB/FeatureServer/0/query" # ———- Ayudantes ———- def arcgis_geojson(url, params): r = request.get(url, params={**params, "f": "geojson"}, timeout=90) r.raise_for_status() return r.json() def sql_quote(s: str) -> str: return "'" + s.replace("'", "''") + "'" def fetch_lads_by_names(nombres): donde = "LAD24NM IN ({})".format(",".join(sql_quote(n) para n en nombres)) data = arcgis_geojson(LAD24_FS, { "dónde": dónde, "outFields": "LAD24NM", "returnGeometry": "true", "outSR": "4326" }) gdf = gpd.GeoDataFrame.from_features(data.get("features",[]), crs="EPSG:4326") if gdf.empty: rise RuntimeError(f"No se encontraron LAD para: {nombres}") return gdf def fetch_lad_by_name(nombre): return fetch_lads_by_names([nombre]) def fetch_region_by_name(nombre): data = arcgis_geojson(REGION23_FS, { "dónde": f"RGN23NM={sql_quote(nombre)}", "outFields": "RGN23NM", "returnGeometry": "true", "outSR": "4326" }) gdf = gpd.GeoDataFrame.from_features(data.get("features",[]), crs="EPSG:4326") if gdf.empty: rise RuntimeError(f"No hay función de región para: {name}") return gdf def fetch_buas_intersecting_bbox(minx, miny, maxx, maxy): data = arcgis_geojson(BUA_FS, { "where": "1=1", "geometryType": "esriGeometryEnvelope", "geometry": json.dumps({ "xmin": float(minx), "ymin": float(miny), "xmax": float(maxx), "ymax": float(maxy), "spatialReference": {"wkid": 4326} }), "inSR": "4326", "spatialRel": "esriSpatialRelIntersects", "outFields": "BUA22NM,BUA22CD,Shape__Area", "returnGeometry": "true", "outSR": "4326" }) return gpd.GeoDataFrame.from_features(data.get("features",[]), crs="EPSG:4326") def agregado_bua_by_mask(mask_gdf: gpd.GeoDataFrame, etiqueta: str) -> gpd.GeoDataFrame: """ Recupera los polígonos BUA 2022 que intersectan una máscara (LAD/unión de región) y los disuelve en un polígono. Utiliza Shapely 2.x union_all() para construir la geometría de la máscara. """ # Unir los polígonos de máscara mask_union = mask_gdf.geometry.union_all() # Obtener BUA candidatos a través de la máscara bbox, luego filtrar por intersección real con la unión minx, miny, maxx, maxy = gpd.GeoSeries([mask_union], crs="EPSG:4326").total_bounds buas = fetch_buas_intersecting_bbox(minx, miny, maxx, maxy) if buas.empty: rise RuntimeError(f"No hay BUA que intersecten bbox para {label}") buas = buas[buas.intersects(mask_union)] if buas.empty: rise RuntimeError(f"No hay BUA que realmente intersecten la máscara para {label}") disuelta = buas[["geometry"]].dissolve().reset_index(drop=True) disuelto["city"] = etiqueta regresar disuelto[["city", "geometry"]] # ———- Definiciones de grupo ———- GM_10 = ["Manchester","Salford","Trafford","Stockport","Tameside", "Oldham","Rochdale","Bury","Bolton","Wigan"] WMCA_7 = ["Birmingham","Coventry","Dudley","Sandwell","Solihull","Walsall","Wolverhampton"] GLASGOW_CR_8 = ["Ciudad de Glasgow","East Dunbartonshire","West Dunbartonshire", "East Renfrewshire","Renfrewshire","Inverclyde", "North Lanarkshire","South Lanarkshire"] EDIMBURGO = "Ciudad de Edimburgo" CARDIFF = "Cardiff" # ———- Crear máscaras ———- london_region = fetch_region_by_name("Londres") # Máscara de región para Londres gm_lads = fetch_lads_by_names(GM_10) # Greater Manchester (10) wmca_lads = fetch_lads_by_names(WMCA_7) # West Midlands (7) gcr_lads = fetch_lads_by_names(GLASGOW_CR_8) # Región de la ciudad de Glasgow (8) edi_lad = fetch_lad_by_name(EDINBURGH) # LAD único cdf_lad = fetch_lad_by_name(CARDIFF) # LAD único # ———- BUA agregados por cada máscara ———- capas =[]london_bua = agregado_bua_by_mask(london_region, "Londres (BUA 2022 agregado)") london_bua.to_file("london_bua_aggregate.geojson", driver="GeoJSON") Layers.append(london_bua) man_bua = agregado_bua_by_mask(gm_lads, "Manchester (BUA 2022 agregado)") man_bua.to_file("manchester_bua_aggregate.geojson", driver="GeoJSON") Layers.append(man_bua) bham_bua = agregado_bua_by_mask(wmca_lads, "Birmingham (BUA 2022 agregado)") bham_bua.to_file("birmingham_bua_aggregate.geojson", driver="GeoJSON") capas.append(bham_bua) glas_bua = agregado_bua_by_mask(gcr_lads, "Glasgow (BUA 2022 agregado)") glas_bua.to_file("glasgow_bua_aggregate.geojson", driver="GeoJSON") capas.append(glas_bua) edi_bua = agregado_bua_by_mask(edi_lad, "Edimburgo (agregado BUA 2022)") edi_bua.to_file("edinburgh_bua_aggregate.geojson", driver="GeoJSON") Layers.append(edi_bua) cdf_bua = agregado_bua_by_mask(cdf_lad, "Cardiff (agregado BUA 2022)") cdf_bua.to_file("cardiff_bua_aggregate.geojson", driver="GeoJSON") Layers.append(cdf_bua) # ———- Combinar e informar áreas ———- ciudades = gpd.GeoDataFrame(pd.concat(layers, ignore_index=True), crs="EPSG:4326") # ———- Trazar el contorno del Reino Unido + los seis agregados ———- # Esquema del Reino Unido (Natural Earth) 1:10m, países simples) ne_url = "https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_10m_admin_0_countries.geojson" world = gpd.read_file(ne_url) uk = world[world["ADMIN"] == "United Kingdom"].to_crs(4326) # Recortar marco para nuestras ciudades minx, miny, maxx, maxy = ciudades.total_bounds uk_crop = uk.cx[minx-5 : maxx+5, miny-5 : maxy+5] fig, ax = plt.subplots(figsize=(9, 10), dpi=150) uk_crop.boundary.plot(ax=ax, color="black", linewidth=1.2) ciudades.plot(ax=ax, column="city", alpha=0.45, edgecolor="black", linewidth=0.8, legend=True) # Etiquete cada polígono usando un punto interior label_pts = ciudades.representative_point() for (x, y), nombre en zip(label_pts.geometry.apply(lambda p: (px, py)), ciudades["ciudad"]): ax.text(x, y, name, fontsize=8, ha="center", va="center") ax.set_title("BUA 2022 Agregados: Londres, Manchester, Birmingham, Glasgow, Edimburgo, Cardiff", fontsize=12) ax.set_xlim(minx-1, maxx+1) ax.set_ylim(miny-1, maxy+1) ax.set_aspect("equal", ajustable="box") ax.set_xlabel("Longitud"); ax.set_ylabel("Latitud") plt.tight_layout() plt.show()

Después de ejecutar este código, debería tener 6 archivos GeoJSON almacenados en su directorio actual y también debería ver un resultado como este, que nos indica visualmente que los archivos de límites de nuestra ciudad contienen datos válidos.

Obteniendo nuestros datos de accidentes

Nuestra última pieza del rompecabezas de datos son los datos sobre accidentes. El gobierno del Reino Unido publica informes sobre datos de accidentes de vehículos que cubren períodos de cinco años. Los datos más recientes cubren el período de 2019 a 2024. Este conjunto de datos cubre todo el Reino Unido, por lo que necesitaremos procesarlo para extraer solo los datos de las seis ciudades que nos interesan. Ahí es donde entrará DuckDB, pero hablaremos de eso más adelante.

Para ver o descargar los datos de accidentes en formato CSV (Fuente: Departamento de Transporte — Datos de seguridad vial), haga clic en el siguiente enlace

https://data.dft.gov.uk/road-accidents-safety-data/dft-road-casualty-statistics-collision-last-5-years.csv

Al igual que los datos de límites de la ciudad, estos también se publican bajo la Licencia de Gobierno Abierto v3.0 (OGL 3.0) y, como tal, tienen las mismas condiciones de licencia.

El conjunto de datos de accidentes contiene una gran cantidad de campos, pero para nuestros propósitos, solo nos interesan 3 de ellos:

latitud longitud número_de_accidentes

Obtener nuestro recuento de víctimas para cada ciudad ahora es solo un proceso de 3 pasos.

1/ Cargando el conjunto de datos de accidentes en DuckDB

Si nunca antes ha encontrado DuckDB, la conclusión es que es una base de datos súper rápida en memoria (también puede ser persistente) escrita en C++ diseñada para cargas de trabajo de SQL analítico.

Una de las principales razones por las que me gusta es su velocidad. Es una de las bibliotecas de análisis de datos de terceros más rápidas que he usado. También es muy extensible mediante el uso de extensiones como la geoespacial, que usaremos ahora.

Ahora podemos cargar los datos del accidente de esta manera.

solicitudes de importación import duckdb # CSV remoto (colisiones de los últimos 5 años) url = "https://data.dft.gov.uk/road-accidents-safety-data/dft-road-casualty-statistics-collision-last-5-years.csv" local_file = "collisions_5yr.csv" # Descargue el archivo r = request.get(url, stream=True) r.raise_for_status() con open(local_file, "wb") as f: for chunk in r.iter_content(chunk_size=8192): f.write(chunk) print(f"Descargado {local_file}") # Conectar una vez con = duckdb.connect(database=':memory:') # Instalar + cargar espacial en ESTA conexión con.execute("INSTALL espacial;") con.execute("CARGAR espacial;") # Crear tabla de accidentes con geometría con.execute(""" CREAR TABLA accidentes AS SELECT TRY_CAST(Latitud COMO DOBLE) COMO latitud, TRY_CAST(Longitud COMO DOBLE) COMO longitud, TRY_CAST(Número_de_Víctimas COMO INTEGER) COMO número_de_víctimas, ST_Point(TRY_CAST(Longitud COMO DOUBLE), TRY_CAST(Latitud COMO DOUBLE)) AS geom FROM read_csv_auto('collisions_5yr.csv', header=True, nullstr='NULL') DONDE TRY_CAST(Latitude AS DOUBLE) NO ES NULL Y TRY_CAST(Longitude AS DOUBLE) NO ES NULL Y TRY_CAST(Number_of_Casualties AS INTEGER) NO ES NULL """) # Vista previa rápida print(con.execute("DESCRIBIR accidentes").df()) print(con.execute("SELECT * FROM accidentes LIMIT 5").df())

Debería ver el siguiente resultado.

Colliss_5yr.csv descargado nombre_columna tipo_columna clave nula valor predeterminado extra 0 latitud DOBLE SÍ Ninguno Ninguno Ninguno 1 longitud DOBLE SÍ Ninguno Ninguno Ninguno 2 número_de_víctimas INTEGER SÍ Ninguno Ninguno Ninguno 3 geom GEOMETRÍA SÍ Ninguno Ninguno Ninguno latitud longitud número_de_víctimas 0 51.508057 -0.153842 3 1 51.436208 -0.127949 1 2 51.526795 -0.124193 1 3 51.546387 -0.191044 1 4 51.541121 -0.200064 2 geom 0 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, … 1 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, … 2 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, … 3 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, … 4 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, …

2/ Cargando los datos de los límites de la ciudad usando funciones espaciales de DuckDB

La función que usaremos para hacer esto se llama ST_READ, que puede leer e importar una variedad de formatos de archivos geoespaciales usando la biblioteca GDAL.

city_files = { "Londres": "london_bua_aggregate.geojson", "Edimburgo": "edinburgh_bua_aggregate.geojson", "Cardiff": "cardiff_bua_aggregate.geojson", "Glasgow": "glasgow_bua_aggregate.geojson", "Manchester": "manchester_bua_aggregate.geojson", "Birmingham": "birmingham_bua_aggregate.geojson" } para ciudad, archivo en city_files.items(): con.execute(f""" CREAR TABLA {city.lower()} AS SELECT '{city}' COMO ciudad, geom FROM ST_Read('{file}') """) con.execute(""" CREAR TABLA ciudades AS SELECT * FROM london UNION ALL SELECT * FROM edinburgh UNION ALL SELECT * DESDE Cardiff UNION TODO SELECCIONAR * DESDE glasgow UNION TODO SELECCIONAR * DESDE manchester UNION TODO SELECCIONAR * DESDE birmingham """)

3/ El siguiente paso es sumar los accidentes a los polígonos de la ciudad y contar las víctimas.

La función geoespacial clave que utilizamos esta vez se llama ST_WITHIN. Devuelve VERDADERO si el primer punto geométrico está dentro del límite del segundo.

importar duckdb casualties_per_city = con.execute(""" SELECT c.city, SUM(a.number_of_casualties) AS total_casualties, COUNTAS accident_count FROM accidentes a UNIR ciudades c ON ST_Within(a.geom, c.geom) GROUP BY c.city ORDER BY total_casualties DESC """).df() print("Víctimas por ciudad:") print(casualties_per_city)

Tenga en cuenta que ejecuté la consulta anterior en una computadora de escritorio potente y aún así tardé unos minutos en obtener resultados, así que tenga paciencia. Sin embargo, eventualmente deberías ver un resultado similar a este.

Víctimas por ciudad: ciudad total_casualties accident_count 0 Londres 134328,0 115697 1 Birmingham 14946,0 11119 2 Manchester 4518,0 3502 3 Glasgow 3978,0 3136 4 Edimburgo 3116,0 2600 5 Cardiff 1903,0 1523

Análisis

No sorprende que Londres tenga, en general, el mayor número de víctimas. Pero por su tamaño, ¿es más o menos peligroso conducir o ser peatón que en el resto de ciudades?

Claramente hay un problema con las cifras de bajas de Manchester y Glasgow. Ambos deberían ser más grandes, según el tamaño de sus ciudades. La sugerencia es que esto podría deberse a que muchas de las concurridas carreteras de circunvalación y carreteras periféricas del metro (M8/M74/M77; M60/M62/M56/M61) asociadas con cada ciudad se encuentran justo fuera de sus estrechos polígonos BUA, lo que lleva a una representación insuficiente significativa de los datos de accidentes y víctimas. ¡Dejaré esa investigación como ejercicio para el lector!

Para nuestra determinación final de la seguridad del conductor, necesitamos conocer el tamaño del área de cada ciudad para poder calcular la tasa de víctimas por km².

Por suerte, DuckDB tiene una función para eso. ST_AREA calcula el área de una geometría.

# — Calcular áreas en km² (CRS84 -> OSGB 27700) — print("nCalculando áreas en km^2…") areas = con.execute(""" SELECT city, ST_Area( ST_MakeValid( ST_Transform( — ST_Simplify(geom, 0.001), — Experimente con el valor épsilon (por ejemplo, 0.001 grados) geom, 'OGC:CRS84','EPSG:27700' ) ) ) / 1e6 AS area_km2 FROM ciudades ORDENAR POR area_km2 DESC; """).df() print("Áreas de la ciudad:") print(areas.round(2))

Obtuve este resultado, que parece ser correcto.

área de la ciudad_km2 0 Londres 1321,45 1 Birmingham 677,06 2 Manchester 640,54 3 Glasgow 481,49 4 Edimburgo 123,00 5 Cardiff 96,08

Ahora tenemos todos los datos que necesitamos para declarar qué ciudad tiene los conductores más seguros del Reino Unido. Recuerde, cuanto menor sea el número "safety_index", más seguro.

ciudad área_km2 víctimas índice_seguridad (víctimas/área) 0 Londres 1321,45 134328 101,65 1 Birmingham 677,06 14946 22,07 2 Manchester 640,54 4518 7,05 3 Glasgow 481,49 3978 8,26 4 Edimburgo 123,00 3116 25,33 5 Cardiff 96,08 1903 19,08

No me siento cómodo incluyendo los resultados tanto de Manchester como de Glasgow debido a las dudas sobre sus recuentos de bajas que comentábamos antes.

Teniendo esto en cuenta, y porque soy el jefe de este artículo, declaro a Cardiff ganadora del premio a la ciudad más segura desde la perspectiva del conductor y del peatón. ¿Qué opinas de estos resultados? ¿Vives en una de las ciudades que miré? Si es así, ¿los resultados respaldan su experiencia al conducir o ser peatón allí?

Resumen

Examinamos la viabilidad de realizar análisis de datos exploratorios en un conjunto de datos geoespaciales. Nuestro objetivo era determinar cuál de las seis principales ciudades del Reino Unido era más segura para conducir o ser peatón. Utilizando una combinación de GeoPandas y DuckDB, pudimos:

Utilice Geopandas para descargar datos de límites de ciudades desde un sitio web oficial del gobierno que representen con razonable precisión el tamaño de cada ciudad. Descargue y posprocese un extenso CSV de encuesta de accidentes de 5 años para obtener los tres campos de interés que contiene, a saber, latitud, longitud y número de víctimas de accidentes de tráfico. Combine los datos de accidentes con los datos de los límites de la ciudad en latitud/longitud utilizando funciones geoespaciales de DuckDB para obtener el número total de víctimas de cada ciudad durante 5 años. Se utilizaron funciones geoespaciales de DuckDB para calcular el tamaño en km² de cada ciudad. Calculó un índice de seguridad para cada ciudad, dividiendo el número de víctimas en cada ciudad por su tamaño. No tuvimos en cuenta dos de nuestros resultados debido a dudas sobre la exactitud de algunos de los datos. Calculamos que Cardiff tenía el índice de seguridad más bajo y, por lo tanto, se la consideraba la más segura de las ciudades que encuestamos.

Con los datos de entrada correctos, el análisis geoespacial utilizando las herramientas que describo podría ser una ayuda invaluable en muchas industrias. Piense en el análisis de tráfico y accidentes (como he mostrado), también me viene a la mente el análisis de inundaciones, deforestación, incendios forestales y sequías. Básicamente, cualquier sistema o proceso que esté conectado a un sistema de coordenadas espaciales está listo para que cualquiera pueda realizar un análisis de datos exploratorio.