LA Crime: ora in 3D (non sono necessari occhiali) |  di Aleksei Rozanov |  Giugno 2024

 | Intelligenza-Artificiale

Visualizzazione dei geodati del crimine in Python

Immagine di autore.

In a mio parere, una delle migliori caratteristiche che i dati geografici possiedono è la loro capacità di visualizzazione 3D. Tuttavia, a causa della quantità di risorse computazionali necessarie per tali calcoli, raramente vengono eseguiti in Python (spesso JavaScript e le relative librerie utilizzate come alternative). In uno dei miei articoli precedenti ho condiviso sei pacchetti Python che consentono la creazione di bellissime mappe statiche e interattive, ma solo nello spazio 2D.

Oggi voglio colmare questa lacuna e studiare con te un framework davvero elegante ed efficiente per visualizzazioni basate sul web ad alte prestazioni deck.jlche ha anche una libreria Python PyDeck.

Per esplorare adeguatamente le sue capacità in Python abbiamo bisogno di un ampio set di dati geospaziali. Un candidato perfetto per questo è Dati sulla criminalità di Los Angeles 2010-2020 set di dati da Kaggle. Fortunatamente, ha un licenza apertacosì da poterlo utilizzare liberamente per i nostri scopi.

Gli autori ne distribuiscono due csv file, che uniremo in uno solo filtrando tutte le colonne tranne longitudine e latitudine (coordinate dei luoghi in cui sono stati commessi i crimini).

🐍Il codice Python completo: GitHub.

import numpy as np
import pandas as pd
import xarray as xr
import geopandas as gpd
import random
import math

import matplotlib.pyplot as plt
from shapely import Point

import cartopy
import cartopy.crs as ccrs
import cartopy.feature as cfeature

import warnings

warnings.filterwarnings('ignore')

recent = pd.read_csv('./LA Crimes/Crime_Data_from_2020_to_Present.csv')(('LAT', 'LON'))
old = pd.read_csv('./LA Crimes/Crime_Data_from_2010_to_2019.csv')(('LAT', 'LON'))
df = pd.concat((old, recent))
df = df((df.LON!=0) & (df.LAT!=0)) #zeros are Nans according to meta info

Dopo averlo caricato in Panda, voglio eseguire una visualizzazione 2D statica utilizzando cartopiagiusto per avere un riferimento attendibile. Se tracciamo semplicemente i dati, otterremo una serie di punti dati, che per noi non sono utili.

Immagine di autore.

Eseguiamo invece l'interpolazione spaziale utilizzando il metodo NN (puoi leggere di più a riguardo nell'altro mio articolo).

In pratica significa trasferire le osservazioni sparse in una griglia geografica (PyDeck farà la stessa cosa, in questo caso si può parlare di aggregazione dei dati).

def coords(x,y, base=0.01):
x, y = round(base * math.ceil(abs(x)/base),2), round(base * math.ceil(y/base),2)
return (y,x)

def NN(data, LAT, LON):
array = np.zeros((LAT.shape(0), LON.shape(0)),dtype=int)
onGrid = data.apply(lambda row: coords(row.LAT, row.LON, 0.01), axis = 1).value_counts()
for coor in onGrid.index:
lon_idx, lat_idx = np.where(LON==coor(0)), np.where(LAT==coor(1))
array(lat_idx,lon_idx) = int(onGrid(coor))
return array

Una volta terminato l'algoritmo (dovrai attendere un po' di tempo, dato che abbiamo più di 2 milioni di righe da elaborare), possiamo racchiudere i suoi risultati in un Bene set di dati e mapparlo.

LAT, LON = np.arange(round(df.LAT.min()), round(df.LAT.max()), 0.01).astype(np.float32), np.arange(round(df.LON.min()), round(df.LON.max()), 0.01).astype(np.float32)
crimes = NN(df, LAT, LON)
ds = xr.Dataset(
{'Crimes': (('lat', 'lon'), crimes)},
coords={'lat': LAT, 'lon': LON})

fig, ax = plt.subplots(subplot_kw=dict(projection=ccrs.PlateCarree()), figsize=(16, 9))

ds.Crimes.plot(ax=ax, cmap='Reds')
ax.set_extent((-118.9, -118.1, 33.6, 34.5 ), crs=ccrs.PlateCarree())
ax.gridlines(draw_labels=True,linewidth=2, color='black', alpha=0.5, linestyle='--')
ax.add_feature(cfeature.BORDERS, edgecolor='black', linewidth=1)

ax.add_feature(cfeature.COASTLINE, edgecolor='black', linewidth=1)
ax.add_feature(cartopy.feature.RIVERS, edgecolor='blue', linewidth=0.5)
states_provinces = cfeature.NaturalEarthFeature(
category='cultural', name='admin_1_states_provinces',
scale='10m', facecolor='none')

plt.show()

Immagine di autore.

Dal mio punto di vista, sembra carino e informativo, ma se devi vendere questo progetto a qualcuno, molto probabilmente fallirai con una mappa del genere xD. Quindi installiamo PyDeck e vediamo cosa può fare per noi!

Il primo tipo di livello mappa che ho trovato bello è stato il livello Esagono. È necessario specificarlo durante la creazione della Layer variabile in PyDeck. Ci sono molti altri parametri cruciali per noi:

  • raggio (il raggio dell'esagono in metri, cioè la risoluzione spaziale in m);
  • elevazione_scale (fattore di scala per i contenitori, maggiore è il valore, più alti sono gli esagoni);
  • elevation_range (altezza minima e massima);
  • selezionabile (mostra interattivamente i valori);
  • estruso (elevazione della cella).
layer = pdk.Layer(
'HexagonLayer',
df,
get_position=('LON', 'LAT'),
radius=500, #bin radius
auto_highlight=True,
elevation_scale=50, #scale factor for bins (the greater - the higher)
elevation_range=(0, 3000),
pickable=True,
extruded=True,#cell elevation
)

La seconda variabile che dobbiamo creare è lo stato di visualizzazione. Dobbiamo nutrirlo con:

  • longitudine e latitudine;
  • zoom (zoom iniziale);
  • min_zoom e max_zoom;
  • rilevamento (angolo di visione sinistro/destro);
  • inclinazione (angolo di visione su/giù).
view_state = pdk.ViewState(
longitude=-118.3,
latitude=34.4,
zoom=8,
min_zoom=6,
max_zoom=15,
bearing=-20,#left/right angle
pitch=20, #up/down angle
)

Poiché disponiamo di un set di dati di grandi dimensioni, Google Colab non visualizza tutto il set di dati, quindi hai due opzioni:

  • Campionare N righe dal set di dati;
  • Salva la mappa in HTML e aprila nel tuo browser.

Se scegli il secondo, otterrai questo:

Immagine di autore.

Ad essere onesti, mi piace l'aspetto degli esagoni, ma non li ho mai visti in nessun articolo/presentazione/lezione scientifica, quindi consiglierei di usarli consapevolmente.

Ora proviamo a creare una visualizzazione simile, ma utilizzando le colonne. Ma in questo caso dovremo passare alla funzione il dataset xarray che abbiamo creato in precedenza e specificare il colore e la variabile da visualizzare:

layer = pdk.Layer(
'ColumnLayer',
ds.to_dataframe().reset_index(),
get_position=('lon', 'lat'),
get_elevation='Crimes',
elevation_scale=10,
radius=200,
get_fill_color=('Crimes', 220),
pickable=True,
extruded=True,
)
Immagine di autore.

In sostanza, il grafico a dispersione è una nuvola di punti, ma gli autori di PyDeck hanno sviluppato dei cilindri, che sembrano piuttosto unici:

Immagine di autore.
layer = pdk.Layer(
'ColumnLayer',
df(:15000),
get_position=('LON', 'LAT'),
auto_highlight=True,
get_radius=200, # Radius is given in meters
get_fill_color=(180, 0, 200, 140), # Set an RGBA value for fill
pickable=True)
Immagine di autore.

Una cosa davvero interessante di PyDeck è che, come plotly, geemap, folium e altri strumenti di mappatura interattivi, consente agli utenti di modificare la mappa di base, il che significa che puoi progettare una mappa in base al contesto del tuo progetto:

r = pdk.Deck(layers=(layer),
initial_view_state=view_state,
map_style=pdk.map_styles.LIGHT, # ‘light’, ‘dark’, ‘road’, ‘satellite’, ‘dark_no_labels’, and ‘light_no_labels
)
Immagine di autore.

La funzionalità successiva, che trovo estremamente utile, sta modificando la descrizione interattiva dei dati. Posizionando il cursore su una colonna/esagono/punto puoi ottenere metainformazioni, ma spesso sembrano un po' assurde. Ma in PyDeck puoi facilmente superarlo:

Immagine di autore.
r = pdk.Deck(layers=(layer),
initial_view_state=view_state,
"html": "<b>Number of crimes:</b> {elevationValue}",
"style": {
"backgroundColor": "yellow",
"color": "black"
}
},
)
Immagine di autore.

Infine, la caratteristica più sorprendente di questa libreria è la possibilità di modificare l'angolo di visualizzazione sulla mappa semplicemente facendo clic con il pulsante destro del mouse:

from ipywidgets import HTML

text = HTML(value='Move the viewpoint')

def filter_by_bbox(row, west_lng, east_lng, north_lat, south_lat):
return west_lng < row('lng') < east_lng and south_lat < row('lat') < north_lat

def filter_by_viewport(widget_instance, payload):
west_lng, north_lat = payload('data')('nw')
east_lng, south_lat = payload('data')('se')
filtered_df = df(df.apply(lambda row: filter_by_bbox(row, west_lng, east_lng, north_lat, south_lat), axis=1))

r.deck_widget.on_click(filter_by_viewport)

Immagine di autore.

Mi diverto sicuramente PyDeck e pianifica di immergerti più a fondo nel deck.jl struttura. Con una sintassi estremamente semplice e intuitiva, consente agli utenti di creare visualizzazioni impressionanti a basso consumo energetico. Python limita molto le capacità di questo pacchetto, quindi dai un'occhiata al loro galleriaè strabiliante, soprattutto la loro funzione sperimentale GlobalView…

Speriamo che questo articolo sia stato informativo e approfondito per te!

===========================================

Tutte le mie pubblicazioni su Medium sono gratuite e ad accesso aperto, ecco perché apprezzerei davvero se mi seguissi qui!

Ps Sono estremamente appassionato di (Geo)Data Science, ML/AI e cambiamento climatico. Quindi, se vuoi lavorare insieme su qualche progetto, contattami LinkedIn.

🛰️Segui per saperne di più🛰️

Fonte: towardsdatascience.com

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *