
本文详细介绍了如何根据一个索引列表,从现有pandas dataframe中高效提取对应的x、y坐标,并构建一个新的dataframe。文章首先探讨了基于循环和字典的初步实现方式及其改进,随后重点展示了利用numpy进行矢量化操作的优化方案,该方案显著提升了数据处理性能,为后续的数据可视化和分析奠定了坚实基础。
从索引映射构建坐标DataFrame教程
在数据处理和分析中,我们经常需要根据特定的映射规则从一个数据集中提取信息并重构为新的数据结构。本教程将聚焦于一个常见场景:给定一个包含索引对的列表和一个包含坐标信息的Pandas DataFrame,目标是创建一个新的DataFrame,其中每一行代表一个由索引对指向的X、Y坐标。
1. 问题描述与原始数据
假设我们拥有以下两组数据:
- tours: 一个列表的列表(list of lists),其中每个子列表包含两个整数。第一个整数代表原始DataFrame中X坐标的行索引,第二个整数代表Y坐标的行索引。
- df: 一个Pandas DataFrame,包含多列数据,其中包括 Node、X、Y、Demand 和 Profit。
我们的任务是创建一个名为 coord 的新DataFrame,它只有 X 和 Y 两列。coord 的每一行都应根据 tours 中的索引对,从 df 中查找对应的X和Y值。
示例原始数据:
import pandas as pd
import numpy as np
tours = [[0, 4], [0, 5], [0, 6], [1, 13], [2, 0], [3, 8], [4, 9], [5, 10],
[6, 7], [7, 1], [8, 2], [9, 3], [10, 11], [11, 14], [12, 0], [13, 12], [14, 0]]
data = {
'Node': [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
'X': [5.7735, 2.8867, -2.8868, -5.7735, -2.8867, 2.8868, 8.6603, 0.0000, -8.6603, -8.6603, 0.0000, 8.6603, 5.3405, 3.3198, 6.4952],
'Y': [0.00, 5.00, 5.00, 0.00, -5.00, -5.00, 5.00, 10.00, 5.00, -5.00, -10.00, -5.00, 0.75, 4.25, -1.25],
'Demand': [40.0, 40.0, 40.0, 40.0, 40.0, 40.0, 40.0, 40.0, 40.0, 40.0, 40.0, 40.0, 10.0, 10.0, 10.0],
'Profit': [16.0, 16.0, 16.0, 16.0, 16.0, 16.0, 16.0, 24.0, 24.0, 24.0, 24.0, 24.0, 10.0, 10.0, 11.0]
}
df = pd.DataFrame(data, index=range(len(data['Node']))) # 确保df的索引从0开始,与tours中的索引匹配理解 tours 的含义:tours 中的 [0, 4] 表示:
- X坐标取自 df.iloc[0]['X']
- Y坐标取自 df.iloc[4]['Y']
2. 方法一:基于循环和字典的构建
一种直观的方法是遍历 tours 列表,在每次迭代中根据索引从 df 中提取X和Y值,并将它们存储在一个字典中,最后将字典转换为DataFrame。
2.1 初步尝试与问题
最初的尝试可能如下:
d = {}
for t, tour in enumerate(tours):
xi = tour[0]
yi = tour[1]
key = t
d[key] = df["X"].iloc[xi], df["Y"].iloc[yi]
# 尝试直接使用pd.DataFrame(d.items(), columns=['X', 'Y'])
# 这将导致错误或不符合预期的结果,因为d.items()会把键作为第一列,值(元组)作为第二列这种方法在将字典 d 转换为DataFrame时会遇到问题。pd.DataFrame(d.items(), columns=['X', 'Y']) 会将字典的键作为第一列(在这里是索引 0, 1, 2...),而将值(一个包含X和Y的元组)作为第二列。这并不是我们期望的两列 X 和 Y。
2.2 改进后的字典方法
为了正确地将字典转换为DataFrame,其中字典的键作为DataFrame的索引,而字典的值(元组)拆分为多列,我们需要使用 pd.DataFrame.from_dict() 方法,并设置 orient='index' 参数。
d = {}
for t, tour in enumerate(tours):
xi = tour[0] # 获取X坐标的索引
yi = tour[1] # 获取Y坐标的索引
# 根据索引从df中查找对应的X和Y值
x_val = df["X"].iloc[xi]
y_val = df["Y"].iloc[yi]
d[t] = (x_val, y_val) # 将(X, Y)元组作为字典的值
# 使用from_dict并指定orient='index'来正确构建DataFrame
coord_loop = pd.DataFrame.from_dict(d, orient='index', columns=['X', 'Y'])
print("方法一(循环+字典)结果:")
print(coord_loop.head())优点:
- 逻辑清晰,易于理解,适合初学者。
- 在数据量较小的情况下表现尚可。
缺点:
- 使用Python循环遍历Pandas Series,效率较低,尤其是在处理大型数据集时。
- 需要额外创建字典作为中间数据结构。
3. 方法二:利用NumPy进行矢量化优化 (推荐)
对于Pandas和NumPy而言,矢量化操作通常比Python循环具有更高的性能。我们可以将 tours 转换为NumPy数组,并将 df 中的X、Y列提取为NumPy数组,然后利用NumPy的高级索引功能一步到位地获取所有坐标。
3.1 核心思想
- 将 tours 转换为NumPy数组,这样可以方便地通过 tours[:, 0] 获取所有X索引,通过 tours[:, 1] 获取所有Y索引。
- 将 df 的 X 和 Y 列提取为一个NumPy数组,这样可以方便地通过索引直接访问这些值。
- 利用NumPy的广播和高级索引特性,一次性从 df 的X、Y数组中提取所有需要的坐标。
3.2 实现步骤
# 1. 将tours列表转换为NumPy数组
tours_np = np.array(tours)
# 2. 从df中提取X和Y列,并转换为NumPy数组
# 这样arr[index, 0] 对应X值,arr[index, 1] 对应Y值
df_coords_np = df[["X", "Y"]].to_numpy()
# 3. 使用高级索引一次性获取所有X和Y坐标
# tours_np[:, 0] 提供了所有X坐标的索引
# tours_np[:, 1] 提供了所有Y坐标的索引
extracted_x = df_coords_np[tours_np[:, 0], 0] # 获取所有X坐标
extracted_y = df_coords_np[tours_np[:, 1], 1] # 获取所有Y坐标
# 4. 构建最终的DataFrame
coord_vectorized = pd.DataFrame({"X": extracted_x, "Y": extracted_y})
print("\n方法二(NumPy矢量化)结果:")
print(coord_vectorized.head())代码解释:
- tours_np = np.array(tours): 将 tours 转换为一个 (n, 2) 形状的NumPy数组。
- df_coords_np = df[["X", "Y"]].to_numpy(): 从 df 中选择 X 和 Y 列,并将其转换为一个NumPy数组。这个数组的行索引与原始 df 的行索引一致,第一列是 X 值,第二列是 Y 值。
- tours_np[:, 0]:这会选择 tours_np 数组的所有行,并获取每行的第一个元素(即X坐标的索引)。
- tours_np[:, 1]:这会选择 tours_np 数组的所有行,并获取每行的第二个元素(即Y坐标的索引)。
- df_coords_np[tours_np[:, 0], 0]: 这是一个高级索引操作。它使用 tours_np[:, 0] 作为行索引,从 df_coords_np 中选择对应的行,然后从这些行中选择第0列(即X坐标)。
- df_coords_np[tours_np[:, 1], 1]: 同样,它使用 tours_np[:, 1] 作为行索引,从 df_coords_np 中选择对应的行,然后从这些行中选择第1列(即Y坐标)。
- pd.DataFrame({"X": extracted_x, "Y": extracted_y}): 最后,将提取出的X和Y数组构建成一个新的Pandas DataFrame。
优点:
- 高性能: NumPy的矢量化操作在底层使用C语言实现,效率远高于Python循环。
- 代码简洁: 相比于循环,矢量化代码通常更简洁、更易读(对于熟悉NumPy的用户)。
- 内存效率: 减少了中间数据结构的创建。
4. 性能对比与最佳实践
在处理中小型数据集时,两种方法可能在执行时间上差异不大。然而,当 tours 列表包含成千上万甚至数百万个元素时,NumPy的矢量化方法将展现出压倒性的性能优势。
最佳实践建议:
- 优先使用矢量化操作: 在Pandas和NumPy中,只要有可能,就应优先考虑使用矢量化操作来代替显式的Python循环。
- 理解数据结构: 在进行复杂索引和数据提取时,清晰地理解原始数据(df)和索引数据(tours)的结构是至关重要的。
- 验证结果: 无论采用哪种方法,都应在小规模数据集上验证输出结果的正确性。
5. 总结与展望
本教程展示了两种从索引映射构建坐标DataFrame的方法。虽然基于循环和字典的方法直观易懂,但利用NumPy进行矢量化处理是更高效、更专业的解决方案,尤其适用于大规模数据处理。
最终生成的 coord DataFrame(无论是通过方法一的改进版还是方法二)都包含了我们需要的X和Y坐标对。这个DataFrame可以直接用于后续的数据可视化任务,例如使用Matplotlib、Seaborn或Plotly等库绘制路径或散点图。例如,要绘制路径,可以直接使用 coord['X'] 和 coord['Y'] 作为绘图函数的输入。
# 示例:使用matplotlib绘制路径(需要安装matplotlib)
# import matplotlib.pyplot as plt
# plt.figure(figsize=(10, 8))
# plt.plot(coord_vectorized['X'], coord_vectorized['Y'], marker='o', linestyle='-', color='blue')
# plt.title('Generated Route Coordinates')
# plt.xlabel('X Coordinate')
# plt.ylabel('Y Coordinate')
# plt.grid(True)
# plt.show()掌握这种高效的数据提取和重构技术,将极大地提升您在Python数据科学项目中的工作效率。










