You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于PySpark实现含缺失值的Partial K-means聚类(SPSS PAIRWISE等效)

分步解决你的PySpark聚类需求

一、先过滤缺失值数量超过4的样本

先给每行统计目标列的缺失数,再筛选出符合要求的样本。假设你的8个目标列是col1到col8,替换成你的实际列名即可:

from pyspark.sql import functions as F

# 定义要处理的8个目标列
target_cols = ["col1", "col2", "col3", "col4", "col5", "col6", "col7", "col8"]

# 计算每行的缺失值数量
df_with_miss_count = df.withColumn(
    "missing_count",
    sum(F.when(F.col(c).isNull(), 1).otherwise(0) for c in target_cols)
)

# 过滤掉缺失数>4的样本,最后删除计数列
filtered_df = df_with_miss_count.filter(F.col("missing_count") <= 4).drop("missing_count")

二、将分类标签转为数值特征

K-means只能处理数值型数据,把strongly agree/agree/disagree转成对应数值(比如3/2/1),保留原有的NaN值:

# 逐个列做数值映射
for col_name in target_cols:
    filtered_df = filtered_df.withColumn(
        f"{col_name}_num",
        F.when(F.col(col_name) == "strongly agree", 3)
          .when(F.col(col_name) == "agree", 2)
          .when(F.col(col_name) == "disagree", 1)
          .otherwise(F.col(col_name))  # 保留NaN不做转换
    )

# 收集所有数值化后的列名,后续聚类用
num_cols = [f"{c}_num" for c in target_cols]

三、实现SPSS风格的Partial K-means(MISSING=PAIRWISE)

PySpark ML原生的K-means会直接丢弃带缺失值的样本,不支持SPSS的成对缺失处理逻辑。下面是自定义实现的核心步骤,完全对应MISSING=PAIRWISE——计算距离时只取样本和质心都非空的特征,更新质心时只取对应类中非空样本的均值:

import numpy as np
from pyspark.sql.types import ArrayType, DoubleType, IntegerType

# 1. 初始化8个质心:从样本里随机选8个非全空的样本作为初始质心(保证质心有有效数据)
initial_centroids = filtered_df.select(num_cols).na.drop(how="all").sample(False, 0.001, seed=42).limit(8).collect()
centroids = [np.array([row[c] for c in num_cols]) for row in initial_centroids]

# 2. 定义计算成对缺失欧氏距离的UDF:返回样本所属的聚类标记(1-8)
def pairwise_cluster_assign(sample_array, centroids):
    min_dist = float("inf")
    closest_cluster = -1
    for idx, centroid in enumerate(centroids):
        # 找出样本和质心都非空的特征索引
        valid_features = ~np.logical_or(np.isnan(sample_array), np.isnan(centroid))
        if np.sum(valid_features) == 0:
            continue  # 没有共同有效特征,跳过该质心
        # 计算有效特征的欧氏距离
        dist = np.linalg.norm(sample_array[valid_features] - centroid[valid_features])
        if dist < min_dist:
            min_dist = dist
            closest_cluster = idx + 1  # 聚类标记从1开始,对应1-8类
    return closest_cluster

distance_udf = F.udf(lambda x: pairwise_cluster_assign(np.array(x), centroids), IntegerType())

# 3. 迭代执行聚类(最大迭代次数设为10,可根据需求调整)
max_iterations = 10
for i in range(max_iterations):
    # 将数值列转为数组,方便UDF处理
    df_with_features = filtered_df.withColumn("features", F.array(*num_cols).cast(ArrayType(DoubleType())))
    # 给每个样本分配聚类标记
    df_with_clusters = df_with_features.withColumn("cluster_label", distance_udf(F.col("features")))
    
    # 更新质心:每个聚类的质心取对应类中各特征的非空均值
    new_centroids = []
    for cluster_id in range(1, 9):
        cluster_data = df_with_clusters.filter(F.col("cluster_label") == cluster_id)
        centroid_vals = []
        for col in num_cols:
            mean_val = cluster_data.select(F.mean(col)).first()[0]
            centroid_vals.append(mean_val if mean_val is not None else np.nan)
        new_centroids.append(np.array(centroid_vals))
    
    # 检查质心是否收敛(变化小于阈值则提前停止)
    if np.allclose(centroids, new_centroids, rtol=1e-3, atol=1e-3, equal_nan=True):
        print(f"聚类在第{i+1}次迭代收敛")
        break
    centroids = new_centroids

# 最终结果:保留原始列和聚类标记列
final_df = df_with_clusters.drop("features")

关键说明:

  • 初始质心选的是随机非全空样本,你也可以根据业务场景手动指定初始值
  • 距离计算用的是欧氏距离,和SPSS K-means默认一致
  • 对于完全没有共同有效特征的样本,当前代码返回-1,你可以根据需求改成丢弃或归为特定类

四、SPSS聚类代码对应转换说明

假设你的SPSS代码是这样:

CLUSTER
/VARIABLES=col1 col2 ... col8
/METHOD=KMEANS(CLUSTERS=8)
/MISSING=PAIRWISE
/PRINT=INITIAL FINAL
/SAVE CLUSTER.

对应的PySpark逻辑就是上面三步:先过滤缺失过多的样本,数值化分类特征,自定义实现带成对缺失处理的K-means,最后生成聚类标记列。


内容的提问来源于stack exchange,提问作者ar_mm18

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 10:54:51