commit 3c3c919a44e216f9d0b5b5b08630a5c0ae698bab Author: NunoSempere Date: Thu Apr 21 23:23:24 2022 -0400 feat: initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..c50a285 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +## About + +![](decision-method.png) + +This package contains a series of utilities related to forecasting. It is currently in _alpha_. So far, it only contains utilities related to forecast aggregation, but I may add content related to scoring, charts, etc. + +## Built with + +- vanilla javascript +- [Best readme template](https://github.com/othneildrew/Best-README-Template) + +## Getting started + +### Installation + +```sh +npm install forecasting +``` + +### Usage + +#### Aggregation + +```js +import { + median, + arithmeticMean, + geometricMean, + geometricMeanOfOdds, + extremizedGeometricMeanOfOdds, + neyman, +} from "forecasting"; + +let ps = [0.1, 0.2, 0.4, 0.5]; +console.log(ps); + +console.log(median(ps)); +console.log(arithmeticMean(ps)); +console.log(geometricMean(ps)); +console.log(geometricMeanOfOdds(ps)); +console.log(extremizedGeometricMeanOfOdds(ps, 1.5)); // 1.5 is the extremization factor +console.log(extremizedGeometricMeanOfOdds(ps, 2.5)); +console.log(neyman(ps)); +``` + +You may also install [@forecasting/aggregation](https://www.npmjs.com/package/@forecasting/aggregation) directly + +#### Scoring + +To be done + +#### Charts + +To be done + +## Roadmap + +- [ ] Do another repository for scoring methods +- [ ] Do another repository for charts diff --git a/index.js b/index.js new file mode 100644 index 0000000..a78b125 --- /dev/null +++ b/index.js @@ -0,0 +1,17 @@ +import { + median, + arithmeticMean, + geometricMean, + geometricMeanOfOdds, + extremizedGeometricMeanOfOdds, + neyman, +} from "@forecasting/aggregation"; + +export { + median, + arithmeticMean, + geometricMean, + geometricMeanOfOdds, + extremizedGeometricMeanOfOdds, + neyman, +}; diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json new file mode 100644 index 0000000..57243c0 --- /dev/null +++ b/node_modules/.package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "forecasting", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "node_modules/@forecasting/aggregation": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@forecasting/aggregation/-/aggregation-0.0.1.tgz", + "integrity": "sha512-N6NwJaHioyJQZwjvbNYQCknegrQoWne5I1TILA0jSu+8xkCIN+16cYumc1hZSYAKTzfBsiQWXZbuubfVMpgXFg==" + } + } +} diff --git a/node_modules/@forecasting/aggregation/README.md b/node_modules/@forecasting/aggregation/README.md new file mode 100644 index 0000000..172f939 --- /dev/null +++ b/node_modules/@forecasting/aggregation/README.md @@ -0,0 +1,55 @@ +## About + +![](decision-method.png) + +This package contains a series of utilities for forecast aggregation. It is currently in _alpha_, meaning that the code itself works, but there isn't error checking. + +For an introduction to different aggregation methods, see Jaime Sevilla's [Aggregation](https://forum.effectivealtruism.org/s/hjiBqAJNKhfJFq7kf) series. For an explanation of the neyman method, see [here](https://forum.effectivealtruism.org/s/hjiBqAJNKhfJFq7kf/p/biL94PKfeHmgHY6qe). + +## Built with + +- vanilla javascript +- [Best readme template](https://github.com/othneildrew/Best-README-Template) + +## Getting started + +### Installation + +```sh +npm install @forecasting/aggregation +``` + +### Usage + +```js +import { + median, + arithmeticMean, + geometricMean, + geometricMeanOfOdds, + extremizedGeometricMeanOfOdds, + neyman, +} from "@forecasting/aggregation"; + +let ps = [0.1, 0.2, 0.4, 0.5]; +console.log(ps); + +console.log(median(ps)); +console.log(arithmeticMean(ps)); +console.log(geometricMean(ps)); +console.log(geometricMeanOfOdds(ps)); +console.log(extremizedGeometricMeanOfOdds(ps, 1.5)); // 1.5 is the extremization factor +console.log(extremizedGeometricMeanOfOdds(ps, 2.5)); +console.log(neyman(ps)); +``` + +## Roadmap + +- [ ] validate probabilities (must be 0<= p <=1) +- [ ] Decide on a return type if probabilities are not validated (-1? / null?) +- [ ] Write wrapper code for validation +- [ ] Validate that array.length > 0 +- [ ] add weighting? by recency? +- [ ] filter outliers? +- [ ] Write documentation +- [ ] Do another repository for scoring methods diff --git a/node_modules/@forecasting/aggregation/decision-method.png b/node_modules/@forecasting/aggregation/decision-method.png new file mode 100644 index 0000000..3292f90 Binary files /dev/null and b/node_modules/@forecasting/aggregation/decision-method.png differ diff --git a/node_modules/@forecasting/aggregation/index.js b/node_modules/@forecasting/aggregation/index.js new file mode 100644 index 0000000..e56d45d --- /dev/null +++ b/node_modules/@forecasting/aggregation/index.js @@ -0,0 +1,64 @@ +// Helpers +const sum = (array) => array.reduce((a, b) => a + b, 0); +const probabilityToOdds = (p) => p / (1 - p); +const oddsToProbability = (o) => o / (1 + o); + +// Main functions +export const median = (array) => { + // needs validation array not empty + let midway = Math.floor(array.length); + let arrayToBeSorted = [...array]; + // sorting mutates the array, which I am averse to + let arraySorted = arrayToBeSorted.sort((a, b) => a - b); + if (midway % 2) { + return arraySorted[midway]; + } else { + return (arraySorted[midway - 1] + arraySorted[midway]) / 2; + } +}; + +export const arithmeticMean = (array) => { + let result = sum(array) / array.length; + return result; +}; + +export const geometricMean = (array) => { + // sum of logs seems more numerically stable than multiplying a lot of numbers 0<=p<=1 + let arrayAsLog = array.map((p) => Math.log(p)); + let sumOfLogs = sum(arrayAsLog) / arrayAsLog.length; + let result = Math.exp(sumOfLogs); + return result; +}; + +export const geometricMeanOfOdds = (array) => { + let arrayOfOdds = array.map((p) => probabilityToOdds(p)); + let arrayOfLogsOfOdds = arrayOfOdds.map((p) => Math.log(p)); + let sumOfLogsOfOdds = sum(arrayOfLogsOfOdds) / arrayOfLogsOfOdds.length; + let geomMeanOfOdds = Math.exp(sumOfLogsOfOdds); + let result = oddsToProbability(geomMeanOfOdds); + return result; +}; + +export const extremizedGeometricMeanOfOdds = ( + array, + extremizationParameter = 1.5 +) => { + let arrayOfOdds = array.map((p) => probabilityToOdds(p)); + let arrayOfLogsOfOdds = arrayOfOdds.map((p) => Math.log(p)); + let extremizedSumOfLogsOfOdds = + (extremizationParameter * sum(arrayOfLogsOfOdds)) / + arrayOfLogsOfOdds.length; + let extremizedGeomMeanOfOdds = Math.exp(extremizedSumOfLogsOfOdds); + let result = oddsToProbability(extremizedGeomMeanOfOdds); + return result; +}; + +export const neyman = (array) => { + let n = array.length; + + let d = + (n * (Math.sqrt(3 * Math.pow(n, 2) - 3 * n + 1) - 2)) / + (Math.pow(n, 2) - n - 1); + let result = extremizedGeometricMeanOfOdds(array, d); + return result; +}; diff --git a/node_modules/@forecasting/aggregation/package.json b/node_modules/@forecasting/aggregation/package.json new file mode 100644 index 0000000..ee04aa3 --- /dev/null +++ b/node_modules/@forecasting/aggregation/package.json @@ -0,0 +1,18 @@ +{ + "name": "@forecasting/aggregation", + "version": "0.0.1", + "description": "Forecasting aggregation utilities", + "main": "index.js", + "scripts": { + "test": "node test.js" + }, + "keywords": [ + "forecasting", + "aggregation", + "prediction", + "prediction", + "markets" + ], + "author": "Nuño Sempere", + "license": "MIT" +} diff --git a/node_modules/@forecasting/aggregation/tests.js b/node_modules/@forecasting/aggregation/tests.js new file mode 100644 index 0000000..97a8f25 --- /dev/null +++ b/node_modules/@forecasting/aggregation/tests.js @@ -0,0 +1,19 @@ +import { + median, + arithmeticMean, + geometricMean, + geometricMeanOfOdds, + extremizedGeometricMeanOfOdds, + neyman, +} from "./index.js"; + +let ps = [0.1, 0.2, 0.4, 0.5]; +console.log(ps); + +console.log(median(ps)); +console.log(arithmeticMean(ps)); +console.log(geometricMean(ps)); +console.log(geometricMeanOfOdds(ps)); +console.log(extremizedGeometricMeanOfOdds(ps, 1.5)); +console.log(extremizedGeometricMeanOfOdds(ps, 2.5)); +console.log(neyman(ps)); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b55a593 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,28 @@ +{ + "name": "forecasting", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "forecasting", + "version": "0.0.1", + "license": "ISC", + "dependencies": { + "@forecasting/aggregation": "^0.0.1" + } + }, + "node_modules/@forecasting/aggregation": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@forecasting/aggregation/-/aggregation-0.0.1.tgz", + "integrity": "sha512-N6NwJaHioyJQZwjvbNYQCknegrQoWne5I1TILA0jSu+8xkCIN+16cYumc1hZSYAKTzfBsiQWXZbuubfVMpgXFg==" + } + }, + "dependencies": { + "@forecasting/aggregation": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@forecasting/aggregation/-/aggregation-0.0.1.tgz", + "integrity": "sha512-N6NwJaHioyJQZwjvbNYQCknegrQoWne5I1TILA0jSu+8xkCIN+16cYumc1hZSYAKTzfBsiQWXZbuubfVMpgXFg==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1a49c6e --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "forecasting", + "version": "0.0.1", + "description": "Forecasting related utilities.", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "forecasting", + "prediction" + ], + "author": "Nuño Sempere", + "license": "ISC", + "dependencies": { + "@forecasting/aggregation": "^0.0.1" + } +}