bwsample
Version:
The JS implementation of the pypi package bwsample
188 lines (167 loc) • 4.9 kB
JavaScript
const { adjustScore } = require("./scaling");
const rank = (cnt, method, adjust=undefined, avg="exist") => {
// select method
if (method == "ratio"){
var [positions, sortedids, metrics, info] = maximizeRatio(cnt, avg);
}else if (method == "approx" || method == "hoaglin"){
var [positions, sortedids, metrics, info] = maximizeHoaglinapprox(cnt, avg);
}else{
throw new Error(`method='${method}' not available.`);
}
// adjust scores
if (adjust !== undefined){
var scores = adjustScore(metrics, adjust);
}else{
var scores = [...metrics];
}
// done
return [positions, sortedids, metrics, scores, info];
}
/**
* see https://stackoverflow.com/a/65410414
*
* Example:
* clickCount = [5, 2, 4, 3, 1]
* imgUrl = ['1.jpg', '2.jpg', '3.jpg', '4.jpg', '5.jpg']
* order = argsort(clickCount);
* newArray = order.map(i => imgUrl[i])
*/
const argsort = (arr) => {
const decor = (v, i) => [v, i]; // set index to value
const undecor = a => a[1]; // leave only index
return arr.map(decor).sort().map(undecor);
}
/**
* Rank items based simple ratios, and calibrate row sums as scores
*
* @param {JSON LIL} cnt LIL-format sparse matrix of the pair counts
* @param {String} avg How to compute denominator for averaging (Default: "exist")
*/
const maximizeRatio = (cnt, avg="exist") => {
// compute ratios
var ratio = {}
for( var id1 in cnt ){
for( var id2 in cnt[id1] ){
//console.log(id1, id2, cnt[id1][id2], cnt[id2][id1])
var Nij = cnt[id1][id2];
var Nji = 0;
if (cnt.hasOwnProperty(id2)){
if (cnt[id2].hasOwnProperty(id1)){
Nji = cnt[id2][id1];
}
}
if (!ratio.hasOwnProperty(id1)){
ratio[id1] = {}
}
if (!ratio.hasOwnProperty(id2)){
ratio[id2] = {}
}
ratio[id1][id2] = Nij / (Nij + Nji)
ratio[id2][id1] = Nji / (Nij + Nji)
}
}
// sum up rows in LIL matrix (i.e. the 1st key)
var metrics = []
var sortedids = []
for( var id1 in ratio ){
var tmp = 0;
var num = 0;
for( var id2 in ratio[id1] ){
tmp += ratio[id1][id2];
num++;
}
metrics.push(tmp / Math.max(1, num))
sortedids.push(id1)
}
if (avg !== "exist"){
throw new Error("Only avg=exist is implemented!");
}
// sort, larger row sums are better
var positions = argsort(metrics)
positions.reverse() // maximize
metrics = positions.map(i => metrics[i])
sortedids = positions.map(i => sortedids[i])
// done
return [positions, sortedids, metrics, {}];
}
/**
* Rank based on p-values computed with the Hoaglin Approximation of DoF=0
*
* @param {JSON LIL} cnt LIL-format sparse matrix of the pair counts
* @param {String} avg How to compute denominator for averaging (Default: "exist")
*/
const maximizeHoaglinapprox = (cnt, avg="exist") => {
// wrap p-value computation here
const hoaglin_pvalue = (Nij, Nji) => {
// compute Expected E
var E = (Nij + Nji) / 2.0;
// compute X^2
var Chi2 = Math.pow(Nij - E, 2) / E;
// compute Hoaglin's Approximation for DoF=0
var pval = Math.pow(0.1, (Math.sqrt(Chi2) + 1.37266) / 2.13161);
// ensure interval
return Math.max(0.0, Math.min(1.0, pval));
}
// compute Q=1-pvalue
var qij = {}
for( var id1 in cnt ){
for( var id2 in cnt[id1] ){
var Nij = cnt[id1][id2];
var Nji = 0
if (cnt.hasOwnProperty(id2)){
if (cnt[id2].hasOwnProperty(id1)){
Nji = cnt[id2][id1];
}
}
if (!qij.hasOwnProperty(id1)){
qij[id1] = {}
}
if (!qij.hasOwnProperty(id2)){
qij[id2] = {}
}
// if `Nij > Nji` then set Qij=1-pval and Qji=0
// if `Nji > Nij` then set Qji=1-pval and Qij=0
// if `Nji = Nij` then set both Qij=Qji=0
if (Nij > Nji){
var pval = hoaglin_pvalue(Nij, Nji);
qij[id1][id2] = 1.0 - pval;
qij[id2][id1] = 0.0;
}else if (Nji > Nij){
var pval = hoaglin_pvalue(Nji, Nij);
qij[id2][id1] = 1.0 - pval;
qij[id1][id2] = 0.0;
}else {
qij[id1][id2] = 0.0;
qij[id2][id1] = 0.0;
}
}
}
// sum up rows in LIL matrix (i.e. the 1st key)
var metrics = []
var sortedids = []
for( var id1 in qij ){
var tmp = 0;
var num = 0;
for( var id2 in qij[id1] ){
tmp += qij[id1][id2];
num++;
}
metrics.push(tmp / Math.max(1, num))
sortedids.push(id1)
}
if (avg !== "exist"){
throw new Error("Only avg=exist is implemented!");
}
// sort, larger row sums are better
var positions = argsort(metrics)
positions.reverse() // maximize
metrics = positions.map(i => metrics[i])
sortedids = positions.map(i => sortedids[i])
// done
return [positions, sortedids, metrics, {}];
}
module.exports = {
rank,
maximizeRatio,
maximizeHoaglinapprox
}