UNPKG

hyphy-scope

Version:

Reusable Svelte components for HyPhy analysis visualization

230 lines (229 loc) 8.41 kB
import * as _ from "lodash-es"; export const MEME_COLORS = { 'Diversifying': '#e3243b', 'Neutral': '#444', 'Invariable': '#CCC' }; export function getMemeAttributes(resultsJson) { const hasResamples = _.get(resultsJson, ["MLE", "LRT"]) ? _.sample(_.get(resultsJson, ["MLE", "LRT"])["0"])?.length || 0 : 0; const hasSubstitutions = !!_.get(resultsJson, ["substitutions"]); const hasSiteLRT = !!_.find(_.get(resultsJson, ["MLE", "headers"]), (d) => d[0] === "Variation p"); const hasBackground = false; // Simplified for now const siteIndexPartitionCodon = Object.values(resultsJson['data partitions']) .map((d, k) => Object.values(d['coverage'][0]).map((site) => [+k + 1, site + 1])) .flat(); // Basic calculations const numberOfSequences = resultsJson.input["number of sequences"]; const numberOfSites = resultsJson.input["number of sites"]; const numberOfPartitions = resultsJson.input["partition count"]; const testedBranchCount = 5; // Default fallback const partitionSizes = Object.values(resultsJson['data partitions']) .map(d => d.coverage[0].length); return { testedBranchCount, hasResamples, hasSubstitutions, hasSiteLRT, hasBackground, siteIndexPartitionCodon, numberOfSequences, numberOfSites, numberOfPartitions, partitionSizes }; } export function getMemeCountSitesByPvalue(resultsJson, pvalueThreshold) { const countSites = Object.values(resultsJson["MLE"]["content"]) .map(d => d.filter(r => r[6] <= +pvalueThreshold).length) .reduce((sum, count) => sum + count, 0); return countSites; } export function getMemeSelectedBranchesPerSelectedSite(resultsJson, pvalueThreshold) { const countSites = getMemeCountSitesByPvalue(resultsJson, pvalueThreshold); const selectedBranchesPerSelectedSite = countSites ? (Object.values(resultsJson["MLE"]["content"]) .map(d => d.filter(r => r[6] <= +pvalueThreshold)) .map(d => d.reduce((sum, r) => sum + r[7], 0)) .reduce((sum, count) => sum + count, 0) / countSites).toFixed(2) : "N/A"; return selectedBranchesPerSelectedSite; } export function getMemeTileSpecs(resultsJson, pvalueThreshold) { const attrs = getMemeAttributes(resultsJson); const countSites = getMemeCountSitesByPvalue(resultsJson, pvalueThreshold); const selectedBranchesPerSelectedSite = getMemeSelectedBranchesPerSelectedSite(resultsJson, pvalueThreshold); // Compute count of sites with ω variation below p-value threshold const variationCount = attrs.hasSiteLRT ? Object.values(resultsJson['MLE']['content']) .map(d => d.filter(r => r[11] <= +pvalueThreshold).length) .reduce((sum, count) => sum + count, 0) : 0; return [ { number: resultsJson.input["number of sequences"], color: "asbestos", description: "sequences in the alignment", icon: "icon-options-vertical icons" }, { number: resultsJson.input["number of sites"], color: "asbestos", description: "codon sites in the alignment", icon: "icon-options icons" }, { number: resultsJson.input["partition count"], color: "asbestos", description: "partitions", icon: "icon-arrow-up icons" }, { number: attrs.testedBranchCount, color: "asbestos", description: "median branches/partition used for testing", icon: "icon-share icons" }, { number: attrs.hasResamples || "N/A", color: "asbestos", description: "parametric bootstrap replicates", icon: "icon-layers icons" }, { number: countSites, color: "midnight_blue", description: "sites subject to episodic diversifying selection", icon: "icon-plus icons" }, { number: selectedBranchesPerSelectedSite, color: "midnight_blue", description: "median branches with support for selection/selected site", icon: "icon-share icons" }, { number: variationCount, color: "midnight_blue", description: "sites with variable ω across branches", icon: "icon-energy icons" } ]; } export function getMemeSiteTableData(resultsJson, pvalueThreshold) { const attrs = getMemeAttributes(resultsJson); const siteInfo = []; let index = 0; const mleHeaders = _.map(resultsJson["MLE"]["headers"], (d) => { d[2] = d[0]; return d; }); _.each(resultsJson["data partitions"], (pinfo, partition) => { const mleData = resultsJson["MLE"]["content"][partition]; _.each(pinfo["coverage"][0], (ignore, i) => { const siteRecord = { 'Partition': attrs.siteIndexPartitionCodon[index][0], 'Codon': attrs.siteIndexPartitionCodon[index][1], 'alpha': mleData[i][0], 'beta-': mleData[i][1], 'p-': mleData[i][2], 'beta+': mleData[i][3], 'p+': mleData[i][4], 'p-value': mleData[i][6], 'Branches': mleData[i][7], 'class': "Neutral" }; // Assign additional headers _.each(mleHeaders, (info, idx) => { if (idx < 8) { siteRecord[info[2]] = mleData[i][idx]; } }); // Determine site class let siteClass = "Neutral"; if (mleData[i][0] === 0 && mleData[i][1] === 0 && mleData[i][3] === 0) { siteClass = "Invariable"; } else { if (mleData[i][6] <= +pvalueThreshold) { siteClass = "Diversifying"; } } siteRecord.class = siteClass; siteInfo.push(siteRecord); index++; }); }); const options = { 'Partition': 'Part.', 'Codon': 'Codon', 'class': 'Class', 'alpha': 'α', 'beta-': 'β-', 'p-': 'p-', 'beta+': 'β+', 'p+': 'p+', 'p-value': 'p-value', 'Branches': 'Branches' }; return [siteInfo, options, mleHeaders]; } export function getMemeOmegaPlot(record) { const ratio = (beta, alpha) => { if (alpha > 0) { return beta / alpha; } if (alpha === 0) { if (beta === 0) return 1; } return 100; }; const alpha = record[0]; const rateInfo = [ { 'value': ratio(record[1], alpha), 'weight': record[2] }, { 'value': ratio(record[3], alpha), 'weight': record[4] } ]; return rateInfo; } export function getMemePosteriorsPerBranchSite(resultsJson, rateClass = 1) { const results = []; let offset = 0; _.each(resultsJson["branch attributes"], (data, partition) => { let partitionSize = 0; _.each(data, (perBranch, branch) => { if (perBranch["Posterior prob omega class by site"]) { _.each(perBranch["Posterior prob omega class by site"][rateClass], (p, i) => { const prior = resultsJson['MLE']['content'][partition][i][4]; results.push({ 'Branch': branch, 'Codon': i + offset + 1, 'Posterior': p, 'ER': getMemeComputeER(prior, p) }); }); partitionSize = perBranch["Posterior prob omega class by site"][rateClass].length; } }); offset += partitionSize; }); return results; } export function getMemeComputeER(prior, posterior) { let priorOdds = prior < 1 ? prior / (1 - prior) : Infinity; let posteriorOdds = posterior < 1 ? posterior / (1 - posterior) : Infinity; if (posteriorOdds > 0) { return posteriorOdds / priorOdds; } else { if (priorOdds === 0) return 1; return Infinity; } }