Aquí está el código completo (escrito en JupyterLab). Desglosaré los bloques de código en las siguientes secciones.
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import geopandas as gpd
from geopy.distance import great_circle# SEC schools with coordinates (coords by ChatGPT4):
data = {
'school': ['Alabama', 'LSU', 'Ole Miss', 'Miss State',
'Auburn', 'Arkansas', 'Missouri', 'Vanderbilt',
'Tennessee', 'Florida', 'Georgia', 'Kentucky',
'S. Carolina', 'TAMU', 'Texas', 'Oklahoma'],
'latitude': [33.209, 30.412, 34.365, 33.456,
32.603, 36.068, 38.951, 36.162,
35.960, 29.651, 33.950, 38.049,
34.000, 30.620, 30.284, 35.222],
'longitude': [-87.538, -91.177, -89.526, -88.811,
-85.484, -94.172, -92.328, -86.784,
-83.920, -82.324, -83.377, -84.500,
-81.034, -96.340, -97.740, -97.445]
}
df = pd.DataFrame(data)
# Pick a school to plot the distance from.
# Use the same name as in the previous data dict:
SCHOOL = 'Texas'
# Set the grid resolution.
# Larger = higher res and smoother contours:
RESOLUTION = 500
# Get coordinates for SCHOOL:
school_index = df[df['school'] == SCHOOL].index[0]
school_coords = df.loc[school_index, ['latitude', 'longitude']].to_numpy()
# Create grid of points for interpolation:
x_min, x_max = df['longitude'].min(), df['longitude'].max()
y_min, y_max = df['latitude'].min(), df['latitude'].max()
xx, yy = np.meshgrid(np.linspace(x_min, x_max, RESOLUTION),
np.linspace(y_min, y_max, RESOLUTION))
# Calculate distances from SCHOOL to every point in grid:
distances = np.zeros(xx.shape)
for i in range(xx.shape[0]):
for j in range(xx.shape[1]):
point_coords = (yy[i, j], xx[i, j])
distances[i, j] = great_circle(school_coords, point_coords).miles
# Create the color-filled contour map:
fig, ax = plt.subplots(1, 1, figsize=(10, 8))
contour = ax.contourf(xx, yy, distances,
cmap='coolwarm',
alpha=0.9)
cbar = fig.colorbar(contour, ax=ax, shrink=0.7)
cbar.set_label(f'Distance from {SCHOOL} (miles)')
ax.scatter(df['longitude'], df['latitude'], s=2, color='black')
# Load state boundaries from US Census Bureau:
url = 'https://www2.census.gov/geo/tiger/GENZ2021/shp/cb_2021_us_state_20m.zip'
states = gpd.read_file(url)
# Filter states within the map limits:
states = states.cx[x_min:x_max, y_min:y_max]
# Plot the state boundaries:
states.boundary.plot(ax=ax, linewidth=1, edgecolor='black')
# Add labels for the schools:
for i, school in enumerate(df['school']):
ax.annotate(
school,
(df['longitude'][i], df['latitude'][i]),
textcoords="offset points",
xytext=(2, 1),
ha='left',
fontsize=8
)
ax.set_xlabel('Longitude')
ax.set_ylabel('Latitude')
ax.set_title(f'Distance from {SCHOOL} to Other SEC Schools')
# fig.savefig('distance_map.png', dpi=600)
plt.show()
Y aquí está el resultado, que muestra la distancia desde la Universidad de Texas en Austin a las otras escuelas de la SEC:
Importación de bibliotecas
Este proyecto requiere NumPy, Matplotlib, pandas, geopandas, geopyy picante. Puede encontrar instrucciones de instalación en los enlaces.
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import geopandas as gpd
from geopy.distance import great_circle
Cargando datos
Para los datos de entrada, hice una lista de las escuelas y luego hice que ChatGPT produjera el diccionario con las coordenadas lat-lon. Luego, el diccionario se convirtió en un DataFrame de pandas llamado df
.
# SEC schools with coordinates (coords by ChatGPT4):
data = {
'school': ['Alabama', 'LSU', 'Ole Miss', 'Miss State',
'Auburn', 'Arkansas', 'Missouri', 'Vanderbilt',
'Tennessee', 'Florida', 'Georgia', 'Kentucky',
'S. Carolina', 'TAMU', 'Texas', 'Oklahoma'],
'latitude': [33.209, 30.412, 34.365, 33.456,
32.603, 36.068, 38.951, 36.162,
35.960, 29.651, 33.950, 38.049,
34.000, 30.620, 30.284, 35.222],
'longitude': [-87.538, -91.177, -89.526, -88.811,
-85.484, -94.172, -92.328, -86.784,
-83.920, -82.324, -83.377, -84.500,
-81.034, -96.340, -97.740, -97.445]
}df = pd.DataFrame(data)
Asignar constantes
El código producirá un mapa de distancia desde una de las escuelas de la SEC enumeradas. Asignaremos el nombre de la escuela (escrito exactamente como aparece en el diccionario) a una constante llamada SCHOOL
.
# Pick a school to plot the distance from.
# Use the same name as in the data dict:
SCHOOL = 'Texas'
Para controlar la «suavidad» de los contornos, usaremos una constante llamada RESOLUTION
. Cuanto mayor sea el número, más fina será la cuadrícula subyacente y, por tanto, más suaves serán los contornos. Los valores entre 500 y 1000 producen buenos resultados.
# Set the grid resolution.
# Larger = higher res and smoother contours:
RESOLUTION = 500
Obtener la ubicación de la escuela
Ahora para obtener las coordenadas del mapa de la escuela especificada. En este caso, la escuela será la Universidad de Texas en Austin, Texas.
# Get coordinates for SCHOOL:
school_index = df[df['school'] == SCHOOL].index[0]
school_coords = df.loc[school_index, ['latitude', 'longitude']].to_numpy()
La primera línea identifica el DataFrame. índice de la escuela especificada por el SCHOOL
constante. Luego, este índice se utiliza para obtener las coordenadas de la escuela. Porque index
devuelve una lista de índices donde la condición es verdadera, usamos [0]
para obtener el primer elemento (presumiblemente el único) de esta lista.
A continuación, extraemos los valores de latitud y longitud del DataFrame y los convertimos en una matriz NumPy con el to_numpy()
método.
Si no está familiarizado con las matrices NumPy, consulte este artículo:
Creando la cuadrícula
Antes de hacer un mapa de contorno, debemos construir una cuadrícula regular y llenar los nodos de la cuadrícula (intersecciones) con valores de distancia. El siguiente código crea la cuadrícula.
# Create grid of points for interpolation:
x_min, x_max = df['longitude'].min(), df['longitude'].max()
y_min, y_max = df['latitude'].min(), df['latitude'].max()
xx, yy = np.meshgrid(np.linspace(x_min, x_max, RESOLUTION),
np.linspace(y_min, y_max, RESOLUTION))
El primer paso aquí es obtener los valores mínimo y máximo (x_min, x_max
y y_min, y_max
) de la longitud y latitud del DataFrame.
A continuación, utilizamos NumPy meshgrid()
Método para crear una cuadrícula de puntos dentro de los límites definidos por las latitudes y longitudes mínimas y máximas.
Así es como se ve la cuadrícula para una resolución de 100:
Cada nodo contendrá un valor que se puede contornear.
Calcular distancias
El siguiente código calcula distancias concéntricas desde la escuela especificada.
# Calculate distances from SCHOOL to every point in grid:
distances = np.zeros(xx.shape)
for i in range(xx.shape[0]):
for j in range(xx.shape[1]):
point_coords = (yy[i, j], xx[i, j])
distances[i, j] = great_circle(school_coords, point_coords).miles
La primera orden del día es inicializar una matriz NumPy llamada distances
. Tiene la misma forma que elxx
cuadrícula y está lleno de ceros. Lo usaremos para almacenar las distancias calculadas desde SCHOOL
.
A continuación, recorremos las filas de la cuadrícula y luego, en un bucle anidado, iteramos sobre las columnas de la cuadrícula. Con cada iteración recuperamos las coordenadas del punto en la posición (i, j)
en la grilla, con yy
y xx
sosteniendo las coordenadas de la cuadrícula.
La última línea calcula el distancia del gran círculo (la distancia entre dos puntos en una esfera) desde la escuela hasta las coordenadas del punto actual (point_coords
). El resultado final es una serie de distancias con unidades en millas.
Creando el mapa
Ahora que tenemos datos de x, y y distancia, podemos contornear los valores de distancia y hacer una visualización.
# Create the color-filled contour map:
fig, ax = plt.subplots(1, 1, figsize=(10, 8))
contour = ax.contourf(xx, yy, distances,
cmap='coolwarm',
alpha=0.9)
cbar = fig.colorbar(contour, ax=ax, shrink=0.7)
cbar.set_label(f'Distance from {SCHOOL} (miles)')
ax.scatter(df['longitude'], df['latitude'], s=2, color='black')
Comenzamos configurando una figura Matplotlib de tamaño 10 x 8. Si no está familiarizado con el fig, ax
terminología, consulte este fantástico artículo para obtener una introducción rápida:
Para dibujar los contornos llenos de color utilizamos Matplotlib. contourf()
método. Utiliza el xx
, yy
y distances
valores, el coolwarm
mapa de colores y una ligera cantidad de transparencia (alpha=0.9
).
En mi opinión, falta la barra de color predeterminada para la pantalla, por lo que la personalizamos un poco. El fig.colorbar()
El método agrega una barra de color al gráfico para indicar la escala de distancia. El shrink
El argumento evita que la altura de la barra de color sea desproporcionada con respecto a la trama.
Finalmente, usamos Matplotlib scatter()
método para agregar las ubicaciones de las escuelas al mapa, con un tamaño de marcador de 2
. Más adelante etiquetaremos estos puntos con los nombres de las escuelas.
Agregar los límites estatales
Actualmente, el mapa solo tiene las ubicaciones de las escuelas para usar como puntos de referencia. Para que el mapa sea más identificable, el siguiente código agrega límites estatales.
# Load state boundaries from US Census Bureau:
url = 'https://www2.census.gov/geo/tiger/GENZ2021/shp/cb_2021_us_state_20m.zip'
states = gpd.read_file(url)# Filter states within the map limits:
states = states.cx[x_min:x_max, y_min:y_max]
# Plot the state boundaries:
states.boundary.plot(ax=ax, linewidth=1, edgecolor='black')
La tercera línea utiliza geopandas. cx
Método de indexación para corte espacial. Filtra geometrías en un GeoDataFrame basado en un cuadro delimitador definido por las coordenadas x (longitud) e y (latitud) mínima y máxima. Aquí, filtramos todos los estados fuera del cuadro delimitador.
Agregar etiquetas y un título
El siguiente código finaliza la trama atando algunos cabos sueltos, como agregar los nombres de las escuelas a sus marcadores de mapa, etiquetar los ejes xey y establecer un título actualizable.
# Add labels for the schools:
for i, school in enumerate(df['school']):
ax.annotate(
school,
(df['longitude'][i], df['latitude'][i]),
textcoords="offset points",
xytext=(2, 1),
ha='left',
fontsize=8
)ax.set_xlabel('Longitude')
ax.set_ylabel('Latitude')
ax.set_title(f'Distance from {SCHOOL} to Other SEC Schools')
fig.savefig('distance_map.png', dpi=600)
plt.show()
Para etiquetar las escuelas, utilizamos un for
bucle y enumeración para elegir las coordenadas y nombres correctos para cada escuela y usar Matplotlib annotate()
método para publicarlos en el mapa. Usamos annotate()
en lugar del text()
método para acceder al xytext
argumento, que nos permite cambiar la etiqueta a donde queramos.