https://github.com/karfau/scor
Calculate scores for numeric values or items, and get the "total score" (aka "arithmetic mean" or "weighted arithmetic mean") from multiple scores.
https://github.com/karfau/scor
deno scores typescript
Last synced: about 2 months ago
JSON representation
Calculate scores for numeric values or items, and get the "total score" (aka "arithmetic mean" or "weighted arithmetic mean") from multiple scores.
- Host: GitHub
- URL: https://github.com/karfau/scor
- Owner: karfau
- License: mit
- Created: 2021-12-27T13:36:38.000Z (over 4 years ago)
- Default Branch: main
- Last Pushed: 2022-01-14T18:08:10.000Z (over 4 years ago)
- Last Synced: 2025-11-27T18:28:08.760Z (7 months ago)
- Topics: deno, scores, typescript
- Language: TypeScript
- Homepage:
- Size: 108 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[](https://deno.land/x/scor)
# scor
Calculate scores for numeric values or items, and get the "total score" (aka
[arithmetic mean](https://en.wikipedia.org/wiki/Arithmetic_mean) or
[weighted arithmetic mean](https://en.wikipedia.org/wiki/Weighted_arithmetic_mean))
from multiple scores.
## Usage
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.
```ts
import {
createToMean,
distributeWeights,
scorForItems,
} from "https://deno.land/x/scor/scor.ts";
import { getPackagesData } from "./npm.ts";
const packages = await getPackagesData();
const scors = { // scorForItems uses `toValue` (1st parameter) to determine `min` and `max`
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),
}));
// the result could look like this (1 means highest score, 0 lowest score):
// => [{downloads: 0.786, maintainers:0.2}, {downloads: 0.89, maintainers: 1}, {downloads:1, maintainers: 0}, ...]
// or calculate the arithmetic mean per item
const scorePerItem = packages.map(createToMean(scors));
// => [0.493, 0.945, 0.5]
// or the weighted arithmetic mean
const weightedScorePerItem = packages.map(createToMean(
scors,
{ downloads: 0.75, maintainers: 0.25 },
));
// => [0.31975, 0.45875, 0.375]
// 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]),
);
// => [0.31975, 0.45875, 0.375] (of course the sam as above)
// 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}
```
## Concept and vision
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 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 same `range`: between `0`(`value <= min`)
and `1` (`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 (to
avoid `NaN`)
- The user fully controls the conversion of `item` to `value` (`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 custom scale (e.g. logarithmic): `Math.log10(value)`
- ...
- The user fully controls `min` and `max` values, but they can be derived from
`items` (also using `toValue`, see `getItemRange`).
- All options for `scor` are 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 `Scor` is immutable, "set..." methods create a new instance.
- A `Scor` never keeps references to the items it is scoring.
- Multiple `Scor`s can easily be combined into a single overall weighted score
per item, e.g. to use it for sorting
## TODOs
Contributions are welcome!
- [post about it and get feedback](https://dev.to/karfau/i-published-my-first-deno-package-4720)
- Add support for weighted sum?
- Add support for more kind of averages?
- https://en.wikipedia.org/wiki/Average#Summary_of_types
- https://en.wikipedia.org/wiki/Geometric_mean ?
- https://en.wikipedia.org/wiki/Harmonic_mean ?
- publish to npm(?)