"""Authored exact average ranks and Pearson correlation of ranks; no inference p-value or SciPy."""
from fractions import Fraction as F
import math,json
def rank(values):
    if len(values)<2 or any(not math.isfinite(v) for v in values):raise ValueError('at least two finite values')
    order=sorted(range(len(values)),key=lambda i:values[i]);result=[None]*len(values);i=0
    while i<len(order):
        j=i+1
        while j<len(order) and values[order[j]]==values[order[i]]:j+=1
        r=F((i+1)+j,2)
        for k in order[i:j]:result[k]=r
        i=j
    return result
def pearson(a,b):
    if len(a)!=len(b):raise ValueError('paired lengths differ')
    ma=sum(a,F(0))/len(a);mb=sum(b,F(0))/len(b)
    numerator=sum(((x-ma)*(y-mb) for x,y in zip(a,b)),F(0));aa=sum(((x-ma)**2 for x in a),F(0));bb=sum(((y-mb)**2 for y in b),F(0))
    return (None if aa==0 or bb==0 else float(numerator)/math.sqrt(float(aa*bb))),numerator,aa,bb
def demo():
    x=[1,1,2,3,3];y=[5,4,3,2,1];rx,ry=rank(x),rank(y);rho,num,aa,bb=pearson(rx,ry)
    naive=1-F(6)*sum(((a-b)**2 for a,b in zip(rx,ry)),F(0))/(len(x)*(len(x)**2-1))
    assert num==-9 and aa==9 and bb==10 and naive==-F(17,20)
    assert abs(rho+3/math.sqrt(10))<1e-12 and abs(rho-float(naive))>.09
    transformed=pearson(rank([v**3 for v in x]),ry)[0];assert transformed==rho
    constant=pearson(rank([1]*5),ry)[0];assert constant is None
    nonmonotone=pearson(rank([-2,-1,0,1,2]),rank([4,1,0,1,4]))[0];assert nonmonotone==0
    return {'authored_x':x,'authored_y':y,'average_rank_x':[str(v) for v in rx],'average_rank_y':[str(v) for v in ry],'rank_centered_cross_sum':str(num),'rank_centered_square_sums':[str(aa),str(bb)],'spearman_as_Pearson_of_average_ranks':rho,'incorrect_no_ties_shortcut_on_this_tied_data':str(naive),'strictly_increasing_cube_transform_rho':transformed,'constant_input_result':constant,'deterministic_square_nonmonotone_example':{'x':[-2,-1,0,1,2],'y':[4,1,0,1,4],'rho':nonmonotone},'p_value_independence_or_causality_tested':False,'scipy_executed':False}
if __name__=='__main__':print(json.dumps(demo(),ensure_ascii=False,indent=2))
