Mann-Kendall Test w/Sen's Slope
by cormip
HTML
<h2>Mann Kendall Trend Test with Sen's Slope Calculation</h2>
<label>Values: <input style="width:100%;" id="values" value="6.8, 5.9, 5.7, 5.5, 5.5, 4.5, 4, 5.1, 4.5, 4.5, 3.3, 4.8"></label><br />
<small>comma-delimited numeric values</small><br />
<br />
<label>alpha <input id="alpha" value="0.05"></label><br />
<br />
<button id="btnMKtest">Run Mann-Kendall Test</button>
<div id="mk" style="display: none;">
<h3>Mann Kendall Test Results</h3>
<table>
<tr><td align=right>n:</td><td><span id="n"></span></td></tr>
<tr><td align=right>MK-stat:</td><td><span id="mkstat"></span></td></tr>
<tr><td align=right>stdErr:</td><td><span id="stderr"></span></td></tr>
<tr><td align=right>z-stat:</td><td><span id="zstat"></span></td></tr>
<tr><td align=right>p-value:</td><td><span id="pvalue"></span></td></tr>
<tr><td align=right>trend:</td><td><strong id="trend"></strong></td></tr>
</table>
</div>
<div id="sen" style="display: none;">
<h3>Sen's Slope</h3>
<table>
<tr><td align=right>pairCount:</td><td><span id="paircount"></span></td></tr>
<tr><td align=right>k:</td><td><span id="k"></span></td></tr>
<tr><td align=right>lower:</td><td><span id="lower"></span></td></tr>
<tr><td align=right>upper:</td><td><span id="upper"></span></td></tr>
<tr><td colspan="2"> </td></tr>
<tr><td align=right>slope:</td><td><span id="slope"></span></td></tr>
<tr><td align=right>confLower:</td><td><span id="conflower"></span></td></tr>
<tr><td align=right>confUpper:</td><td><span id="confupper"></span></td></tr>
</table>
</div>
JavaScript 1.7
//Special thanks to: https://www.real-statistics.com/time-series-analysis/time-series-miscellaneous/mann-kendall-test/
const erf = x => {
//A&S formula 7.1.26
const
a1 = 0.254829592,
a2 = -0.284496736,
a3 = 1.421413741,
a4 = -1.453152027,
a5 = 1.061405429,
p = 0.3275911
x = Math.abs(x);
const t = 1 / (1 + p * x);
//Direct calculation using formula 7.1.26 is absolutely correct
//But calculation of nth order polynomial takes O(n^2) operations
//return 1 - (a1 * t + a2 * t * t + a3 * t * t * t + a4 * t * t * t * t + a5 * t * t * t * t * t) * Math.Exp(-1 * x * x);
//Horner's method, takes O(n) operations for nth order polynomial
return 1 - ((((((a5 * t + a4) * t) + a3) * t + a2) * t) + a1) * t * Math.exp(-1 * x * x);
}
const NORMSDIST = z => {
let sign = 1;
if (z < 0) sign = -1;
return 0.5 * (1.0 + sign * erf(Math.abs(z)/Math.sqrt(2)))
}
const NORMSINV = p => {
const
a1 = -39.6968302866538,
a2 = 220.946098424521,
a3 = -275.928510446969,
a4 = 138.357751867269,
a5 = -30.6647980661472,
a6 = 2.50662827745924,
b1 = -54.4760987982241,
b2 = 161.585836858041,
b3 = -155.698979859887,
b4 = 66.8013118877197,
b5 = -13.2806815528857,
c1 = -7.78489400243029E-03,
c2 = -0.322396458041136,
c3 = -2.40075827716184,
c4 = -2.54973253934373,
c5 = 4.37466414146497,
c6 = 2.93816398269878,
d1 = 7.78469570904146E-03,
d2 = 0.32246712907004,
d3 = 2.445134137143,
d4 = 3.75440866190742,
p_low = 0.02425,
p_high = 1 - p_low
let q, r, retVal
if ((p < 0) || (p > 1)) {
console.log("NORMSINV: Argument out of range.");
retVal = 0;
} else if (p < p_low) {
q = Math.sqrt(-2 * Math.log(p))
retVal = (((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / ((((d1 * q + d2) * q + d3) * q + d4) * q + 1)
} else if (p <= p_high) {
q = p - 0.5
...