KDTree 基于 pivot_table 向量查询 PCA 二维索引时维度不匹配,如何解决?
KDTree 要求查询向量与建树数据的特征维度相同,且处于同一个特征空间。如果用 PCA 降维后的二维坐标建树,就需要先用已拟合的 PCA 转换原始 pivot_table 向量,再进行查询。
如果只是想获取某一行原始数据对应的 PCA 坐标,保留行索引后直接取值更合适。KDTree 返回的是最近邻在建树数组中的行位置,不会自动将原始向量转换为 PCA 坐标。
为什么 MRE 会报错?
建树数据的形状是:
dummy.shape # (2, 2)
也就是两个样本,每个样本有两个特征。查询向量经过 reshape 后的形状是:
x = np.array([0, 8, 9, 0])
x.reshape(1, -1).shape # (1, 4)
建树时每个样本有两个特征,查询时却传入了四个特征,因此报错:
ValueError: query data dimension must match training data dimension
reshape(1, -1) 只是把向量整理成一个样本的输入格式,不会减少特征数,也不会执行 PCA。
在这个 MRE 中,如果查询数据本身就是二维坐标,可以这样写:
idx = tree.query(
np.array([[0.0, 8.0]]),
k=1,
return_distance=False,
)[0, 0]
直接截取原始四维向量的前两个值,虽然能消除维度报错,却不一定有意义。这两个值必须属于建树时使用的坐标空间。
在 PCA 二维空间中查询
假设 Matrix 的每一行是一个样本,每一列是一个 pivot_table 特征,可以按以下顺序处理:
- 在原始特征矩阵上拟合 PCA,得到二维坐标
Z。 - 使用
Z建立 KDTree。 - 使用同一个 PCA 对象转换查询向量。
- 查询树,再用返回的行位置读取对应坐标或样本标识。
例如:
import numpy as np
from sklearn.decomposition import PCA
from sklearn.neighbors import KDTree
# 示例:4 个样本,每个样本有 4 个特征。
# 实际使用时可替换为 Matrix.to_numpy(dtype=float)。
X = np.array([
[0.0, 8.0, 9.0, 0.0],
[1.0, 2.0, 0.0, 3.0],
[7.0, 6.0, 2.0, 1.0],
[3.0, 1.0, 5.0, 4.0],
])
pca = PCA(n_components=2)
Z = pca.fit_transform(X) # shape: (n_samples, 2)
tree = KDTree(Z)
def find_pca_neighbor(x):
x = np.asarray(x, dtype=float)
if x.ndim != 1 or x.shape[0] != X.shape[1]:
raise ValueError(
f"x 必须是长度为 {X.shape[1]} 的一维特征向量"
)
query_z = pca.transform(x.reshape(1, -1)) # shape: (1, 2)
distances, indices = tree.query(query_z, k=1)
idx = int(indices[0, 0])
return idx, Z[idx], float(distances[0, 0])
idx, coordinates, distance = find_pca_neighbor(
np.array([0.0, 8.0, 9.0, 0.0])
)
函数返回三个值:
idx:最近邻在Z中的行位置。coordinates:该最近邻的 PCA 二维坐标。distance:查询点与该最近邻在 PCA 二维空间中的距离。
如果原始数据来自 DataFrame,且行顺序保持一致,可以用这个位置取得原始行标签:
sample_id = Matrix.index[idx]
查询时要调用 pca.transform()。如果对查询向量调用 fit_transform(),就会重新拟合 PCA,得到的坐标空间无法与现有树对应。如果拟合 PCA 前做过标准化,查询数据也必须经过同一个已拟合的标准化器。
如果只是查找已有行对应的坐标
同一批数据中的 X[i] 与 Z[i] 一一对应。知道行位置时,直接读取即可:
coordinates = Z[i]
如果知道 DataFrame 的行标签,且标签唯一,可以先找到行位置:
i = Matrix.index.get_loc(sample_id)
coordinates = Z[i]
如果手头只有原始向量,需要先找出原始特征空间中的最近邻,再取得它的 PCA 坐标,就应使用原始特征建树:
raw_tree = KDTree(X)
x = np.array([0.0, 8.0, 9.0, 0.0])
distances, indices = raw_tree.query(x.reshape(1, -1), k=1)
idx = int(indices[0, 0])
coordinates = Z[idx]
这与前面的二维查询使用了不同的距离判断依据:
| 建树数据 | 查询输入 | 最近邻的判断依据 |
|---|---|---|
原始特征 X | 原始向量 x | 原始特征空间中的距离 |
PCA 坐标 Z | pca.transform(x) | PCA 二维空间中的距离 |
PCA 降维可能改变最近邻关系,需要按实际需求选择查询方式。
最近邻查询也不能保证找到唯一的原始记录。原始数据可能有重复行,降维后也可能出现重合坐标。如果需要准确识别某条记录,应使用稳定的样本 ID。
自定义 metric 应返回距离
MRE 中的 custom() 返回行位置,可以用来查找记录。近邻模型的 metric 则需要接收两个样本向量,并返回一个距离标量,因此不能直接用这个查找函数充当距离函数。
如果要计算两个样本在 PCA 二维空间中的欧氏距离,可以这样写:
def custom_metric(x, x2):
points = np.vstack([x, x2])
projected = pca.transform(points)
return float(np.linalg.norm(projected[0] - projected[1]))
PCA 可以直接转换输入向量,无需通过 KDTree 查找坐标。
原代码中的这处调用也需要检查:
metrics(Matrix.values, Matrix.values, 5)
这里传入的是两个完整矩阵。如果函数内部把 x 当成单个样本,再执行 x.reshape(1, -1),就会将整个矩阵展平成一个超长向量。
要计算所有样本之间的距离,可以让 pairwise_distances 逐对调用距离函数:
from sklearn.metrics import pairwise_distances
D = pairwise_distances(X, metric=custom_metric)
如果只需要 PCA 坐标间的欧氏距离,直接批量计算更方便,也能避免反复执行 PCA 转换:
D = pairwise_distances(Z, metric="euclidean")
使用 metric="precomputed" 时,后续模型应接收这个距离矩阵。训练距离矩阵的形状为 (n_train, n_train),查询距离矩阵的形状为 (n_query, n_train),列顺序必须与训练样本一致。
使用时还要检查什么?
- 查询向量的特征列及其排列顺序必须与拟合 PCA 时一致。即使维度相同,列顺序不同也会得到错误结果。
- 不要通过补零、截断或展平整张矩阵来强行匹配维度,除非这些操作本身就是既定的数据处理规则。
- 即使输入向量不在已有数据中,KDTree 也会返回最近邻。要判断是否存在对应记录,还需要检查距离或使用样本 ID。
- 如果只需要在 PCA 二维空间中做欧氏近邻查询,直接在
Z上建树即可,无需在自定义metric内嵌套查询。
备注:内容仅供参考。
