PAYATHON 2026

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 特征,可以按以下顺序处理:

  1. 在原始特征矩阵上拟合 PCA,得到二维坐标 Z。
  2. 使用 Z 建立 KDTree。
  3. 使用同一个 PCA 对象转换查询向量。
  4. 查询树,再用返回的行位置读取对应坐标或样本标识。

例如:

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 坐标 Zpca.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 内嵌套查询。

备注:内容仅供参考。