Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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 '.';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import { cosineSimilarity } from './cosine-similarity';

这样引入吧。从 index 容易出现循环引用


/**
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.cosineSimilarity = cosineSimilarityValue;
});
// Sort the returned nodes according to cosine similarity
similarNodes.sort((a: NodeSimilarity, b: NodeSimilarity) => b.cosineSimilarity - a.cosineSimilarity);
return { allCosineSimilarity, similarNodes };
}
5 changes: 4 additions & 1 deletion packages/graph/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,7 @@ export interface IAlgorithmCallbacks {
allowTraversal?: (param: { previous?: NodeID; current?: NodeID; next: NodeID }) => boolean;
}

export type NodeID = string | number;
export type NodeID = string | number;
export interface NodeSimilarity extends Node<{ [key: string]: any }> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

好像应该 extends 的是 NodeData。Node 结构是 { id: ID, data: NodeData }。这些属性应该放在 NodeData 里面

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,
}
}