UNPKG

clusterkw

Version:

A package for clustering keywords using OpenAI embeddings

90 lines (89 loc) 3.33 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.hierarchicalClustering = hierarchicalClustering; /** * Hierarchical clustering algorithm (agglomerative) * @param keywords Array of keywords to cluster * @param distances Matrix of distances between keywords * @param options Configuration options * @returns Array of clusters */ function hierarchicalClustering(keywords, distances, options) { const n = keywords.length; // Default options const minClusterSize = options.minClusterSize || 2; const distanceThreshold = options.distanceThreshold || 0.3; const linkage = options.linkage || 'average'; // Initialize each keyword as its own cluster let clusters = keywords.map((keyword, index) => ({ items: [keyword], indices: [index] })); // Clone the distances matrix to avoid modifying the original const distanceMatrix = distances.map(row => [...row]); // Main loop: merge clusters until we can't merge any more while (clusters.length > 1) { // Find the two closest clusters let minDistance = Infinity; let minI = -1; let minJ = -1; for (let i = 0; i < clusters.length; i++) { for (let j = i + 1; j < clusters.length; j++) { const distance = getClusterDistance(clusters[i].indices, clusters[j].indices, distanceMatrix, linkage); if (distance < minDistance) { minDistance = distance; minI = i; minJ = j; } } } // If the minimum distance is above the threshold, stop merging if (minDistance > distanceThreshold) { break; } // Merge the two closest clusters const merged = { items: [...clusters[minI].items, ...clusters[minJ].items], indices: [...clusters[minI].indices, ...clusters[minJ].indices] }; // Remove the two clusters and add the merged one clusters = [ ...clusters.slice(0, minI), ...clusters.slice(minI + 1, minJ), ...clusters.slice(minJ + 1), merged ]; } // Filter out clusters that are too small return clusters .filter(cluster => cluster.items.length >= minClusterSize) .map(cluster => ({ items: cluster.items })); } /** * Calculate distance between two clusters based on linkage method */ function getClusterDistance(indicesA, indicesB, distances, linkage) { const allDistances = []; // Collect all pairwise distances between points in the two clusters for (const i of indicesA) { for (const j of indicesB) { allDistances.push(distances[i][j]); } } if (allDistances.length === 0) { return Infinity; } // Apply the linkage method switch (linkage) { case 'single': // Minimum distance between any two points return Math.min(...allDistances); case 'complete': // Maximum distance between any two points return Math.max(...allDistances); case 'average': default: // Average distance between all points return allDistances.reduce((sum, d) => sum + d, 0) / allDistances.length; } }