ArrayStatistics
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © RicardoSantos

//@version=5

// @description Statistic Functions using arrays.
library(title="ArrayStatistics")

import RicardoSantos/ArrayExtension/1 as ae
import RicardoSantos/ArrayOperationsFloat/1 as aof

import RicardoSantos/DebugConsole/2 as console
[__T, __C] = console.init(20)

// @function Root Mean Squared
// @param sample float array, data sample points.
// @returns float
export rms(float[] sample) => //{
    int _size = array.size(sample)
    if _size > 0
        float _sum2 = array.sum(aof.power(sample, array.from(2.0)))
        math.sqrt(_sum2 / _size)
//{ usage:
var float[] rms_a = array.from(10, 4, 6, 8.0)//ae.sequence_float(1.0, 20.0, 1.0)
console.queue_one(__C, str.format('Root Mean Squared ({0}) = {1}', str.tostring(rms_a), rms(rms_a)))
//{ reference:
//      https://www.geeksforgeeks.org/program-to-calculate-root-mean-square/
//      https://github.com/vjaunet/fortran_lib_ifort/blob/master/SOURCE/lib_stat.f90
//}}

// @function Pearson's 1st Coefficient of Skewness.
// @param sample float array, data sample.
// @returns float
export skewness_pearson1 (float[] sample) => //{
    (array.avg(sample) - array.mode(sample)) / array.stdev(sample)
//{ usage:
// plot(skewness_pearson1(array.from(10.0,3,4,5,8,3,5,2)))
var float[] sp1_a = array.from(10.0,3,4,5,8,3,5,2)
console.queue_one(__C, str.format('Pearson´s 1st Coefficient of Skewness ({0}) = {1}', str.tostring(sp1_a), skewness_pearson1(sp1_a)))
//{ reference:
//      https://www.investopedia.com/terms/s/skewness.asp
//      https://www.statisticshowto.com/pearsons-coefficient-of-skewness/
//}}}

// @function Pearson's 2nd Coefficient of Skewness.
// @param sample float array, data sample.
// @returns float
export skewness_pearson2 (float[] sample) => //{
    (3.0 * (array.avg(sample) - array.median(sample))) / array.stdev(sample)
//{ usage:
console.queue_one(__C, str.format('Pearson´s 2nd Coefficient of Skewness ({0}) = {1}', str.tostring(sp1_a), skewness_pearson2(sp1_a)))
// plot(skewness_pearson2(array.from(10.0,3,4,5,8,3,5,2)))
//{ reference:
//      https://www.investopedia.com/terms/s/skewness.asp
//      https://www.statisticshowto.com/pearsons-coefficient-of-skewness/
//}}}

// @function Pearson correlation coefficient measures the linear relationship between two datasets.
// @param sample_a float array, sample with data.
// @param sample_b float array, sample with data.
// @returns float p 
export pearsonr(float[] sample_a, float[] sample_b) => //{
    int _size_a = array.size(id=sample_a)
    int _size_b = array.size(id=sample_b)
    if _size_a > 0 and _size_b > 0
        float _err_sum_ab = array.covariance(id1=sample_a, id2=sample_b)
        float _err_sum_a2 = array.stdev(id=sample_a)
        float _err_sum_b2 = array.stdev(id=sample_b)
        float _r = _err_sum_ab / (_err_sum_a2 * _err_sum_b2)
//{
// usage:
float[] pr_a = array.from(1.0, 2.0, 3.0, 4.0, 5.0)
float[] pr_b = array.from(10.0, 9.0, 2.5, 6.0, 4.0)
// plot(pearsonr(a, b))// 0.74
console.queue_one(__C, str.format('Pearson´s Correlation Coefficient ({0}, {1}) = {2}', str.tostring(pr_a), str.tostring(pr_b), pearsonr(pr_a, pr_b)))
//}}

// @function Kurtosis of distribution.
// @param sample float array, data sample.
// @returns float
export kurtosis (float[] sample) => //{
    int _size = array.size(sample)
    float _mean = array.avg(sample)
    if _size > 0
        float _m2 = 0.0
        float _m4 = 0.0
        for _i = 0 to _size-1
            float _e = array.get(sample, _i) - _mean
            _m2 += math.pow(_e, 2)
            _m4 += math.pow(_e, 4)
        _m2 /= _size
        _m4 /= _size
        _m4 / math.pow(_m2, 2.0)
//{ usage:
// plot(kurtosis(array.from(42, 20, 38, 78, 54, 26))) // 2.35
console.queue_one(__C, str.format('Kurtosis ({0}) = {1}', str.tostring(sp1_a), kurtosis(sp1_a)))
//{ reference:
//      https://www.educba.com/kurtosis-formula/
//}}}

// @function Get range around median containing specified percentage of values.
// @param sample int array, Histogram array.
// @param percent float, Values percentage around median.
// @returns tuple with [int, int], Returns the range which containes specifies percentage of values.
export range_int ( int[] sample, float percent ) => //{
    int _size = array.size(sample)
    //
    switch
        (_size < 1) => runtime.error('ArrayStatistics -> range_int(): Parameter "sample" needs to have 1 or more elements.')
    //
    int _total = array.sum(sample)
    int _h = int(_total * ( percent + ( 1 - percent ) / 2 ))
    int _hits = _total
    int _min = -1
    int _max = _size
    // get range min value
    for _i = 0 to _size-1
        _min += 1
        _hits -= array.get(sample, _min)
        if _hits < _h
            break
    // get range max value
    _hits := _total
    for _i = _size - 1 to 0
        _max -= 1
        _hits -= array.get(sample, _max)
        if _hits < _h
            break
    [_min, _max]
//{ usage:
// create histogram array
var int[] r_histogram = array.from(1, 1, 2, 3, 6, 8, 11, 12, 7, 3)
[r_min, r_max] = range_int(r_histogram, 0.75) // 75% range around median: ([4, 8])
console.queue_one(__C, str.format('Range ({0}, 75%) = min: {1}, max: {2}', str.tostring(r_histogram), r_min, r_max))
// plot(min), plot(max)
//{ remarks:
//      The input array is treated as histogram, i.e. its indexes are
//          treated as values of stochastic function, but array values
//          are treated as "probabilities" (total amount of hits).
//      The method calculates range of stochastic variable, which summary
//          probability comprises the specified percentage of histogram's hits.
//}}}

// @function Computes a weighted average (the mean, where each value is weighted by its relative importance).
// @param sample float array, data sample.
// @param weights float array, weights to apply to samples.
// @returns float
export average_weighted (float[] sample, float[] weights) =>  //{
    int _size_s = array.size(id=sample)
    int _size_w = array.size(id=weights)
    //
    switch
        (_size_s < 1) => runtime.error('ArrayStatistics -> average_weighted(): Parameter "sample" needs to have 1 or more elements.')
        (_size_s != _size_w) => runtime.error('ArrayStatistics -> average_weighted(): Parameter "sample" and "weights" size must match.')
    //
    float _avg = 0.
    float _count = array.sum(id=weights)
    for _i = 0 to _size_s - 1 by 1
        _avg := _avg + array.get(id=sample, index=_i) * array.get(id=weights, index=_i)
    //
    _avg := _avg / _count
//{ usage:
var float[] aw_a = array.from(1.0, 2, 3, 0, 1, 2, 3)
var float[] aw_b = array.from(1.0, 1, 2, 1, 1, 2, 1)
console.queue_one(__C, str.format('Average Weighted ({0}, {1}) = {2}', str.tostring(aw_a), str.tostring(aw_b), average_weighted(aw_a, aw_b)))
// t0 = average_weighted(array.from(1, 2, 3, 0, 1, 2, 3), array.from(1, 1, 2, 1, 1, 2, 1))
// t1 = average_weighted(array.from(.1, 0.2, .3, .0, .1, .2, .3), array.from(1, 1, 2, 1, 1, 2, 1))
// c(_i) => close[_i]
// h = ta.highest(7)
// l = ta.lowest(7)
// r = h - l
// w(_i) => (c(_i) - l) / r
// t2y = array.from(c(0), c(1), c(2), c(3), c(4), c(5), c(6))
// t2w = array.from(w(0), w(1), w(2), w(3), w(4), w(5), w(6))
// t2 = average_weighted(t2y, t2w)  // more weight to higher values
// plot(series=t0)
// plot(series=t1)
// plot(series=t2)
// plot(series=close, color=color.new(color.yellow, 0))
// plot(series=ta.sma(close, 7), color=color.new(color.orange, 0))
//{ remarks:
//      https://help.smartsheet.com/function/avgw
//}


// @function Calculate the arithmetic-geometric mean of two numbers (_value_a, _value_b).
// @param value_a float, number value.
// @param value_b float, number value.
// @param tolerance_value float, number value, default=1e-12.
// @returns float
export arithmetic_geometric_mean_simple (float value_a, float value_b, float tolerance_value=1.0e-12) =>  //{
    float _an = 0.5 * (value_a + value_b)
    float _gn = math.sqrt(value_a * value_b)
    while math.abs(_an - _gn) > tolerance_value
        float _at = _an
        _an := 0.5 * (_at + _gn)
        _gn := math.sqrt(_at * _gn)
    _an
//{ usage:
console.queue_one(__C, str.format('Arithmetic Geometric Mean Simple (1, 1 / sqrt(2)) = {0, number, #.#########}', arithmetic_geometric_mean_simple(1.0, 1.0 / math.sqrt(2.0)) )) // expected: 0.847213084835
console.queue_one(__C, str.format('Arithmetic Geometric Mean Simple (sqrt(2), 1) = {0, number, #.#########}', arithmetic_geometric_mean_simple(math.sqrt(2.0), 1.0))) // expected: 1.1981402347355922074...
//{ remarks:
//      http://rosettacode.org/wiki/Arithmetic-geometric_mean#Basic_Version
//}}}

// @function Computes the arithmetic geometric mean of a sequence of values.
// @param sample float array, data sample of positive values only.
// @returns float
export arithmetic_geometric_mean (float[] sample) =>  //{
    int _size = array.size(sample)
    //
    switch
        (_size < 1) => runtime.error('ArrayStatistics -> geometric_mean(): "sample" has the wrong size.')
    //
    float _agm = array.get(sample, 0)
    if _size > 1
        for _i = 1 to _size - 1 by 1
            _agm *= array.get(sample, _i)
    nz(math.pow(_agm, (1.0 / _size)), 0.0)
//{ usage:
if barstate.islast
    console.queue_one(__C, str.format('Arithmetic Geometric Mean (close)[4] = {0}', arithmetic_geometric_mean(array.from(close, close[1], close[2], close[3]))))
    // console.queue_one(__C, str.format('Arithmetic Geometric Mean = {0}', arithmetic_geometric_mean(array.from(math.sqrt(2.0), 1.0))))
    // console.queue_one(__C, str.format('Arithmetic Geometric Mean = {0}', arithmetic_geometric_mean(array.from(math.sqrt(2.0), -1.0, 2))))
    // console.queue_one(__C, str.format('Arithmetic Geometric Mean = {0}', arithmetic_geometric_mean(array.from(50.3, 55.7, 57.1, 54.9, 55.5))))
//{ remarks:
//      https://www.scribbr.com/statistics/geometric-mean/
//}}}

// @function Computes the simple version of the z-score formula.
// @param value float, data value.
// @param mean float, data mean.
// @param deviation float, the standard deviation of the data.
// @returns float.
export zscore_simple (float value, float mean, float deviation) => //{
    (value - mean) / deviation
//{ usage:
//{ remarks:
//      https://zscoregeek.com/zscore/
//      https://www.statisticshowto.datasciencecentral.com/standardized-values-examples/
//}}}

// @function Computes the z-score of a data sample values.
// @param sample float array, data values.
// @returns float array.
export zscore (float[] sample) => //{
    int _size_s = array.size(sample)
    //
    switch
        (_size_s < 1) => runtime.error('ArrayStatistics -> zscore(): "sample" has the wrong size.')
    //
    float _mean = array.avg(sample)
    float _std = array.stdev(sample)
    float[] _zscores = array.new_float(_size_s)
    for _i = 0 to _size_s - 1
        array.set(_zscores, _i, zscore_simple(array.get(sample, _i), _mean, _std))
    _zscores
//{ usage:
var float[] zscore_sample = array.from(6.0, 7, 7, 12, 13, 13, 15, 16, 19, 22)
console.queue_one(__C, str.format('Zscores for {0}\n\t\t\t\t\t\t\t\t = {1}', str.tostring(zscore_sample), str.tostring(zscore(zscore_sample))))
//{ remarks:
//      https://zscoregeek.com/zscore/
//      https://www.statisticshowto.datasciencecentral.com/standardized-values-examples/
//}}}


console.update(__T, __C)