scipy.spatial.distance.cdist批量计算欧式距离最稳最快,输入需为数值型二维数组且列对齐,结果为(len(df1), len(df2))矩阵;余弦相似度推荐sklearn.metrics.pairwise.cosine_similarity,避免scipy的cosine函数。

用 scipy.spatial.distance.cdist 批量算欧式距离
直接调 cdist 最稳,比手写循环或广播快得多,也比 sklearn.metrics.pairwise_distances 更轻量。它默认按行配对,df1 每行和 df2 每行算距离,结果是 (len(df1), len(df2)) 的二维数组。
- 确保两 DataFrame 列名一致、顺序一致,且都是数值型;否则先用
df.select_dtypes(include=['number'])过滤 - 传入前建议重置索引:
cdist(df1.values, df2.values, metric='euclidean'),避免因索引不齐导致隐式对齐出错 - 如果只算自身两两距离(比如聚类),用
squareform(pdist(df.values))更合适,省空间 - 遇到
ValueError: Input must be 2-dimensional,大概率是某列含object类型(比如字符串或 NaN 混在数字列里),用df.apply(pd.to_numeric, errors='coerce')强转
用 sklearn.metrics.pairwise.cosine_similarity 算余弦相似度
别用 scipy.spatial.distance.cosine——它返回的是余弦距离(1 − 相似度),而且只能两两算,没法批量。直接上 cosine_similarity,返回值范围是 [−1, 1],越接近 1 越相似。
- 输入必须是二维数组,所以传
df1.values和df2.values,不是 DataFrame 本身 - 如果数据含负数,余弦相似度仍有效;但若业务上只关心方向(如文本 TF-IDF 向量),可提前用
MinMaxScaler或StandardScaler归一化,不过不是必须 - 出现
LinAlgError: Singular matrix,通常是某行全零(比如某文档所有词频为 0),加一行df = df.replace(0, 1e-10)防除零即可 - 结果是稠密数组,大数据量时注意内存;可改用
cosine_similarity(df1.values, df2.values, dense_output=False)返回稀疏矩阵
DataFrame 行间逐对计算的常见翻车点
很多人想“给每对样本加个 ID”,然后用 apply + 自定义函数,这在几百行以内还行,上千行就明显变慢,且容易因 axis 或索引错位返回形状不对。
- 别用
df1.apply(lambda r1: df2.apply(lambda r2: np.linalg.norm(r1 - r2), axis=1), axis=1)—— 嵌套 apply 效率极低,且r1是 Series,减r2可能因索引对不齐产生 NaN - 如果必须用 pandas 原生操作,至少先对齐列:
common_cols = df1.columns.intersection(df2.columns),再切片df1[common_cols].values - NaN 处理要显式:默认
cdist和cosine_similarity都不接受 NaN,得先填(df.fillna(0))或删(df.dropna()),不能靠自动忽略 - dtype 混合很坑:哪怕一列是
int64、另一列是float32,.values可能悄悄转成object,用df.astype(np.float64).values强制统一
小数据快速验证用 numpy.linalg.norm 手撕单对距离
调试或只算一两个样本时,没必要调库。记住:欧式距离就是 L2 范数,余弦相似度等于点积除以模长乘积。
- 欧式:
np.linalg.norm(df1.iloc[0] - df2.iloc[0])—— 注意iloc返回 Series,减法自动按 index 对齐,务必确认两行 index 完全一致,否则结果错 - 余弦:
u, v = df1.iloc[0].values, df2.iloc[0].values;u @ v / (np.linalg.norm(u) * np.linalg.norm(v)) - 如果
u或v全零,np.linalg.norm返回 0,除零会得nan,加个np.clip或判断更安全
实际用的时候,90% 场景走 cdist 或 cosine_similarity 就够了。真正麻烦的从来不是怎么算,而是数据没对齐、类型没清理、NaN 没处理——这些地方卡住的时间,远多于选哪个函数。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











