import clusterfck from "tayden-clusterfck" ; import jStat from "jstat" ; type Item = { orderedValueList : number [ ] ; } ; type ProcessedItem = Item & { isAllNaNs : boolean ; preProcessedValueList : number [ ] ; } ; export type EntityItem = Item & { entityId : string ; } ; export type CaseItem = Item & { caseId : string ; } ; export type CasesAndEntities = { [ caseId ] : { [ entityId ] : number ; } ; } ; export type ClusteringMessage = { dimension : "CASES" | "ENTITIES" ; casesAndEntities : CasesAndEntities ; } ; const ctx = ( self as any as Worker ) ; param m ctx . onmessage = function ( m ) { console . log ( 'Clustering worker received message' ) ; var result = null ; if ( ( m . data as ClusteringMessage ) . dimension === "CASES" ) { result = hclusterCases ( ( m . data as ClusteringMessage ) . casesAndEntities ) ; } else if ( ( m . data as ClusteringMessage ) . dimension === "ENTITIES" ) { result = hclusterGeneticEntities ( ( m . data as ClusteringMessage ) . casesAndEntities ) ; } else { throw new Error ( "Illegal argument given to clustering-worker.js for m.data.dimension: " + m . data . dimension ) ; } console . log ( 'Posting clustering result back to main script' ) ; ctx . postMessage ( result ) ; } ; /**
 * Returns false if any value is a valid number != 0.0,
 * and true otherwise.
 */ function isAllNaNs ( values ) { for ( let i = 0 ; i < values . length ; i ++ ) { const val = values [ i ] ; if ( ! isNaN ( val ) && val != null && val != 0.0 ) { return false ; } } return true ; } /**
 * Distance measure using 1-spearman's correlation. This function does expect that item1 and item2
 * are an item than contains a item.preProcessedValueList attribute which is the ranked version
 * of item.orderedValueList.
 *
 */ function preRankedSpearmanDist ( item1 , item2 ) { if ( item1 . isAllNaNs && item2 . isAllNaNs ) { return 0 ; } else if ( item1 . isAllNaNs || item2 . isAllNaNs ) { return 3 ; } var ranks1 = item1 . preProcessedValueList ; var ranks2 = item2 . preProcessedValueList ; var r = jStat . corrcoeff ( ranks1 , ranks2 ) ; if ( isNaN ( r ) ) { console . log ( "NaN in correlation calculation" ) ; r = - 2 ; } return 1 - r ; } /**
 * Prepares the data for using spearman method in the distance function.
 * It will pre-calculate ranks and store this in inputItems[x].preProcessedValueList.
 * This pre-calculation significantly improves the performance of the clustering step itself.
 */ function _prepareForDistanceFunction ( inputItems ) { var allNaN = [ ] ; var notAllNaN = [ ] ; for ( var i = 0 ; i < inputItems . length ; i ++ ) { var inputItem = inputItems [ i ] as ProcessedItem ; inputItem . isAllNaNs = isAllNaNs ( inputItem . orderedValueList ) ; if ( inputItem . isAllNaNs ) { allNaN . push ( inputItem ) ; continue ; } else { notAllNaN . push ( inputItem ) ; } var ranks = jStat . rank ( inputItem . orderedValueList ) ; inputItem . preProcessedValueList = ranks ; } return { notAllNaN : notAllNaN , allNaN : allNaN } ; } param casesAndEntitites return function hclusterCases ( casesAndEntitites ) { var refEntityList = null ; var inputItems = [ ] ; for ( var caseId in casesAndEntitites ) { var caseObj = casesAndEntitites [ caseId ] ; var inputItem = new Object ( ) as CaseItem ; inputItem . caseId = caseId ; inputItem . orderedValueList = [ ] ; if ( refEntityList == null ) { refEntityList = getRefList ( caseObj ) ; } for ( var j = 0 ; j < refEntityList . length ; j ++ ) { var entityId = refEntityList [ j ] ; var value = caseObj [ entityId ] ; inputItem . orderedValueList . push ( value ) ; } inputItems . push ( inputItem ) ; } if ( refEntityList . length == 1 ) { inputItems . sort ( function ( i1 , i2 ) { var val1 = i1 . orderedValueList [ 0 ] ; var val2 = i2 . orderedValueList [ 0 ] ; val1 = ( val1 == null || isNaN ( val1 ) ? Number . MAX_VALUE : val1 ) ; val2 = ( val2 == null || isNaN ( val2 ) ? Number . MAX_VALUE : val2 ) ; if ( val1 > val2 ) { return 1 ; } else if ( val1 < val2 ) { return - 1 ; } return 0 ; } ) ; return inputItems ; } var processedInputItems = _prepareForDistanceFunction ( inputItems ) ; var clusters = clusterfck . hcluster ( processedInputItems . notAllNaN , preRankedSpearmanDist ) ; return clusters . clusters ( 1 ) [ 0 ] . concat ( processedInputItems . allNaN ) ; } function getRefList ( caseItem ) { var result = [ ] ; for ( var entityId in caseItem ) { result . push ( entityId ) ; } return result ; } param casesAndEntitites return function hclusterGeneticEntities ( casesAndEntitites ) { var refEntityList = null ; var inputItems = [ ] ; var refCaseIdList = [ ] ; for ( var caseId in casesAndEntitites ) { var caseObj = casesAndEntitites [ caseId ] ; if ( refEntityList == null ) { refEntityList = getRefList ( caseObj ) ; } refCaseIdList . push ( caseId ) ; } for ( var i = 0 ; i < refEntityList . length ; i ++ ) { var entityId = refEntityList [ i ] ; var inputItem = new Object ( ) as EntityItem ; inputItem . entityId = entityId ; inputItem . orderedValueList = [ ] ; for ( var j = 0 ; j < refCaseIdList . length ; j ++ ) { var caseId = refCaseIdList [ j ] ; var caseObj = casesAndEntitites [ caseId ] ; var value = caseObj [ entityId ] ; inputItem . orderedValueList . push ( value ) ; } inputItems . push ( inputItem ) ; } var processedInputItems = _prepareForDistanceFunction ( inputItems ) ; var clusters = clusterfck . hcluster ( processedInputItems . notAllNaN , preRankedSpearmanDist ) ; return clusters . clusters ( 1 ) [ 0 ] . concat ( processedInputItems . allNaN ) ; } export default null ;