Last active
January 28, 2018 17:53
-
-
Save ra100/fb983ceaa483822a2e8ae7a8d7f28871 to your computer and use it in GitHub Desktop.
Normalize relative scales across multiple evaluations.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const DEFAULT_MODIFIER = { multiplier: 1, offset: 0, error: Number.MAX_VALUE } | |
const users = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'] | |
const sourceData = [ | |
{ A: 1, B: 0.8, C: 0.34, D: 0.33, E: 0 }, | |
{ B: 1, A: 0.9, C: 0.85, F: 0.5, J: 0.1, G: 0 }, | |
{ B: 1, H: 0.7, D: 0.5, J: 0.3, F: 0 }, | |
{ C: 1, A: 0.9, B: 0.8, I: 0.6, E: 0.1, J: 0 } | |
] | |
const getAverage = arr => | |
arr.reduce((acc, curr, index) => (acc * index + curr) / (index + 1), 0) | |
const modifySingle = (feedback, offset, multiplier) => | |
Object.entries(feedback) | |
.map(([key, val]) => [key, (val + offset) * multiplier]) | |
.reduce((acc, curr) => { | |
acc[curr[0]] = curr[1] | |
return acc | |
}, {}) | |
const offsetAndMultiply = (source, offset, multiplier) => | |
source.map(feedback => modifySingle(feedback, offset, multiplier)) | |
const offsetSource = offsetAndMultiply(sourceData, 1, 1) | |
const getIntersection = (scaleA = {}, scaleB = {}) => | |
Object.entries(scaleA) | |
.filter(([key]) => scaleB[key] !== undefined) | |
.reduce((acc, [key, value]) => { | |
acc[key] = { etalon: value, compare: scaleB[key], name: key } | |
return acc | |
}, {}) | |
const getMultiplier = (offset, etalon, value) => etalon / (value + offset) | |
const getOptimalModifier = (etalon, index, sourceData) => | |
Object.values(sourceData).map((data, i) => { | |
if (i === index) return { index: i, skip: true } | |
const intersection = getIntersection(etalon, data) | |
Object.entries(intersection).forEach(([key, value]) => { | |
intersection[key] = { | |
...value, | |
offset: value.etalon - value.compare | |
} | |
}) | |
Object.values(intersection).forEach(val => { | |
const multipliers = Object.values(intersection) | |
.filter(({ name }) => name !== val.name) | |
.map(({ compare, etalon }) => | |
getMultiplier(val.offset, etalon, compare) | |
) | |
const min = Math.min(...multipliers) | |
const max = Math.max(...multipliers) | |
const avg = (min + max) / 2 | |
const difference = getAverage(multipliers.map(m => Math.abs(m - avg))) | |
intersection[val.name] = { | |
...val, | |
multipliers, | |
averageMultiplier: avg, | |
multiplierError: difference | |
} | |
}) | |
const modifier = Object.values(intersection).reduce( | |
( | |
acc, | |
{ averageMultiplier: multiplier, multiplierError: error, offset } | |
) => { | |
if (error > acc.error) return acc | |
return { multiplier, offset, error } | |
}, | |
{ ...DEFAULT_MODIFIER } | |
) | |
// console.log('Modifier', modifier) | |
return { index: i, modifier } | |
}) | |
const test = getOptimalModifier(offsetSource[0], 0, offsetSource) | |
offsetSource.forEach((data, index) => { | |
getOptimalModifier(offsetSource[index], index, offsetSource).map(modifier => { | |
if (modifier.skip) return | |
offsetSource[modifier.index] = modifySingle( | |
offsetSource[modifier.index], | |
modifier.modifier.offset, | |
modifier.modifier.multiplier | |
) | |
}) | |
}) | |
const normalizedEvalutions = offsetAndMultiply(offsetSource, -1, 1) | |
console.log('Normalized Evaluations:\n', JSON.stringify(normalizedEvalutions)) | |
const evaluation = {} | |
normalizedEvalutions.forEach(feedback => { | |
Object.entries(feedback).forEach(([name, value]) => { | |
evaluation[name] = [...(evaluation[name] || []), value] | |
}) | |
}) | |
// values for heatmap | |
console.log('Merged evaluations:\n', evaluation) | |
const result = Object.entries(evaluation) | |
.map(([name, value]) => ({ | |
name, | |
average: getAverage(value) | |
})) | |
.sort(({ average: a }, { average: b }) => b - a) | |
.map(({ name, average }) => `${name}: ${average}`) | |
// user averages | |
console.log('Evaluations:\n', result.join('\n')) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const users = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'] | |
const sourceData = [ | |
{A: 1, B: 0.8, C: 0.34, D: 0.33, E: 0}, | |
{B: 1, A: 0.9, C: 0.85, F: 0.5, J: 0.1, G: 0}, | |
{B: 1, H: 0.7, D: 0.5, J: 0.3, F: 0}, | |
{C: 1, A: 0.9, B: 0.8, I: 0.6, E: 0.1, J: 0} | |
] | |
const getDistanceVector = (name, data) => { | |
const vector = {} | |
data.forEach(res => { | |
if (res[name] === undefined) return | |
const position = res[name] | |
Object.entries(res).forEach(([key, value]) => { | |
const distance = value - position | |
const [avg, weight] = vector[key] || [0, 0] | |
vector[key] = [((avg * weight) + distance) / (weight + 1), weight + 1] | |
}) | |
}) | |
return vector | |
} | |
const vectors = users.map(name => ({ | |
name, | |
vector: getDistanceVector(name, sourceData) | |
})) | |
console.log('Source:', JSON.stringify(sourceData)) | |
console.log('Vectors', JSON.stringify(vectors)) | |
const scale = vectors.reduce((acc, curr) => { | |
const name = curr.name | |
if (!acc[name]) { | |
acc[name] = [0, 0] | |
} | |
console.log(name) | |
Object.entries(curr.vector).forEach(([distName, dist]) => { | |
if (distName === name) return | |
if (!acc[distName]) acc[distName] = dist | |
const currentDistPosition = acc[distName][0] | |
const currentDistance = acc[distName][0] - acc[name][0] | |
const avgPosition = currentDistPosition - currentDistance | |
console.log(`distance: ${name} ${distName}`, currentDistance, avgPosition) | |
}) | |
return acc | |
}, {}) | |
console.log(scale) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment