"""Enumerate four training sets, actually fit two estimators, and decompose exact squared error."""
from fractions import Fraction as F
import itertools,json
def mean(xs):return sum(xs,F(0))/len(xs)
def fit_constant(xs,ys):return (mean(ys),F(0))
def fit_line(xs,ys):
    mx,my=mean(xs),mean(ys);den=sum((x-mx)**2 for x in xs)
    slope=sum((x-mx)*(y-my) for x,y in zip(xs,ys))/den
    return my-slope*mx,slope
def demo():
    xs=[F(0),F(2)];noise=[F(-1,2),F(1,2)];xstar=F(0);fstar=F(0);out=[]
    for name,fit in [('constant',fit_constant),('line',fit_line)]:
        rows=[];pred=[]
        for e0,e1 in itertools.product(noise,repeat=2):
            ys=[xs[0]+e0,xs[1]+e1];a,b=fit(xs,ys);p=a+b*xstar;pred.append(p)
            rows.append({'training_noise':[str(e0),str(e1)],'training_y':[str(y) for y in ys],'fitted_intercept':str(a),'fitted_slope':str(b),'prediction_at_zero':str(p)})
        avg=mean(pred);bias2=(avg-fstar)**2;variance=mean([(p-avg)**2 for p in pred]);mse=mean([(p-fstar)**2 for p in pred]);assert mse==bias2+variance
        future=mean([(p-(fstar+e))**2 for p,e in itertools.product(pred,noise)]);noisevar=mean([e**2 for e in noise]);assert future==mse+noisevar
        out.append({'estimator':name,'training_set_fits':rows,'expected_prediction':str(avg),'squared_bias':str(bias2),'prediction_variance_across_training_sets':str(variance),'expected_squared_error_against_f':str(mse),'future_independent_noise_variance':str(noisevar),'expected_squared_error_against_future_y':str(future)})
    assert [(r['squared_bias'],r['prediction_variance_across_training_sets'],r['expected_squared_error_against_f']) for r in out]==[('1','1/8','9/8'),('0','1/4','1/4')]
    return {'true_function':'f(x)=x','fixed_training_x':['0','2'],'evaluation_x':'0','training_noise':'independent fair -1/2 or +1/2 at each fixed x','training_sets':4,'training_set_probability':'1/4','future_noise':'independent fair -1/2 or +1/2 at x=0','estimators':out,'scikit_learn_or_bagging_executed':False,'commercial_or_customer_data':False}
if __name__=='__main__':print(json.dumps(demo(),ensure_ascii=False,indent=2))
