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:

Un colorido mapa de distancias desde la Universidad de Texas en Austin.
Distancia de la Universidad de Texas a otras escuelas de la SEC (por autor)

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:

Una visualización de los nodos (puntos) en una malla 2D.
Los nodos de una cuadrícula creada con resolución = 100 (por autor)

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, yyy distancesvalores, 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.