clusterkw
Version:
A package for clustering keywords using OpenAI embeddings
90 lines (89 loc) • 3.33 kB
JavaScript
;
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;
}
}