
在数据分析实践中,我们常会使用多种聚类算法对同一批数据项进行分析,每种算法可能基于不同的特征或参数生成各自的聚类结果。核心挑战在于,如何将这些独立的聚类结果进行整合,特别是当不同算法产生的集群之间存在重叠(即共享至少一个数据项)时,需要将这些相互关联的集群合并成一个更大的、统一的集群。这种合并必须是传递性的:如果集群A与B重叠,B与C重叠,那么A、B、C都应被合并到同一个最终集群中。
为了确保最终合并的集群有一个确定的标识,我们通常会选择一个确定性规则来生成新的集群键,例如,将合并后所有数据项中的最大ID作为新集群的键。
示例数据与预期输出:
假设我们有以下8个数据项(ID从1到8):
| id |
|---|
| 1 |
| 2 |
| ... |
| 8 |
以下是两个不同聚类算法的输出:
聚类结果 1
| cluster_key | id |
|---|---|
| 3 | 1 |
| 3 | 2 |
| 3 | 3 |
| 6 | 4 |
| 6 | 5 |
| 6 | 6 |
| 8 | 7 |
| 8 | 8 |
聚类结果 2
| cluster_key | id |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 5 | 3 |
| 5 | 4 |
| 5 | 5 |
| 6 | 6 |
| 8 | 7 |
| 8 | 8 |
根据“任何共享一个ID的集群都应合并,且合并具有传递性”的规则,我们期望的输出如下:
预期合并输出
| cluster_key | id |
|---|---|
| 6 | 1 |
| 6 | 2 |
| 6 | 3 |
| 6 | 4 |
| 6 | 5 |
| 6 | 6 |
| 8 | 7 |
| 8 | 8 |
逻辑解析:
上述问题在图论中被称为连通分量(Connected Components)问题,或者更精确地说,是计算特定关系下的传递闭包(Transitive Closure)。
我们可以将每个原始聚类结果中的一个集群视为图中的一个节点。如果两个集群(无论它们来自哪个聚类算法)共享至少一个数据项,那么它们之间就存在一条边。我们的目标是找出这个图中的所有连通分量,即所有相互连接的节点集合。
这种方法可以被描述为基于“非空重叠”的“单链接聚类(Single Link Clustering)”的一种变体,但其应用对象是已有的集群而非原始数据点。它与通常意义上的“共识聚类(Consensus Clustering)”有所不同,共识聚类旨在寻找一个折衷或改进的聚类结果,而这里我们寻求的是通过重叠关系进行完全合并。
尽管SQL在处理图的传递闭包(尤其是连通分量)方面存在固有限制,特别是对于大型或深度图,递归CTE(Common Table Expression)可能面临性能和深度限制。但我们可以利用SQL来完成数据准备和识别直接重叠关系,然后结合外部工具来处理复杂的连通分量计算。
首先,我们需要将所有聚类结果统一到一个表中,并为每个原始集群分配一个唯一的标识符,以便后续处理。
-- 创建示例数据表
CREATE TABLE Clustering1 (cluster_key INT, id INT);
INSERT INTO Clustering1 VALUES (3,1), (3,2), (3,3), (6,4), (6,5), (6,6), (8,7), (8,8);
CREATE TABLE Clustering2 (cluster_key INT, id INT);
INSERT INTO Clustering2 VALUES (1,1), (2,2), (5,3), (5,4), (5,5), (6,6), (8,7), (8,8);
-- 步骤1:标准化集群数据
-- 为每个原始集群分配一个唯一的 original_cluster_id
-- 这里我们假设 original_cluster_id 可以通过 (source_table, cluster_key) 唯一标识
-- 或者更简单地,直接生成一个序列号
WITH AllClustersRaw AS (
SELECT 'C1' AS source, cluster_key, id FROM Clustering1
UNION ALL
SELECT 'C2' AS source, cluster_key, id FROM Clustering2
),
ClusteredItems AS (
SELECT
ROW_NUMBER() OVER (ORDER BY source, cluster_key) AS original_cluster_id,
source,
cluster_key,
id
FROM (
SELECT DISTINCT source, cluster_key, id FROM AllClustersRaw
) AS distinct_clusters_and_items
)
SELECT * FROM ClusteredItems;ClusteredItems 表的示例输出(original_cluster_id 可能因具体数据库和数据而异):
| original_cluster_id | source | cluster_key | id |
|---|---|---|---|
| 1 | C1 | 3 | 1 |
| 1 | C1 | 3 | 2 |
| 1 | C1 | 3 | 3 |
| 2 | C1 | 6 | 4 |
| ... | ... | ... | ... |
| 5 | C2 | 1 | 1 |
| 6 | C2 | 2 | 2 |
| ... | ... | ... | ... |
接下来,我们需要找出哪些original_cluster_id之间存在直接重叠(即共享至少一个id)。这将为我们构建图的边列表。
-- 步骤2:识别直接连接的集群对
WITH AllClustersRaw AS (
SELECT 'C1' AS source, cluster_key, id FROM Clustering1
UNION ALL
SELECT 'C2' AS source, cluster_key, id FROM Clustering2
),
ClusteredItems AS (
SELECT
DENSE_RANK() OVER (ORDER BY source, cluster_key) AS original_cluster_id,
source,
cluster_key,
id
FROM (
SELECT DISTINCT source, cluster_key, id FROM AllClustersRaw
) AS distinct_clusters_and_items
)
SELECT DISTINCT
c1.original_cluster_id AS cluster_a,
c2.original_cluster_id AS cluster_b
FROM ClusteredItems c1
JOIN ClusteredItems c2 ON c1.id = c2.id AND c1.original_cluster_id < c2.original_cluster_id;输出示例(代表了图的边):
| cluster_a | cluster_b |
|---|---|
| 1 | 5 |
| 1 | 6 |
| 1 | 7 |
| 2 | 7 |
| 2 | 8 |
| 3 | 9 |
| 4 | 9 |
(注意:这里的original_cluster_id是基于DENSE_RANK生成的,与前面的ROW_NUMBER可能不同,但原理一致。例如,如果C1-3的original_cluster_id是1,C2-1是5,那么1,5就是一条边。)
在标准SQL中,直接计算任意图的连通分量(即传递闭包)是复杂的。虽然一些数据库支持递归CTE,可以用于有限深度的路径查找,但将其应用于通用连通分量计算,特别是要为每个分量分配一个统一的merged_component_id,往往效率低下且难以维护。
SQL模拟思路(迭代更新 - 概念性): 对于小型数据集,可以尝试通过迭代更新来模拟Union-Find算法。这需要一个临时表来存储每个original_cluster_id的当前“根”组件ID,并通过多次UPDATE语句来传播这些根ID,直到没有更多的更新发生。
-- 概念性SQL迭代更新(不推荐用于生产环境,尤其大型数据集) -- 假设我们有一个临时表 ComponentRoots (original_cluster_id INT, root_id INT) -- 初始化:每个集群是自己的根 -- INSERT INTO ComponentRoots SELECT original_cluster_id, original_cluster_id FROM (SELECT DISTINCT original_cluster_id FROM ClusteredItems); -- 迭代更新: -- WHILE EXISTS (SELECT 1 FROM ComponentRoots cr1 JOIN ComponentRoots cr2 ON ... WHERE cr1.root_id != cr2.root_id) -- BEGIN -- UPDATE ComponentRoots SET root_id = LEAST(cr1.root_id, cr2.root_id) -- FROM ComponentRoots cr1 JOIN ComponentRoots cr2 ON ... (using the overlap relationships) -- WHERE cr1.root_id != cr2.root_id; -- END;
这种方法通常不是纯粹的SQL查询,而是需要在应用层进行循环控制的批处理操作。
推荐方案: 将步骤3.2中生成的重叠关系(边列表)导出,利用外部编程语言(如Python)的图库或Union-Find算法进行高效计算。这是处理连通分量最健壮和性能最佳的方法。
Python及其丰富的库生态系统,如networkx(图论库)或通过实现Union-Find数据结构,能够高效地解决连通分量问题。对于大规模数据,PySpark及其GraphFrames库是理想选择。
Union-Find是一种用于处理不相交集合的算法,非常适合解决连通分量问题。
Union-Find 类实现:
class UnionFind:
def __init__(self, elements):
self.parent = {e: e for e in elements}
self.rank = {e: 0 for e in elements} # Optional: for optimization (union by rank)
def find(self, i):以上就是多算法聚类结果的合并策略与SQL实现:基于连通分量的传递闭包方法的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号