关注

如何使用 Python 制作邻近地图

原文:towardsdatascience.com/how-to-make-proximity-maps-with-python-da398788e058

快速成功数据科学

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/af550bdce52892afb0a864adcf7e352c.png

密西西比州立大学的距离图(作者提供)

你有没有注意到社交媒体上的一些“距离”地图?我刚刚看到了一个由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 学校的距离:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/a7ed9ddf8f5edc4a78696e5c838a6aa9.png

德克萨斯大学到其他 SEC 学校的距离(作者提供)

导入库

这个项目需要NumPyMatplotlibpandasgeopandasgeopyscipy。你可以在链接中找到安装说明。

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 数组,可以查看这篇文章:

介绍 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_maxy_min, y_max)。

接下来,我们使用 NumPy 的 meshgrid() 方法在由最小和最大纬度和经度定义的范围内创建一个点网格。

这是分辨率为 100 的网格看起来是怎样的:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/1c6b6500c2ac2ce6d250e127a20ae1e7.png

使用分辨率为 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) 的点的坐标,其中 yyxx 保存网格坐标。

最后一行计算了从学校到当前点坐标(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 的神秘面纱

为了绘制颜色填充的等高线,我们使用 Matplotlib 的[contourf()](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.contourf.html)方法。它使用xxyydistances值,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)))

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/dfbda298977e3eaea68f52038c361ac4.png

距离最近的 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]

这是结果:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/3525eaa87d2f66a5cc0028c2779fef2c.png

具有新限制和州的新最近学校距离地图(作者)

我们还可以做更多花哨的事情,比如手动移除不在 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()

这是生成的温度地图:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/9a4314f9ec95c2fdf7364d92a9cf341c.png

温度等高线地图(作者)

这种技术在任何连续且平滑变化的数据上效果都很好,例如温度、降水量、人口等。


摘要

在地图上绘制数据等高线是许多职业的常见做法,包括地质学、气象学、经济学和社会学。在这篇文章中,我们使用了一系列 Python 库来制作从特定大学到多个大学的距离地图,然后我们探讨了如何对其他连续数据进行网格化和等高线绘制,例如温度数据。


谢谢!

感谢阅读,请关注我,未来将有更多快速成功数据科学项目。

转载自CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/wizardforcel/article/details/156281001

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--