原文:
towardsdatascience.com/how-to-make-proximity-maps-with-python-da398788e058
快速成功数据科学
密西西比州立大学的距离图(作者提供)
你有没有注意到社交媒体上的一些“距离”地图?我刚刚看到了一个由Todd Jones制作的地图,展示了你在下 48 个州中任何位置的公园距离。
这些邻近地图既有趣又实用。如果你是一名生存主义者,你可能希望尽可能远离潜在的核导弹目标;如果你是一位热情的渔夫,你可能希望靠近一个Bass Pro Shop。
我和一个几乎对美国大学足球一无所知的英国人一起上了研究生。尽管如此,他在我们每周的赌池中表现得很好。他的一个秘密是,任何需要旅行超过 300 英里去比赛的球队,他都下注,假设竞争队伍实力相当,或者主队有优势。
在这个快速成功数据科学项目中,我们将使用 Python 为东南联盟(SEC)的大学足球队制作“距离”地图。我们将找出平均需要长途跋涉最远的球队,以及最短距离的球队。然后,我们在美国东南部的地图上绘制这些距离的等高线。此外,我们还将探讨如何对其他连续数据,如温度,进行网格化和等高线绘制。
代码
这里是完整的代码(在 JupyterLab 中编写)。我将在以下部分分解代码块。
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()
这里是输出结果,显示了奥斯汀的德克萨斯大学到其他 SEC 学校的距离:
德克萨斯大学到其他 SEC 学校的距离(作者提供)
导入库
这个项目需要NumPy、Matplotlib、pandas、geopandas、geopy和scipy。你可以在链接中找到安装说明。
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import geopandas as gpd
from geopy.distance import great_circle
加载数据
对于输入数据,我制作了一个学校列表,然后让 ChatGPT 生成包含经纬坐标的字典。然后,这个字典被转换成了一个名为df的 pandas DataFrame。
# 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)
分配常量
代码将生成从列表中列出的 SEC 学校之一生成的距离图。我们将学校的名称(精确地按照字典中的格式输入)分配给一个名为SCHOOL的常量。
# Pick a school to plot the distance from.
# Use the same name as in the data dict:
SCHOOL = 'Texas'
为了控制等高线的“平滑度”,我们将使用一个名为 RESOLUTION 的常量。数字越大,基础网格越细,因此等高线越平滑。大约 500-1000 的值会产生良好的结果。
# 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()
第一行识别由 SCHOOL 常量指定的学校的 DataFrame 索引。然后使用此索引获取学校的坐标。因为 index 返回一个条件为真的索引列表,所以我们使用 [0] 来获取此列表中的第一个(可能是唯一的)项。
接下来,我们从 DataFrame 中提取纬度和经度值,并使用 to_numpy() 方法将它们转换为 NumPy 数组。
如果你不太熟悉 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))
此处的第一步是从 DataFrame 中获取经度和纬度的最小值和最大值(x_min, x_max 和 y_min, y_max)。
接下来,我们使用 NumPy 的 meshgrid() 方法在由最小和最大纬度和经度定义的范围内创建一个点网格。
这是分辨率为 100 的网格看起来是怎样的:
使用分辨率为 100 创建的网格节点(作者制图)
每个节点将保存一个可以绘制轮廓的值。
计算距离
以下代码计算了指定学校的同心距离。
# 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
首要任务是初始化一个名为 distances 的 NumPy 数组。它具有与 xx 网格相同的形状,并填充了零。我们将使用它来存储从 SCHOOL 计算出的距离。
接下来,我们遍历网格的行,然后在内层循环中遍历网格的列。在每次迭代中,我们检索网格中位置 (i, j) 的点的坐标,其中 yy 和 xx 保存网格坐标。
最后一行计算了从学校到当前点坐标(point_coords)的大圆距离(球面上两点之间的距离)。最终结果是具有英里单位的距离数组。
创建地图
现在我们有了 x、y 和距离数据,我们可以绘制距离值并制作显示。
# 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')
我们首先设置一个大小为 10 x 8 的 Matplotlib 图形。如果你不熟悉fig, ax术语,可以查看这篇出色的文章进行快速介绍:
为了绘制颜色填充的等高线,我们使用 Matplotlib 的[contourf()](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.contourf.html)方法。它使用xx、yy和distances值,coolwarm颜色映射,以及少量透明度(alpha=0.9)。
我认为默认的颜色条显示不够,所以我们进行了一些自定义。fig.colorbar()方法向图表添加一个颜色条,以指示距离刻度。shrink参数保持颜色条的高度与图表的比例适当。
最后,我们使用 Matplotlib 的scatter()方法将学校位置添加到地图上,标记大小为2。稍后,我们将使用学校名称来标注这些点。
添加州边界
目前地图上只有学校位置作为地标。为了使地图更具相关性,以下代码添加了州边界。
# 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')
第三行使用 geopandas 的cx索引方法进行空间切片。它根据最小和最大 x(经度)和 y(纬度)坐标定义的边界框过滤 GeoDataFrame 中的几何形状。在这里,我们过滤掉所有边界框外的州。
添加标签和标题
以下代码通过解决一些松散的结尾,如将学校名称添加到地图标记上,标注 x 和 y 轴,并设置可更新的标题来完成绘图。
# 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()
为了标记学校,我们使用一个for循环和枚举来选择每个学校的正确坐标和名称,并使用 Matplotlib 的annotate()方法将它们标注在地图上。我们使用annotate()而不是text()方法来访问xytext参数,这让我们可以将标签移动到我们想要的位置。
寻找最短和最长的平均距离
如果我们不想用地图,而是想找到某个学校的平均旅行距离呢?或者找到哪些学校的平均距离最短和最长?以下代码将使用之前的df DataFrame 和之前使用的great_circle()方法来完成这些操作:
# Calculate average distances between each school and the others
coords = df[['latitude', 'longitude']].to_numpy()
distance_matrix = np.zeros((len(coords), len(coords)))
for i in range(len(coords)):
for j in range(len(coords)):
distance_matrix[i, j] = great_circle((coords[i][0], coords[i][1]),
(coords[j][0], coords[j][1])).miles
avg_distances = distance_matrix.mean(axis=1)
shortest_avg_distance_school = df['school'].iloc[avg_distances.argmin()]
longest_avg_distance_school = df['school'].iloc[avg_distances.argmax()]
print(f"School with shortest average distance: {shortest_avg_distance_school}")
print(f"School with longest average distance: {longest_avg_distance_school}")
School with shortest average distance: Miss State
School with longest average distance: Texas
密西西比州立大学位于 SEC 中心附近,平均旅行距离最短(320 英里)。德克萨斯大学位于会议的远西部边缘,平均旅行距离最长(613 英里)。
备注:这些平均距离没有考虑年度日程表。在一个赛季中,没有足够的比赛让所有球队相互比赛,所以给定年份的平均距离可能比这里计算的要短或长。然而,在三年期间,每所学校都会轮流与所有会议球队比赛。
寻找最近的 SEC 学校
记得在本文开头我提到了一个距离最近的国家级公园地图吗?现在我将向你展示如何制作这样一个地图,只是我们将使用 SEC 学校来代替公园。
你所要做的就是拿我们之前的代码,并用这个片段替换“计算距离”块(并调整图表的标题文本):
# Calculate minimum distance to any school from every point in the 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] = min(great_circle(point_coords,
(df.loc[k, 'latitude'],
df.loc[k, 'longitude'])).miles
for k in range(len(df)))
距离最近的 SEC 学校在边界框内的距离(作者)
这可能需要几分钟,所以请耐心等待(或者在运行之前降低网格的分辨率)。
为了制作一个更简洁的地图,通过以下编辑来扩大网格的大小:
# Create grid of points for interpolation
x_min, x_max = -107, -75
y_min, y_max = 23.5, 42.5
xx, yy = np.meshgrid(np.linspace(x_min, x_max, RESOLUTION),
np.linspace(y_min, y_max, RESOLUTION))
使用以下替换来调整州边界的经纬度维度:
# Filter states within the map limits
states = states.cx[-100:-80, 25:36.5]
这是结果:
具有新限制和州的新最近学校距离地图(作者)
我们还可以做更多花哨的事情,比如手动移除不在 SEC 中的州,并将等高线地图裁剪到州的外部边界。但现在我很累了,所以这些将是另一篇文章的任务!
网格化和等高线绘制其他连续数据
在前面的例子中,我们是从位置数据开始,直接从地图坐标计算“距离”。在许多情况下,你会有附加数据,例如温度测量数据,你希望进行等高线绘制。
这里有一个示例脚本,基于我们之前所做的工作。我已经将学校名称替换为华氏度的温度。我还使用了 SciPy 来网格化数据,作为改变节奏的一种方式。
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import geopandas as gpd
from scipy.interpolate import griddata
# Load the temperature data:
data = {
'temp': [90, 94, 89, 90,
91, 87, 85, 84,
87, 95, 90, 83,
88, 95, 96, 90],
'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)
# Generate grid data
x_min, x_max = df['longitude'].min(), df['longitude'].max()
y_min, y_max = df['latitude'].min(), df['latitude'].max()
grid_lat, grid_lon = np.mgrid[y_min:y_max:100j, x_min:x_max:100j]
grid_temp = griddata((df.latitude, df.longitude),
df.temp, (grid_lat, grid_lon),
method='cubic')
# Create plot
fig, ax = plt.subplots(figsize=(10, 7))
contour = ax.contourf(grid_lon, grid_lat, grid_temp, cmap='coolwarm')
plt.colorbar(contour, ax=ax, label='Temperature (°F)')
# 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 data points and labels
scatter = ax.scatter(df.longitude, df.latitude,
c='black',
edgecolors='white',
s=10)
for i, row in df.iterrows():
ax.text(row['longitude'], row['latitude'],
f"{round(row['temp'])}°F",
fontsize=8,
ha='right',
color='k')
# Set labels and title
ax.set_xlabel('Longitude')
ax.set_ylabel('Latitude')
ax.set_title('Temperature Contours')
plt.savefig('temperature_map.png', dpi=600)
plt.show()
这是生成的温度地图:
温度等高线地图(作者)
这种技术在任何连续且平滑变化的数据上效果都很好,例如温度、降水量、人口等。
摘要
在地图上绘制数据等高线是许多职业的常见做法,包括地质学、气象学、经济学和社会学。在这篇文章中,我们使用了一系列 Python 库来制作从特定大学到多个大学的距离地图,然后我们探讨了如何对其他连续数据进行网格化和等高线绘制,例如温度数据。
谢谢!
感谢阅读,请关注我,未来将有更多快速成功数据科学项目。
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/wizardforcel/article/details/156281001



