
    j!                    f    d Z ddlmZ ddlZddlZdZdZd	 ddZ	ddZ
ddZ	 	 dd	Z	 d	 	 	 dd
Zy)a  Measure what the estimator currently assumes.

Three components of the model were constants someone typed rather than
quantities estimated from evidence:

  * the hour-of-day curve and the weekday curve (engine/world.py)
  * the exponents on TV universe, channel share and sport affinity, all fixed
    at 1.0 -- i.e. the assumption that audience scales proportionally with each
  * the per-tier residual sigma behind every 80% interval

Every one of them is measurable from the 5.9M rows already in global_sports.
This module estimates them; worker/fit_model.py runs it and writes the result
to calibration.json, which ReferenceData loads in preference to the constants.

Method, so it can be checked:

  Curves          within-stratum ratio estimation. Inside each
                  (country, channel, event) group the median audience at each
                  hour is expressed as a ratio to that group's own overall
                  median, then the median ratio across groups is taken.
                  Comparing only inside a group holds channel size and event
                  pull fixed, so what survives is the hour effect. This is the
                  same estimator already used for telecast multipliers, and the
                  same logic as a ratio-to-moving-average seasonal index.

  Exponents       weighted least squares on log audience, with event fixed
                  effects absorbed by within-event demeaning. Identification
                  therefore comes from cross-market variation WITHIN an event,
                  which is exactly the comparison a cross-market projection
                  makes. Weights are sqrt(n) on the cell.

  Sigma           standard deviation of log residuals per tier, from a
                  hold-one-out backtest against known audiences.
    )annotationsN   
   c           
     4   | | d   dkD  | |   j                         z     j                         }t        |      }|j                  ||gz         d   j	                         j                         }|j                  |      d   j	                         j                         j                  ddi      }|j                  ||d      }||d   dkD     }|j                  rt        j                  |ddg      S |d   |d   z  |d	<   |j                  |      d	   j                  d
dg      }||d   t        k\     }t        j                  ||j                  d|d
   j                  t              j                   d|d   j                  t"              j                   i      S )zMedian audience at each level of `key`, as a ratio to its group's median.

    Returns columns [key, weight, n_groups]. Levels seen in fewer than
    MIN_GROUPS groups are dropped rather than reported on thin evidence.
    ama_000r   grp_medcolumnsinner)onhowweightn_groupsratiomediansize)notnacopylistgroupbyr   reset_indexrenamemergeemptypd	DataFrameagg
MIN_GROUPSindexastypefloatvaluesint)gskeykeysdmedbasejr   s           2/var/www/html/crystal/crystal2/engine/calibrate.py_within_group_ratior,   -   s    	ByMAC01668A:D
))DC5L
!)
,
3
3
5
A
A
CC99T?9%,,.::<CCI& D (D		$4W	-A	!I,
Aww||S(J$?@@9),AgJ
))C.
!
%
%x&8
9C
c&kZ'
(C<<cii!3x=#7#7#>#E#E#S[%7%7%<%C%CE F F    c                    t        | d      }|j                  r|S |d   |d   j                         z  |d<   |j                  ddi      j	                  d      j                  d      S )z8Hour-of-day weights, normalised so the peak hour is 1.0.hourr   r	   Tdrop)r,   r   maxr   sort_valuesr   r$   outs     r+   measure_hour_curver6   E   si    
b&
)C
yy
MCM$5$5$77CM::vv.:/;;FCOOUYOZZr-   c                    t        | d      }|j                  r|S |d   |d   j                         z  |d<   |j                  d      j	                  d      S )z7Day-of-week weights, normalised so the peak day is 1.0.weekdayr   Tr0   )r,   r   r2   r3   r   r4   s     r+   measure_weekday_curver9   N   sV    
b)
,C
yy
MCM$5$5$77CM??9%11t1<<r-   c                B   g g g g f\  }}}}| | d   dkD     j                  g d      d   j                  ddg      j                         }	|	|	d   t        k\     }	|	j	                  d      D ]z  }
|j                  |
j                        }|j                  |
j                  |
j                  f      }|j                  |
j                        } ||
j                  |
j                        }|r|r|r|r|
j                  dkD  s|j                  t        j                  |      t        j                  ||z        t        j                  |      g       |j                  t        j                  t        |
j                                     |j                  t        j                  t        |
j                                     |j                  |
j                  |
j                  f       } t!        |      dk  rdd	t!        |       d
dS t        j"                  |      }t        j"                  |      }t        j"                  |      }t        j"                  |D cg c]  }|d   	 c}      }g g g }}}t        j$                  |      D ]  }||k(  }|j'                         dk  s8t!        t        j(                  |      d   D ch c]
  }||   d    c}      dk  rT||   }|j                  ||   t        j*                  ||   d|      z
         |j                  ||   t        j*                  ||   |      z
         |j                  |        |sdddS t        j,                  |      }t        j.                  |      }t        j.                  |      }t        j0                  j3                  ||dddf   z  ||z  d      ^}}|||z  z
  }t        t        j                  t        j*                  |dz  |                  }t        t        j&                  ||dz  z              }|dkD  r*dt        t        j&                  ||dz  z              |z  z
  n
t        d      }|j4                  \  }}t        j0                  j7                  ||dddf   z  j8                  ||dddf   z  z        }t        j                  t        j:                  |      t        j&                  ||dz  z        t=        ||z
  d      z  z        } dt        |d         t        |d         t        |d         t        | d         t        | d         t        | d         |t        |      t?        |      t?        t!        t        j$                  |                  dS c c}w c c}w )a  Estimate the exponents on TV universe, relative share and affinity.

    Model, per (country, channel, event) cell:

        ln(mean ama) = alpha_event
                     + b1 ln(TVU)
                     + b2 ln(share / flagship share)
                     + b3 ln(affinity for that event's sport)
                     + e

    alpha_event is absorbed by demeaning within event, so the estimate is
    driven purely by how cells differ ACROSS markets for the SAME event. That
    is the comparison a cross-market projection performs, which is why the
    coefficients are the right ones to carry into it.

    Returns the coefficients plus the diagnostics needed to judge them.
    r   r   countrychannel
event_namemeanr   F)r   <   zonly z usable cells)okreason      )axisweights)rF   zno event has 3+ marketsN)rcond   nanT)rA   tvushareaffinityse_tvuse_sharese_affinitysigma	r2_withinn_cellsn_events) r   r   r   MIN_CELL_ROWS
itertuplesgetr<   r=   r>   r?   appendnplogr!   sqrtr   lenarrayuniquesumwhereaveragevstackconcatenatelinalglstsqshapepinvTdiagr2   r#   )!r$   rJ   rK   flagshipaffinity_ofXYWmetacellsrtsflamevXdYdWdeiwbeta_residrP   ss_totr2nkxtx_invses!                                    r+   fit_exponentsr   X   s   & BNMAq!T9!"g:;IGc66"#KKM 
 %-=01EE* 
/GGAIIIIqyy!)),-\\!))$		1<<0aB1!	"&&)RVVAF^RVVAY78	aff&'	qvv'(Q\\199-.
/ 1v{s1vhm'DEE
A!Q"((1+a	&A1Q4&	'B RBBYYr] !G557Q;#288A;q>BatAwqzBCaGaD
		!A$AaDq!<<=
		!A$AaD!445
		! '@AA	2BR^^B/bnnR6HyyrBq$wK/bEHD1dNE"''"**UaZ<=>E266"rQw,'(F8>
U266"uz/*+f4	4eB 88DAqiinnb2ag;.11R"QW+5EFGG	!RVVB!O%<s1q5!}%LM	NBa>E$q'NdSTgBqElbeUSUVWSX\r1v3s299R=/A+B	D D7 ' Cs   VV
c                   t        j                  |       t        j                  |      z  | dkD  z  |dkD  z  }t        j                  ||   | |   z        }||   }t        |      rt	        t        j
                  |            nd}i i }	}t        j                  |      D ]  }
|||
k(     }t        |      dk  rt        |      t        |      |z   z  }t	        t        j                  |t	        t        j
                  |            z  d|z
  |z  z               |t        |
      <   t	        t        j                  t        j                  |d      dd            |	t        |
      <    ||	fS )a  Per-tier log-residual sigma and a shrunk multiplicative correction.

    correction_tier = exp( w * mean(log truth/pred)_tier + (1-w) * global mean ),
    w = n / (n + shrink_n).

    That is a James-Stein / empirical-Bayes estimator: a tier with few backtest
    rows is pulled toward the global correction rather than trusting its own
    noisy mean. Without it a tier seen ten times would be calibrated as
    confidently as one seen ten thousand times.
    r   g        r   rD   )ddofg?g      @)rX   isfiniterY   r[   r!   r?   r]   expr#   clipstd)predtruthtiershrink_nrA   lrtrmu_gcorrsigrq   vr|   s                r+   residual_statsr      s6    
T	R[[/	/4!8	<		JB	b	DH$	%B	bB!$R5cDB#DYYr] 
DrQwKq6A:Fc!fx'(RVVAbggaj(9$9QUdN$JKLSV
 BGGBFF11$5tTBCCF
D 9r-   )r;   )r$   pd.DataFramer%   strreturnr   )r$   r   r   r   )
r$   r   rJ   dictrK   r   ri   r   r   r   )g       @)
r   
np.ndarrayr   r   r   r   r   r!   r   ztuple[dict, dict])__doc__
__future__r   numpyrX   pandasr   r   rT   r,   r6   r9   r   r    r-   r+   <module>r      sf   !D #  
F)F0[=FD"&FDV &)"->r-   