scor
Calculate scores for numeric values or items, and get the “total score” (aka arithmetic mean or weighted arithmetic mean) from multiple scores.
Concept and vision
Imagine you
- have a long list of items to work on, and you want to prioritize them
- want to show the most relevant items to a user before showing more
For example, let’s look at npm packages. Possible criteria are:
- number of maintainers
- number of dependencies (direct/transient)
- time since last published version
- version (major < 1?) / dist-tags
- weekly downloads
- source code repo attributes (e.g. GitHub stars/forks)
- quality?
- …?
The different relevant values come in very different “shapes”. Once all the data is gathered per package, depending on the use case the different values are more or less relevant.
I experienced that such a “rating system”, or “weighted average score”, is not so easy to get completely right from scratch alongside collecting the data. It also involves a lot of repetitive code that easily leaks its abstractions into the rest of the code.
scor simplifies this by making certain assumptions:
- All values are within a certain range (
min <= value <= max).- only numeric values are accepted, everything else throws
- To use the (different) values as a score and easily compare all of them,
they need to be converted into the samerange: \ between0(value <= min) and1(value >= max)- If the range is “empty” (
min === max), the score is always 0 - values that are “not numeric” (see
isNumeric) result in a score of 0
- If the range is “empty” (
- The user fully controls the conversion of
itemtovalue(toValue):- get deeply nested fields
- calculate from multiple fields
- convert data to a numeric value
- need the highest value to be the lowest score:
-1 * value - need some logarithmic scale:
Math.log10(value) - …
- The user fully controls
minandmaxvalues,
but they can be derived fromitems(also usingtoValue, seegetItemRange). - All options for
scorare optional and can be configured as a second step - Fail as early as possible (by throwing a specific
Error):- using a method that requires an optional value which has not been configured
- a required value is not numeric (beside cases mentioned above)
- A
Scoris immutable, “mutations” create a new instance. - A
Scornever keeps references to the items it is scoring. - Multiple
Scors can easily be combined into a single overall weighted score per item, e.g. to use it for sorting
Usage
(This has not been published as a package yet, but you can of course fetch the code from GitHub.)
import { createToMean, distributeWeights, scorForItems } from "scor"; // I plan to publish to deno.land soon
import { getPackagesData } from "./npm.ts";
const packages = await getPackagesData();
const scors = {
downloads: scorForItems(
// toValue converts an item to a numeric value, in this case with a log10 scale
(p) => Math.log10(p.downloads),
packages,
),
maintainers: scorForItems((p) => p.maintainers.length, packages),
};
// one way to calculate indivudual scores for each item
const scores = packages.map((p) => ({
name: p.name,
downloadScore: scors.downloads.forItem(p),
maintainerScore: scors.maintainers.forItem(p),
}));
// or calculate the arithmetic mean per item
const scorePerItem = packages.map(createToMean(scors));
// or the weighted arithmetic mean
const weightedScorePerItem = packages.map(createToMean(
scors,
{ downloads: 0.75, maintainers: 0.25 },
));
// or as a list wihtout keys
const scorsList = [
scorForItems(
(p) => Math.log10(p.downloads),
packages,
),
scorForItems((p) => p.maintainers.length, packages),
];
const weightedScorePerItemL = packages.map(
createToMean(scorsList, [0.75, 0.25]),
);
// if you have many weights and some should be distributed:
distributeWeights(
[0.5, undefined, undefined],
); // => [0.5, 0.25, 0.25]
distributeWeights(
{ first: 0.7, second: undefined, third: undefined },
); // => {first: 0.7, second: 0.15, third: 0.15}TODOs
Contributions are welcome!
- publish to
deno.land/x/and update docs - post about it and get feedback
- Add support for weighted sum?
- Add support for more kind of averages?
- publish to npm(?)