Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion __tests__/unit/bfs.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import breadthFirstSearch from "../../packages/graph/src/bfs";
import { breadthFirstSearch } from "../../packages/graph/src";
import { Graph } from "@antv/graphlib";

const data = {
Expand Down
41 changes: 41 additions & 0 deletions __tests__/unit/cosine-similarity.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { cosineSimilarity } from "../../packages/graph/src";

describe('cosineSimilarity abnormal demo: ', () => {
it('item contains only zeros: ', () => {
const item = [0, 0, 0];
const targetTtem = [3, 1, 1];
const cosineSimilarityValue = cosineSimilarity(item, targetTtem);
expect(cosineSimilarityValue).toBe(0);
});
it('targetTtem contains only zeros: ', () => {
const item = [3, 5, 2];
const targetTtem = [0, 0, 0];
const cosineSimilarityValue = cosineSimilarity(item, targetTtem);
expect(cosineSimilarityValue).toBe(0);
});
it('item and targetTtem both contains only zeros: ', () => {
const item = [0, 0, 0];
const targetTtem = [0, 0, 0];
const cosineSimilarityValue = cosineSimilarity(item, targetTtem);
expect(cosineSimilarityValue).toBe(0);
});
});

describe('cosineSimilarity normal demo: ', () => {
it('demo similar: ', () => {
const item = [30, 0, 100];
const targetTtem = [32, 1, 120];
const cosineSimilarityValue = cosineSimilarity(item, targetTtem);
expect(cosineSimilarityValue).toBeGreaterThanOrEqual(0);
expect(cosineSimilarityValue).toBeLessThan(1);
expect(Number(cosineSimilarityValue.toFixed(3))).toBe(0.999);
});
it('demo dissimilar: ', () => {
const item = [10, 300, 2];
const targetTtem = [1, 2, 30];
const cosineSimilarityValue = cosineSimilarity(item, targetTtem);
expect(cosineSimilarityValue).toBeGreaterThanOrEqual(0);
expect(cosineSimilarityValue).toBeLessThan(1);
expect(Number(cosineSimilarityValue.toFixed(3))).toBe(0.074);
});
});
2 changes: 1 addition & 1 deletion __tests__/unit/dfs.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import depthFirstSearch from "../../packages/graph/src/dfs";
import { depthFirstSearch } from "../../packages/graph/src";
import { Graph } from "@antv/graphlib";


Expand Down
109 changes: 109 additions & 0 deletions __tests__/unit/nodes-cosine-similarity.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { nodesCosineSimilarity } from "../../packages/graph/src";
import propertiesGraphData from '../data/cluster-origin-properties-data.json';
import { NodeSimilarity } from "../../packages/graph/src/types";

describe('nodesCosineSimilarity abnormal demo', () => {
it('no properties demo: ', () => {
const nodes = [
{
id: 'node-0',
data: {},
},
{
id: 'node-1',
data: {},
},
{
id: 'node-2',
data: {},
},
{
id: 'node-3',
data: {},
}
];
const { allCosineSimilarity, similarNodes } = nodesCosineSimilarity(nodes as NodeSimilarity[], nodes[0]);
expect(allCosineSimilarity.length).toBe(3);
expect(similarNodes.length).toBe(3);
expect(allCosineSimilarity[0]).toBe(0);
expect(allCosineSimilarity[1]).toBe(0);
expect(allCosineSimilarity[2]).toBe(0);
});
});


describe('nodesCosineSimilarity normal demo', () => {
it('simple demo: ', () => {
const nodes = [
{
id: 'node-0',
data: {
amount: 10,
}
},
{
id: 'node-2',
data: {
amount: 100,
}
},
{
id: 'node-3',
data: {
amount: 1000,
}
},
{
id: 'node-4',
data: {
amount: 50,
}
}
];
const { allCosineSimilarity, similarNodes } = nodesCosineSimilarity(nodes as NodeSimilarity[], nodes[0], ['amount']);
expect(allCosineSimilarity.length).toBe(3);
expect(similarNodes.length).toBe(3);
allCosineSimilarity.forEach(data => {
expect(data).toBeGreaterThanOrEqual(0);
expect(data).toBeLessThanOrEqual(1);
})
});

it('complex demo: ', () => {
const { nodes } = propertiesGraphData;
const { allCosineSimilarity, similarNodes } = nodesCosineSimilarity(nodes as NodeSimilarity[], nodes[16]);
expect(allCosineSimilarity.length).toBe(16);
expect(similarNodes.length).toBe(16);
allCosineSimilarity.forEach(data => {
expect(data).toBeGreaterThanOrEqual(0);
expect(data).toBeLessThanOrEqual(1);
})
});


it('demo use involvedKeys: ', () => {
const involvedKeys = ['amount', 'wifi'];
const { nodes } = propertiesGraphData;
const { allCosineSimilarity, similarNodes } = nodesCosineSimilarity(nodes as NodeSimilarity[], nodes[16], involvedKeys);
expect(allCosineSimilarity.length).toBe(16);
expect(similarNodes.length).toBe(16);
allCosineSimilarity.forEach(data => {
expect(data).toBeGreaterThanOrEqual(0);
expect(data).toBeLessThanOrEqual(1);
})
expect(similarNodes[0].id).toBe('node-11');
});

it('demo use uninvolvedKeys: ', () => {
const uninvolvedKeys = ['amount'];
const { nodes } = propertiesGraphData;
const { allCosineSimilarity, similarNodes } = nodesCosineSimilarity(nodes as NodeSimilarity[], nodes[16], [], uninvolvedKeys);
expect(allCosineSimilarity.length).toBe(16);
expect(similarNodes.length).toBe(16);
allCosineSimilarity.forEach(data => {
expect(data).toBeGreaterThanOrEqual(0);
expect(data).toBeLessThanOrEqual(1);
})
expect(similarNodes[0].id).toBe('node-11');
});
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"build:ci": "pnpm -r run build:ci",
"prepare": "husky install",
"test": "jest",
"test_one": "jest ./__tests__/unit/dfs.spec.ts",
"test_one": "jest ./__tests__/unit/nodes-cosine-similarity.spec.ts",
"coverage": "jest --coverage",
"build:site": "vite build",
"deploy": "gh-pages -d site/dist",
Expand Down
4 changes: 1 addition & 3 deletions packages/graph/src/bfs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Performs breadth-first search (BFS) traversal on a graph.
@param startNodeId - The ID of the starting node for BFS.
@param originalCallbacks - Optional object containing callback functions for BFS.
*/
const breadthFirstSearch = (
export const breadthFirstSearch = (
graph: Graph,
startNodeId: NodeID,
originalCallbacks?: IAlgorithmCallbacks,
Expand Down Expand Up @@ -65,5 +65,3 @@ const breadthFirstSearch = (
previousNodeId = currentNodeId;
}
};

export default breadthFirstSearch;
27 changes: 27 additions & 0 deletions packages/graph/src/cosine-similarity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Vector } from "./vector";

/**
Calculates the cosine similarity
@param item - The element.
@param targetItem - The target element.
@returns The cosine similarity between the item and the targetItem.
*/
export const cosineSimilarity = (
item: number[],
targetItem: number[],
): number => {
// Vector of the target element
const targetItemVector = new Vector(targetItem);
// Norm of the target element vector
const targetNodeNorm2 = targetItemVector.norm2();
// Vector of the item
const itemVector = new Vector(item);
// Norm of the item vector
const itemNorm2 = itemVector.norm2();
// Calculate the dot product of the item vector and the target element vector
const dot = targetItemVector.dot(itemVector);
const norm2Product = targetNodeNorm2 * itemNorm2;
// Calculate the cosine similarity between the item vector and the target element vector
const cosineSimilarity = norm2Product ? dot / norm2Product : 0;
return cosineSimilarity;
}
2 changes: 1 addition & 1 deletion packages/graph/src/dfs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function depthFirstSearchRecursive(
});
}

export default function depthFirstSearch(
export function depthFirstSearch(
graph: Graph,
startNodeId: NodeID,
originalCallbacks?: IAlgorithmCallbacks,
Expand Down
4 changes: 3 additions & 1 deletion packages/graph/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ export * from "./iLouvain";
export * from "./k-core";
export * from "./floydWarshall";
export * from "./bfs";
export * from "./dfs";
export * from "./dfs";
export * from "./cosine-similarity"
export * from "./nodes-cosine-similarity";
43 changes: 43 additions & 0 deletions packages/graph/src/nodes-cosine-similarity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { clone } from '@antv/util';
import { getAllProperties, oneHot } from './utils';
import { NodeSimilarity } from './types';
import { cosineSimilarity } from './cosine-similarity';

/**
Calculates the cosine similarity based on node attributes using the nodes-cosine-similarity algorithm.
This algorithm is used to find similar nodes based on a seed node in a graph.
@param nodes - The data of graph nodes.
@param seedNode - The seed node for similarity calculation.
@param involvedKeys - The collection of keys that are involved in the calculation.
@param uninvolvedKeys - The collection of keys that are not involved in the calculation.
@returns An array of nodes that are similar to the seed node based on cosine similarity.
*/
export const nodesCosineSimilarity = (
nodes: NodeSimilarity[] = [],
seedNode: NodeSimilarity,
involvedKeys: string[] = [],
uninvolvedKeys: string[] = [],
): {
allCosineSimilarity: number[],
similarNodes: NodeSimilarity[],
} => {
const similarNodes = clone(nodes.filter(node => node.id !== seedNode.id));
const seedNodeIndex = nodes.findIndex(node => node.id === seedNode.id);
// Collection of all node properties
const properties = getAllProperties(nodes);
// One-hot feature vectors for all node properties
const allPropertiesWeight = oneHot(properties, involvedKeys, uninvolvedKeys) as number[][];
// Seed node properties
const seedNodeProperties = allPropertiesWeight[seedNodeIndex];
const allCosineSimilarity: number[] = [];
similarNodes.forEach((node: NodeSimilarity, index: number) => {
const nodeProperties = allPropertiesWeight[index];
// Calculate the cosine similarity between node vector and seed node vector
const cosineSimilarityValue = cosineSimilarity(nodeProperties, seedNodeProperties);
allCosineSimilarity.push(cosineSimilarityValue);
node.data.cosineSimilarity = cosineSimilarityValue;
});
// Sort the returned nodes according to cosine similarity
similarNodes.sort((a: NodeSimilarity, b: NodeSimilarity) => b.data.cosineSimilarity - a.data.cosineSimilarity);
return { allCosineSimilarity, similarNodes };
}
8 changes: 7 additions & 1 deletion packages/graph/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,10 @@ export interface IAlgorithmCallbacks {
allowTraversal?: (param: { previous?: NodeID; current?: NodeID; next: NodeID }) => boolean;
}

export type NodeID = string | number;
export type NodeID = string | number;

export type NodeSimilarity = Node<PlainObject> & {
data: {
cosineSimilarity?: number;
}
}
4 changes: 2 additions & 2 deletions packages/graph/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const oneHot = (dataList: PlainObject[], involvedKeys?: string[], uninvol
// 获取所有的属性/特征值
const allValue = Object.values(allKeyValueMap);
// 是否所有属性/特征的值都是数值型
const isAllNumber = allValue.every((value) => value.every((item) => (typeof(item) === 'number')));
const isAllNumber = allValue.every((value) => value.every((item) => (typeof (item) === 'number')));

// 对数据进行one-hot编码
dataList.forEach((data, index) => {
Expand All @@ -65,7 +65,7 @@ export const oneHot = (dataList: PlainObject[], involvedKeys?: string[], uninvol
subCode.push(keyValue);
} else {
// 进行one-hot编码
for(let i = 0; i < allKeyValue.length; i++) {
for (let i = 0; i < allKeyValue.length; i++) {
if (i === valueIndex) {
subCode.push(1);
} else {
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"allowJs": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
}
}