phylotree
Version:
A JavaScript library for developing applications and interactive visualizations involving [phylogenetic trees](https://en.wikipedia.org/wiki/Phylogenetic_tree), written as an extension of the [D3](http://d3js.org) [hierarchy layout](https://github.com/d3/
197 lines (166 loc) • 5.27 kB
JavaScript
import * as _ from "lodash";
import { default as phylotree } from "./main";
function remove(i, D) {
let dNew = [];
for (let j of _.range(D.length)) {
if (j != i) {
let dNewRow = [];
for (let k of _.range(D[j].length)) {
if (k != i) {
dNewRow.push(D[j][k]);
}
}
dNew.push(dNewRow);
}
}
return dNew;
}
function getDPrime(distanceMatrix, totalDistances, N) {
let DPrime = _.chunk(_.fill(Array(N * N), 0), N);
for (let i of _.range(N)) {
for (let j of _.range(_.parseInt(i) + 1, N)) {
DPrime[i][j] =
(N - 2) * distanceMatrix[i][j] - totalDistances[i] - totalDistances[j];
DPrime[j][i] = DPrime[i][j];
}
}
return DPrime;
}
function ijMinDPrime(dPrime, N) {
let i = -1;
let j = -1;
let minD = Infinity;
for (let ii of _.range(N)) {
for (let jj of _.range(i, N)) {
if (dPrime[ii][jj] < minD) {
i = ii;
j = jj;
minD = dPrime[i][j];
}
}
}
return [i, j, minD];
}
function createDelta(totalDistances, N) {
let deltaMatrix = _.chunk(Array(N * N), N);
for (let i of _.range(N)) {
for (let j of _.range(parseInt(i) + 1, N)) {
deltaMatrix[i][j] = (totalDistances[i] - totalDistances[j]) / (N - 2);
deltaMatrix[j][i] = deltaMatrix[i][j];
}
}
return deltaMatrix;
}
export function getDistanceMatrix(seqs) {
// Gaps are masked
let initKey = _.keys(seqs)[0];
let seqLength = seqs[initKey].length;
return _.mapValues(seqs, (seq) =>
_.map(seqs, (seq2) =>
_.sum(
_.map(
_.range(seqLength),
(i) => seq[i] != seq2[i] && seq[i] != "-" && seq2[i] != "-"
)
)
)
);
}
function getTotalDistances(distanceMatrix) {
return _.map(distanceMatrix, _.sum);
}
/**
* Create a neighbor joining tree from a distance matrix
* See test/neighbor-join-test.js for a working example
*
* @param {Array} distanceMatrixArr The NxN distance matrix.
* const D = [
* [0, 5, 9, 9, 8],
* [5, 0, 10, 10, 9],
* [9, 10, 0, 8, 7],
* [9, 10, 8, 0, 3],
* [8, 9, 7, 3, 0]
* ];
*
* @param {Number} n The dimension of the distanceMatrixArr.
* @param {Array} nodeList The names of each row in the distanceMatrix
* @returns The neighbor joining new tree.
*/
export default function neighborJoining(distanceMatrixArr, n, nodeList) {
if (n <= 2) {
let tree = new phylotree("");
//T.link(nodeList[0],nodeList[1],D[0][1])
let newNode = tree.getNodes();
// Get root
let distance = distanceMatrixArr[0][1] / 2;
let nodeA = tree.createNode(nodeList[0], [null, [distance]]);
let nodeB = tree.createNode(nodeList[1], [null, [distance]]);
//// Add the children to the newly created node
tree.addChild(newNode, nodeA);
tree.addChild(newNode, nodeB);
return tree;
} else {
let N = n;
let totalDistances = getTotalDistances(distanceMatrixArr);
let dPrime = getDPrime(distanceMatrixArr, totalDistances, N);
let [i, j, minD] = ijMinDPrime(dPrime, N);
let deltaMatrix = createDelta(totalDistances, N);
let limbLengthI = (distanceMatrixArr[i][j] + deltaMatrix[i][j]) / 2;
let limbLengthJ = (distanceMatrixArr[i][j] - deltaMatrix[i][j]) / 2;
let newRow = _.concat(
0,
_.filter(
_.map(_.range(n), (k) => {
if (k != i && k != j) {
return (
0.5 *
(distanceMatrixArr[k][i] +
distanceMatrixArr[k][j] -
distanceMatrixArr[i][j])
);
}
}),
_.isNumber
)
);
let nodeI = nodeList[i];
let nodeJ = nodeList[j];
// Get all nodes of type InternalNode{x} and increment number.
// If there are none, start with InternalNode0
let m = "InternalNode0";
let internalNodes = _.filter(nodeList, (x) =>
_.includes(x, "InternalNode")
);
if (internalNodes.length) {
let highestNum = _.max(
_.map(internalNodes, (label) => _.split(label, "InternalNode")[1])
);
m = "InternalNode" + ++highestNum;
}
nodeList.unshift(m);
distanceMatrixArr = remove(_.max([i, j]), distanceMatrixArr);
distanceMatrixArr = remove(_.min([i, j]), distanceMatrixArr);
distanceMatrixArr.unshift(newRow);
_.each(_.range(1, n - 1), (l) => distanceMatrixArr[l].unshift(newRow[l]));
// Remove from nodeList
_.remove(nodeList, (n) => n == nodeI || n == nodeJ);
let tree = neighborJoining(distanceMatrixArr, N - 1, nodeList);
let treeNodeI = tree.createNode(nodeI, [null, [limbLengthI]]);
let treeNodeJ = tree.createNode(nodeJ, [null, [limbLengthJ]]);
// If the node doesn't exist, create. Otherwise, reassign the length
if (tree.getNodeByName(m)) {
let internalNode = tree.getNodeByName(m);
tree.addChild(internalNode, treeNodeI);
tree.addChild(internalNode, treeNodeJ);
} else {
let newNode = tree.createNode(m, [null, [0]]);
tree.addChild(tree.getNodes(), newNode);
// Add the children to the newly created node
tree.addChild(newNode, treeNodeI);
tree.addChild(newNode, treeNodeJ);
}
// Set negative to 0 and add distance to other limblength
return tree;
}
return null;
}