morpheus.MarkerSelection = function() { }; /** * @private */ morpheus.MarkerSelection.Functions = [ morpheus.FisherExact, morpheus.FoldChange, morpheus.SignalToNoise, morpheus.createSignalToNoiseAdjust(), morpheus.TTest ]; morpheus.MarkerSelection.Functions.fromString = function(s) { for (var i = 0; i < morpheus.MarkerSelection.Functions.length; i++) { if (morpheus.MarkerSelection.Functions[i].toString() === s) { return morpheus.MarkerSelection.Functions[i]; } } throw s + ' not found'; }; morpheus.MarkerSelection.execute = function(dataset, input) { var aIndices = []; var bIndices = []; for (var i = 0; i < input.numClassA; i++) { aIndices[i] = i; } for (var i = input.numClassA; i < dataset.getColumnCount(); i++) { bIndices[i] = i; } var f = morpheus.MarkerSelection.Functions.fromString(input.metric); var permutations = new morpheus.PermutationPValues(dataset, aIndices, bIndices, input.npermutations, f); return { rowSpecificPValues : permutations.rowSpecificPValues, k : permutations.k, fdr : permutations.fdr, scores : permutations.scores }; }; morpheus.MarkerSelection.prototype = { toString : function() { return 'Marker Selection'; }, init : function(project, form) { var _this = this; var updateAB = function(fieldNames) { var ids = []; if (fieldNames != null) { var vectors = morpheus.MetadataUtil.getVectors(project .getFullDataset().getColumnMetadata(), fieldNames); var idToIndices = morpheus.VectorUtil .createValuesToIndicesMap(vectors); idToIndices.forEach(function(indices, id) { ids.push(id); }); } ids.sort(); form.setOptions('class_a', ids); form.setOptions('class_b', ids); }; var $field = form.$form.find('[name=field]'); $field.on('change', function(e) { updateAB($(this).val()); }); if ($field[0].options.length > 0) { $field.val($field[0].options[0].value); } updateAB($field.val()); var $metric = form.$form.find('[name=metric]'); $metric.on('change', function(e) { var isFishy = $(this).val() === 'Fisher Exact Test'; form.setVisible('grouping_value', isFishy); form.setVisible('permutations', !isFishy); form.setVisible('number_of_markers', !isFishy); }); form.setVisible('grouping_value', false); }, gui : function(project) { var dataset = project.getSortedFilteredDataset(); var fields = morpheus.MetadataUtil.getMetadataNames(dataset .getColumnMetadata()); return [ { name : 'metric', options : morpheus.MarkerSelection.Functions, value : morpheus.SignalToNoise.toString(), type : 'select', help : '' }, { name : 'grouping_value', value : '1', help : 'Class values are categorized into two groups based on whether dataset values are greater than or equal to this value', }, { name : 'field', options : fields, type : 'select', multiple : true }, { name : 'class_a', title : 'Class A', options : [], value : '', type : 'checkbox-list', multiple : true }, { name : 'class_b', title : 'Class B', options : [], value : '', type : 'checkbox-list', multiple : true }, { name : 'number_of_markers', value : '100', type : 'text', help : 'The initial number of markers to show in each direction. Click to change.' }, { name : 'permutations', value : '0', type : 'text' } ]; }, execute : function(options) { var project = options.project; // classA and classB are arrays of identifiers if run via user // interface. If run via JSON, will be string arrays var classA = options.input.class_a; for (var i = 0; i < classA.length; i++) { var val = classA[i]; if (!(val instanceof morpheus.Identifier)) { classA[i] = new morpheus.Identifier( morpheus.Util.isArray(val) ? val : [ val ]); } } var classB = options.input.class_b; for (var i = 0; i < classB.length; i++) { var val = classB[i]; if (!(val instanceof morpheus.Identifier)) { classB[i] = new morpheus.Identifier( morpheus.Util.isArray(val) ? val : [ val ]); } } var npermutations = parseInt(options.input.permutations); var fieldNames = options.input.field; if (!morpheus.Util.isArray(fieldNames)) { fieldNames = [ fieldNames ]; } var dataset = project.getSortedFilteredDataset(); var vectors = morpheus.MetadataUtil.getVectors(dataset .getColumnMetadata(), fieldNames); var idToIndices = morpheus.VectorUtil.createValuesToIndicesMap(vectors); var f = morpheus.MarkerSelection.Functions .fromString(options.input.metric); var aIndices = []; var bIndices = []; classA.forEach(function(id) { var indices = idToIndices.get(id); if (indices === undefined) { throw new Error(id + ' not found in ' + idToIndices.keys()); } aIndices = aIndices.concat(indices); }); classB.forEach(function(id) { var indices = idToIndices.get(id); if (indices === undefined) { throw new Error(id + ' not found in ' + idToIndices.keys()); } bIndices = bIndices.concat(indices); }); if (aIndices.length === 0 && bIndices.length === 0) { throw 'No samples in class A and class B'; } if (aIndices.length === 0) { throw 'No samples in class A'; } if (bIndices.length === 0) { throw 'No samples in class B'; } var classASet = {}; for (var i = 0; i < aIndices.length; i++) { classASet[aIndices[i]] = true; } for (var i = 0; i < bIndices.length; i++) { if (classASet[bIndices[i]]) { throw 'The sample was found in class A and class B'; } } var isFishy = f.toString() === morpheus.FisherExact.toString(); if (aIndices.length === 1 || bIndices.length === 1 && !f instanceof morpheus.FisherExact) { f = morpheus.FoldChange; } var list1 = new morpheus.DatasetRowView(new morpheus.SlicedDatasetView( dataset, null, aIndices)); var list2 = new morpheus.DatasetRowView(new morpheus.SlicedDatasetView( dataset, null, bIndices)); // remove // other // marker // selection // fields var markerSelectionFields = morpheus.MarkerSelection.Functions.map( function(f) { return f.toString(); }).concat([ 'odds_ratio', 'FDR(BH)', 'p_value' ]); markerSelectionFields.forEach(function(name) { var index = morpheus.MetadataUtil.indexOf(dataset.getRowMetadata(), name); if (index !== -1) { dataset.getRowMetadata().remove(index); options.controller.removeTrack(name, false); } }); var v = dataset.getRowMetadata().add(f.toString()); var vectors = [ v ]; var comparisonVector = dataset.getColumnMetadata().add('Comparison'); for (var i = 0; i < aIndices.length; i++) { comparisonVector.setValue(aIndices[i], 'A'); } for (var i = 0; i < bIndices.length; i++) { comparisonVector.setValue(bIndices[i], 'B'); } function done() { if (project.getRowFilter().getFilters().length > 0) { project.getRowFilter().setAnd(true, true); } var rowFilters = project.getRowFilter().getFilters(); // remove existing top n filters for (var i = 0; i < rowFilters.length; i++) { if (rowFilters[i] instanceof morpheus.TopNFilter) { project.getRowFilter().remove(i, true); i--; } } if (!isFishy) { project.getRowFilter().add( new morpheus.TopNFilter( parseInt(options.input.number_of_markers), morpheus.TopNFilter.TOP_BOTTOM, vectors[0] .getName()), true); } project.setRowFilter(project.getRowFilter(), true); project.setRowSortKeys([ new morpheus.SortKey(vectors[0].getName(), isFishy ? morpheus.SortKey.SortOrder.ASCENDING : morpheus.SortKey.SortOrder.DESCENDING) ], true); project.setColumnSortKeys([ new morpheus.SortKey(comparisonVector .getName(), morpheus.SortKey.SortOrder.ASCENDING) ], true); project.trigger('trackChanged', { vectors : vectors, render : vectors.map(function() { return 'text'; }), columns : false }); project.trigger('trackChanged', { vectors : [ comparisonVector ], render : [ 'color' ], columns : true }); } if (isFishy) { var groupingValue = parseFloat(options.input.grouping_value); var oddsRatioVector = dataset.getRowMetadata().add('odds_ratio'); var fdrVector = dataset.getRowMetadata().add('FDR(BH)'); var contingencyTableVector = dataset.getRowMetadata().add( 'contingency_table'); var pvalues = []; for (var i = 0, size = dataset.getRowCount(); i < size; i++) { var abcd = morpheus.createContingencyTable(list1.setIndex(i), list2.setIndex(i), groupingValue); contingencyTableVector.setValue(i, '[[' + abcd[0] + ', ' + abcd[1] + '], [' + abcd[2] + ', ' + abcd[3] + ']]'); var ratio = (abcd[0] * abcd[3]) / (abcd[1] * abcd[2]); if (isNaN(ratio) || ratio === Number.POSITIVE_INFINITY) { ratio = 0; } oddsRatioVector.setValue(i, ratio); v.setValue(i, morpheus.FisherExact.fisherTest(abcd[0], abcd[1], abcd[2], abcd[3])); pvalues.push(v.getValue(i)); } var fdr = morpheus.FDR_BH(pvalues); for (var i = 0, size = dataset.getRowCount(); i < size; i++) { fdrVector.setValue(i, fdr[i]); } vectors.push(oddsRatioVector); vectors.push(fdrVector); vectors.push(contingencyTableVector); done(); } else { // background if (npermutations > 0) { options.input.numClassA = aIndices.length; options.input.npermutations = npermutations; var blob = new Blob( [ 'self.onmessage = function(e) {' + 'importScripts(e.data.scripts);' + 'self.postMessage(morpheus.MarkerSelection.execute(morpheus.Dataset.fromJson(e.data.dataset), e.data.input));' + '}' ]); var url = window.URL.createObjectURL(blob); var worker = new Worker(url); var subset = new morpheus.SlicedDatasetView(dataset, null, aIndices.concat(bIndices)); worker.postMessage({ scripts : morpheus.Util.getScriptPath(), dataset : morpheus.Dataset.toJson(subset, { columnFields : [], rowFields : [] }), input : options.input }); worker.onmessage = function(e) { var result = e.data; var pvalueVector = dataset.getRowMetadata().add('p_value'); var fdrVector = dataset.getRowMetadata().add('FDR(BH)'); var kVector = dataset.getRowMetadata().add('k'); for (var i = 0, size = pvalueVector.size(); i < size; i++) { pvalueVector.setValue(i, result.rowSpecificPValues[i]); fdrVector.setValue(i, result.fdr[i]); kVector.setValue(i, result.k[i]); v.setValue(i, result.scores[i]); } vectors.push(pvalueVector); vectors.push(fdrVector); vectors.push(kVector); done(); worker.terminate(); window.URL.revokeObjectURL(url); }; return worker; } else { for (var i = 0, size = dataset.getRowCount(); i < size; i++) { v.setValue(i, f(list1.setIndex(i), list2.setIndex(i))); } done(); } } } };