UNPKG

auspice

Version:

Web app for visualizing pathogen evolution

76 lines (70 loc) 2.91 kB
import { sum } from "d3-array"; import { formatDivergence, guessAreMutationsPerSite} from "./helpers"; import { NODE_VISIBLE } from "../../../util/globals"; import { PhyloNode, PhyloTreeType, Regression } from "./types"; import { ScaleContinuousNumeric } from "d3-scale"; /** * this function calculates a regression between * the x and y values of terminal nodes which are also visible. * The regression is forced to pass through nodes[0]. */ function calculateRegressionThroughRoot(nodes: PhyloNode[]): Regression { const terminalNodes = nodes.filter((d) => !d.n.hasChildren && d.visibility === NODE_VISIBLE); const nTips = terminalNodes.length; if (nTips===0) { return {slope: undefined, intercept: undefined, r2: undefined}; } const offset = nodes[0].x; const XY = sum( terminalNodes.map((d) => (d.y) * (d.x - offset)) ) / nTips; const secondMomentTime = sum( terminalNodes .map((d) => (d.x - offset) * (d.x - offset)) ) / nTips; const slope = XY / secondMomentTime; const intercept = -offset * slope; return {slope, intercept, r2: undefined}; } /** * Calculate regression through visible terminal nodes which have both x & y values * set. These values must be numeric. */ function calculateRegressionWithFreeIntercept(nodes: PhyloNode[]): Regression { const terminalNodesWithXY = nodes.filter( (d) => (!d.n.hasChildren) && d.x!==undefined && d.y!==undefined && d.visibility === NODE_VISIBLE ); const nTips = terminalNodesWithXY.length; if (nTips===0) { return {slope: undefined, intercept: undefined, r2: undefined}; } const meanX = sum(terminalNodesWithXY.map((d) => d.x))/nTips; const meanY = sum(terminalNodesWithXY.map((d) => d.y))/nTips; const slope = sum(terminalNodesWithXY.map((d) => (d.x-meanX)*(d.y-meanY))) / sum(terminalNodesWithXY.map((d) => (d.x-meanX)**2)); const intercept = meanY - slope*meanX; const r2 = 1 - sum(terminalNodesWithXY.map((d) => (d.y - (intercept + slope*d.x))**2)) / sum(terminalNodesWithXY.map((d) => (d.y - meanY)**2)); return {slope, intercept, r2}; } /** sets this.regression */ export function calculateRegression(this: PhyloTreeType): void { if (this.layout==="clock") { this.regression = calculateRegressionThroughRoot(this.nodes); } else { this.regression = calculateRegressionWithFreeIntercept(this.nodes); } } export function makeRegressionText( regression: Regression, layout: string, yScale: ScaleContinuousNumeric<number, number>, ): string { if (layout==="clock") { if (guessAreMutationsPerSite(yScale)) { return `rate estimate: ${regression.slope.toExponential(2)} subs per site per year`; } return `rate estimate: ${formatDivergence(regression.slope)} subs per year`; } return `intercept = ${regression.intercept.toPrecision(3)}, slope = ${regression.slope.toPrecision(3)}, R^2 = ${regression.r2.toPrecision(3)}`; }