From e73f2e3c5bf0f592e0de446ac12a521520d5a188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Soko=C5=82owski?= Date: Wed, 8 Jul 2020 14:54:24 +0200 Subject: [PATCH] add weeklt cohorts matrix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jakub SokoĊ‚owski --- graph.py | 47 +++++++++++++++++++++++++++++++++++++++++++++-- main.py | 8 +++++--- output.png | Bin 20963 -> 0 bytes 3 files changed, 50 insertions(+), 5 deletions(-) delete mode 100644 output.png diff --git a/graph.py b/graph.py index 18fb788..309b167 100644 --- a/graph.py +++ b/graph.py @@ -13,7 +13,7 @@ class PDGraphPeers(): def unique_peers_counts(self): return self.df.groupby(['Peer'])['Date'].nunique() - def number_of_days(self, exclude=20): + def days_per_peers(self, exclude=20): nu_peers = self.unique_peers_counts() ex_twenty_day = nu_peers[nu_peers > exclude] ax = sns.distplot(ex_twenty_day, kde=False, hist=True) @@ -22,4 +22,47 @@ class PDGraphPeers(): xlabel='# of days', ylabel='# of peers' ) - return ax.get_figure() + return ax + + def weekly_cohorts(self): + self.df['datetime'] = pd.to_datetime(self.df['Date']) + self.df['week'] = self.df['datetime'].dt.to_period('W') + self.df['month'] = self.df['datetime'].dt.to_period('M') + self.df['cohort'] = self.df.groupby('Peer')['datetime'].transform('min').dt.to_period('W') + df_cohort = self.df.groupby(['cohort', 'week']).agg(n_peers=('Peer', 'nunique')).reset_index(drop=False) + + df_cohort['period_number'] = ( + df_cohort.week - df_cohort.cohort).apply(attrgetter('n') + ) + cohort_pivot = df_cohort.pivot_table( + index = 'cohort', + columns = 'period_number', + values = 'n_peers' + ) + cohort_size = cohort_pivot.iloc[:,0] + retention_matrix = cohort_pivot.divide(cohort_size, axis = 0) + + fig, ax = plt.subplots(1, 2, figsize=(12, 8), sharey=True, gridspec_kw={'width_ratios': [1, 11]}) + + # retention matrix + sns.heatmap(retention_matrix, + mask=retention_matrix.isnull(), + annot=True, + fmt='.0%', + cmap='RdYlGn', + ax=ax[1]) + ax[1].set_title('Weekly Cohorts: Peer Retention', fontsize=16) + ax[1].set(xlabel='# of periods', + ylabel='') + + # cohort size + cohort_size_df = pd.DataFrame(cohort_size).rename(columns={0: 'cohort_size'}) + white_cmap = mcolors.ListedColormap(['white']) + + fig.tight_layout() + return sns.heatmap(cohort_size_df, + annot=True, + cbar=False, + fmt='g', + cmap=white_cmap, + ax=ax[0]) diff --git a/main.py b/main.py index 627a7a7..e6c120b 100755 --- a/main.py +++ b/main.py @@ -38,9 +38,11 @@ def main(): pdg = PDGraphPeers(data) - print(pdg.unique_peers_counts()) - plot = pdg.number_of_days() - plot.savefig("output.png") + plot = pdg.days_per_peers() + plot.figure.savefig("days_per_peers.png") + + matrix = pdg.weekly_cohorts() + matrix.figure.savefig("weekly_cohorts.png") if __name__ == '__main__': main() diff --git a/output.png b/output.png deleted file mode 100644 index 5f2c8b666b9a31c0aa79452dd27f52a211b2fb79..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20963 zcmdtKcRZJW-#-4Pt)!)-&`=pg2-!^{Gb_6iMcG?cdl{c(@3KNj$R2$v%1E*|*@W!P z_jq-EuIs+;`@XL4{rLUy`{Vxhc!)Reah|X9IgaOX9Ix}SqP!F>4I>SOLZOvDcjf|x zLhVMOP_?gJjoiHK5Vvvpeq*z5*d1 z_O=^_F|D%SgK67Vr&^ZnHS1P1=J*DtaS1b*lH~#)lcVE*2ZQ^H5VLZU#P_{rFC4*Iao5-=6NJ;L*hmRGTQ2!Ygat z@T={oDJvfj%d+L<}SQbO*NFW8EMT^?9?v(@#*R0 zKtgg)&R4@!qwi7DXS4HOzO(3*Out@vN?ct0ZNp8?lq+YY{Z-fmg5~y0y0i83CY9b_ zzhgpTd0c{K-MVMN!NEbtj4t%?G^XE(wH<1tceW^W-Bx_-=C_EG@fxWy%39LS;xSQC zeGS*bYlbq~k|HAZ%+AinIgIx>_J-Q|gocL3x=Knukn~`T{`O7L!O<~Z`uV}R_PcaT zDzvjhbeZzA-dqu{14QhS2I`WNpYGrum>KPC%y-U-kqeIT*L9RW@6V-L?8(IB%;G$u zWHZ#5uGkq)&iIgmU(_jQwwBNDOh4i@eFFksDMd@Tnj183&CTLad8PRM0M&t*C~+3Mk=Ddj z>$ms4c*y+y-ny+_!^24x;`6dz5=)u3!_9F*Rz1fjM_O_+uo%%h^qiDz25W2Z`8Lao z)0~G6N%8RTa3!<2GE=>IbVgD#{_NB3p2f~{6P3yeV$mYQnWyt^P1oLDu|_4`SXKJM z;?>ejCmo(}o=Mo6t>1F#?c3B;Utiw? zIW0@EMbfgeKK}mdaY9#S)vZT=oNc%%;LE*Sx4c%csBd9*d~9rtqyNH^x6HtJid?XO zu>G%y=9U&FlTcxs@8s(C1e+O;b$y{(zy3LEqDHFZ=;)~Dw?ooON=m{`GjEorU6<9T zTkOJ{o1102Y}$(~XfhdUUMZdCGc5boUlmJMPU@CRtbw3OV-%Kw{pe9eU0vOHrN{?! z6aA5?*D40Sy*M4Oo%NR02%q9vRepChttF#bMVQz?Q${j}YJ3!;X-8(FcBwzNdZeUB zSATz;X1a0z^3uq1NNsOetGIRFAIkFxXcGLfkxhubngsR1UmYLhV?S7R+dbLO!=p6a zY~_Xb>N*~J)OBgWDI@jjmn|MF61p#5ycioFS4PZCc6v!{&+X(+b)L7i>HBsvKR+Mq ztTtGinC>`fTs((J6rvuhPti9>ZAfyId3lt-=CwxEC|`E7Z>*nXv39O~N|7g1!XJNZ zQhlX(Qp|DUTZ(?M_1uJ!*i^kf4Gql&Ma4guU1lyDT)o<*)%IE=RVBkzr(wvp#j^Cs zY}iX_Ik_4vgle9njf1^C+rE9L5VUG)YJQOwFHS$isU35kvz80wJ-d1H=C@5ljkyk1 zx959s`4|OFL-y_4*VWPS!nh{hXy#Xku+4x^q*u9N)vb}dSgYQzLam3!d&)V@W%b=- z6vC5!{W2$+rIBhl_cOI(tYs>IN548sdh6``Jm;Q0CoT^rKin9vSCH?{ z&O6x-L6=?BtWIiC2BzVp>-r zvHbHbBP+X@m`+Mcio8S{GPEDLC%gH!GTXH7uCBhxIz4q%zpn1?=qL1? z@yH*N>ol)BN-FZSv*zbZrbkgkRg!hZip6!_TsoQGsXIFxpf%D{9y~eI>BY&$cJB7= z+XF~wPiC=!iRQ90S#kpJ-@iww)aux^M7QMF?U-q>%98Qr5V~KWozY?&%-Ub4=W3I7 zL@!^PoiVB7DApR=k zdZ^2_HFV6c-P|alVz&e;#+x$Cl2_9*9%pu)>3C?|@HTp3aj|=7D3PSZGb!dQ!#uxB zzhcWmMe5G+=GYFE*g2!1T3A`}p)#1K$%P!R)XpD#?(OZ4F412d_go|8%Ke7722VB8 zu0Pv(^pcy9!&sD%>r$>pj&0(ztE=glZ*JMLMfTLGJF~OB;h{zA>F5OYi#_>K1Pnht zp`SG^Wzhfg!w0!^;~H5HM*gx8A*-?={u^a0=tM8E2D&bdhdbzMUAW*~H7;n@dXqJ< zJXmm-zI#J~Yk^T!jK{i-8*Quo{`jN4kNb-(4sCR%ZFwnUbaIkE{YIU@`t660Ca5P1 zOt-lfJiCe#?{`fE<>VNh=r1>oJjZm6w^tv>p^n5U$9jG#DajtFFf8>uQqXIbAXMcU5V_Lj$;ezbNivjDff~kh(yt@Q$ z9!1FKrZf$a5Ak%7mX?-GFZ$OrjRF_vu<27tAtAe~qR;P8&UbR)yZq`IPrVyLN(tehdLLeIy?Te%WCPy=F6eGjs+ea>AND>eM^fVL;SPM2?KX(`5ee(K<% zL&cSX8MQzSfuEl42u2SJ{P^+XuGZLwh6Zv0ii(SiWy3@pJ{A`TPq*HxARp6=s@sF~ z^33PXpLvPXOVT!S=Q+s3Q)z$eR`41Yr*B4OR4Z091eY5!14^V?cFX);#vQu$?{lXb zZ+MEYS`6!P8P>@v==~Zxc)fJ-XKruUt+Lo~7vt?*+Sg69`wte6w00*gIF&h@B0EXE zm1o-wmZ2zLLnpfW^5x5ruv-8E!DP|#x<(RDxpM;v2M-@Eo0*^Qoo}!bTfK4j^%8$> zGneH>J1MOMwZyW-jMku7vny9{(1ztfHZ?r!QzcLkt{PT^nf&PNlyh>*by%A1_3-!i z4-|EH8(D#ZeuFj8uFbiwKWlz^M9{k|*eo9>Ylx;-WSAPbv^XD(v{nl=c}xN04Cdy7opht_1^taG8%SkIE%VY zhf7qnx&G!m(WXHZmeKKXzH47YEF4YhXxxeztbh@EXU~4MF=$zwpBA*uY1u7g`7ut< z+B$7?YDyqhKGd>NKEt#nWNvX#Z<*IShf-0j*tx(Z;5m6 z{~j&r?ErYX+hoO>jmH3l>voT$R2NlMRZR@kl*TPS<<39q9WvG7`}XbIGDKi``}@0c zRWWi*LnHvz5;c3UJ{AKkOiU)mbxA|^3hoR%f2zE`I2y#S%WhY2|G|SG(xn^mhH@uP zxb@ekL`Eh8$&yn&c;raAk^AQTk_-w83gg8rt_=E1^Glh7NjWmF9{}DfCuwJihFfKY z>)fb)ZILF;i=^=E)fMmY_*vE5J~LX(B9cDRm|l90*DP<+FY*X-#d@%|9G_mvdhlQn zsa4gif30b9mULh047C z&1LyT00~ppKp&rN`em*kz5*4Ex9ACOq0}X6T|+Cr7Unp>9y}rI>ElN>;D=jNKeJv8 z=_MniI{pXvYpPe-Tnk|R>`e4 zH8o{KD|A_$tQ!>f2npX6*RPqB%Qp`s?8h2g<()NzxYs7>BwYURK!{5xbLd@@^*}Xyi|g{DW|n0%3dr=Sn${dUGk`x~ z=J~m~cquRD#&;&c7!_>LLQI-7mGT@Xy)!a0sw!ThlL!L^V2J5IW_a19;A!>)a$dZ= z7vzGDSq#)gpZ9lCc>2z)P1l!0)zUbp(mC6>hC}uBMI|6#^F~{g^liI$|4cIpE|0;0 z^trB19gt8FNMLC)+4T!5a^<1vEq6LTJltVur<=jzx}b!^QI(Z-uZlj3zH8i)J!#`* zl~@tb+*X6ROlWejDPx3IXIJ*M3g*edIt7%}3)ue4FkpBxiIlXp z>EQxe+S%1vRfxrIJtTj;Jbn}~&lqFw+>fW+tUB3eIu@tf>I!eKU<#ilT`KDQe4pO( zoc~hW^1N$Z{rcKdl*5K7UX_ox9@@spsCg+IGsq3}o%jpUPae>*B;jEKh8f49^g5dK z#6SM{V{mxa?%mp#n(zXe^p-z`EqjDw~rlR&V$QtcLKvJlS6*r5dlI1Xx7R!s4ovzC&dkbDz(%XD{%f zY@D3ulM9v{!@|#^Iqx2j_9J3v$KJFR+6TfbvQY2lyUEe52eC} zVq;=@8f-6Jig29z88_Zrk$^sIT>s`p)m)p)yd192>~Q!?#enId8tp;8gLWeR%draH zXmsJdS#@JL%vV4t@xXPpuQi_cN+uT;7CN~;T*i<2TBEKPhb8rnLT6Lg za~i!*X6(_<;`5eEPI+j>DquE>C*v{sR0Z-~3%RpM;5j{~8aW|4v57z8)svGxe?E6a z<84%X=;ghxi&~#$vM@MuiipeyDQ7gJmDe6CV&mtp z8I>6Q^K)WWM4ioSx$ycOTqm@qRl$1A?LLuP)d$o0Zo z=r?cfn8DiZ?a7lTK@XJh@t;fLY^zvR^stPqtO}C4=H=O^qNO#SDtdCm6G8|BYh&bumrYMu;I1IuSS;<>#6&fo6w+w- zv;JAHN=~`q^M?-~D*rksCs%H@!#wFwnEk1O<)xgtsi6d~lXa@z$6SsA`C&Tp;w0L;VA%6jI!-(idFg{X0QRm;+4IoI)Y}vY% zsV-)>go}2#>rz(F*HBJG3C4NRinx^0JVaAtn$h#&{CQJCd-Ya?+bt-F*ijql>FM2n z`0xe>957sWsi~>AZQEx3A@$Y62M^+L`d~`EPn|lIX5Mk?Q*rSPyazZ0KkNRv$7Bfv z1k{iK0KeXT+=rClmKV)p<1Q_YdbzTRi|Yc{)NWM}eSt&n z2PhK;6^Du)iF}!V00@dPAlthCC5SK%pfZ!jv`Q4~SoFwvU?4!O6EBKi;}oyx>+>6w zY!jR7i_FG=<{J|78uMPakWF>oExRLH?_|+=mD}>2$sc;dU3K^F-79R~ej+RdATg?JT{UGuYE$NIgeACP{B&r&*f?$HHhW%?aE4!OE64+;nM_w>ZbhYA}_^i`I8 z`BIr?ROJ&EHXKl<^2AfDcxb* zum!*btg}BsUVvpHF)Ef;R#jl02*yQOaVC_usHmtl^3J%>jVcH{7m!WNa7zyOuR)A& z&w$b-J$DD1F@bv`74*lCA1ZMd4v>KBEb{nWa0*a_`$5{&MS6+H>*hHceg0!FLO~^e zq)8$Y!+-Yj(mcO@(T1$7tT>F(IOYPwvVgs=uCBhEc9WeV5=%3Yog<}$)I)bUzL-SD zqfrfdCZL;QXrK={{!A&h{_$~L z-o*FwtG!Qbdu=bzpQR8gC?jX=%|xGUg2| zrQGw*T@iiwbDln@-RQ4hM}b#(@E3lRuyVU6PdP`*{w))5sV&O_0B|V z{L7*6<6>rrpBO|O{vguQ?JLp>G-O+DveT-6xH@MfzsYH$QYr>3QuN`&hiZ_zgk^9D zTdq=&2>coXGIJ!x{?R?}nng{&8| zn0rA%0XakzbK4-v_W{eTHg>be4iAo~PCr}2h`g~dP-FvPU5s#V9nJ787I<*>F2E$^M;PMHpai5ar!2p%~( zIRJn8pzoBz>VAQc2}Fti^URqu3}}=XgD(XMnwb`Pv5aJDy*)b5T89b03_V89-aeCX zGf1T#TuH%03Srg*c6oMGyR3{@iavb`eDmgLXGh0n(79K|7bmJj=f4Gypz}dU^xG7a)3NQF{C9lEY`g8v`H?yP!P=5lSLX zH!qv45T-MJWN>N5@_sMTD*#r8z#wv{mvZ=RxevSYZf6=t*UY!8^||DXCc#w)q0H>0 zqoXUH;875p^r>p;%^LZ{+0rM@Ulaf_D2Pn+$4r~+?HwJ1Xy`ctKvM;SJP$(ng8JJj z;gZX)o)prMX%Sgvh5mS%5G!F$W71Muwzlsax_#AUuU?JnOmB9XACeEJoy{vIlpX30 znaT-wMH<-b++gzfqNw9Y-ZbV}8+UZqIIWB%Kuph_$IaVq+EzJrk2t&7?G2Z3ZFOfB zYeZUpA_oK>AP~^Jb7UmESH6`~XEtN7q`Rcf3JSsa$E^wh?d_L}M}Bx8IG>d5v8#c_ zs;7(xf({>ulInQX(vP?#m>+vZ_TT66kD2$rs_QiRNpuizYVa~LlF+ZaU4wIM1`QEm z<0wyuQ+HZdYJ^CiGJX;~UbxTf(-iy^fA*3hc zdT4?LK9K*?q%44kB&hfE=gSiYTh%_WU!wRu3k-}CyEWC9kevU0th;n_tdx6)#>@)s zBj^JlABi-Eke&chI`C7b-$H61COu|0D>{dW~J-i>7 z01dj;>=#)E7-ZXd4+B5#*06*WKHz{yKRlg|f|KV!|5iEa-mavr~)X(QpfA;BTG}T}+hw4*Kd#Bx+Hr2_suZ&Yb<=D4>Ka+Ek4`L1nAG?Y| ziAZCg1REKNPagCX8CFE9va<8@t9lc4G&UdD*XH$kIXPb>B3OG zVG1UM3*k;G~T^?_W*mvvs_#_5l@=cYt{^+?Fl=KUA^s`bTy+1tKORi zUxAN6G%tlw|Jap}YYBou?Yn@Ht!nPkqqP{`ImN|WD{VXeTt!x&ikQ|$wwAtN1K0S>g-v6dRFd0bEE923R$*>G`!(nD- zhB@c>+(u^6i{RIlFoF~4xocMf2>W(EV4Sg6Ih$ttvw>KCWiK+YM-?!gtOuqBp! z6m>gr-{Bs9cAlBnkx8b)uM0i){|}Z6pArX3;+0Wzs{oJ zQ$opvY&6+dc^YNqZ*GbERgS^sej{TQu1i&wu}FP~+Ub$Dwi<2(bYZuQ0Y%iR#~ljp zYr$6g{cV%%?CkQTpk*NU5-c9;a{9privdAM=;}UnSeBP1{?=52`-Fux!78pwqJwe7Irfj~P4U=sHOu2QHG2l=89_l~+(uzj*QD=(deGGuaYr%EH2U1ySLRG33?#Yy$5L5qA(Q-Ky0m za$2}@T6C)oaBi6@f%&V=r+zk&j)S#xD$ge~I#Psw%a$K^N)X*L@^PZn0U)q2ur-Vx zXJ?njh^mS%Ze0CpZ~yoH22di3FerQ;U%0!5&KNTjN^LL}hm_$PXnw|xX>pjyI^Wiz zY$$HQg<3%0hoz?5KIE28D~<{IDk}Y*5EA7xK@idsp^NGP68W$m(au!Fm27-- z`7S2e3tjvu%L}SLCHvj#^g);sKA3cAOr3qOB6->X@zf#`YqG4aNooD9jisaLdb907 z!W+nRZ!Wy?*weEX%zpv|=K}{082x!~opD=!p6y8M5%?IORzc)=bs<_tz9K<0Jstz> z`rU%a(!jt#&tjzNrzu8KzRw44a3h*SXB#vz1n_-Ap~0sziL)&1-KmdNq|s*Cu_N}& z^Fup~67iBGHA4j|JuYx77?ChOyRot#KYnosm-aV2C3KDn^agt95%Um>&LVcGPARO1 z4(+(nf>(ZezlG`-q*xhwc{R+`ti-PcdL5KYZlr>Y%oCJ`8ZcVa#aKm{X{y5Pcu~zWUm78oX7?cYn4F|!XO|@vT|V($C~L~&Y&-o{pdR(DR2NX=g)KcRlR>& zAbNqd^JCg;CDu-S^!9DjZ6n_cx73k-)?NSr`~Q%6@ITHa;KEKyhDlQlkSyD=V=8d^ zajLyGf1P7L`jXh203u2NQ_LIvPQYe`il7Fa9W^}`gn>Xs<%d#0O5##WV%r5kTJO_Ov4@{-a->ZUS+H>IMy)L1AfdT#Q++ed0PnLyDhrp1OZni6OQtg-NMHg-UA-m+I)hNIpo-@n&uLjj7kXP+_Bg{=69-6C>jdfJp3P|JPQie1RJv zq42=JYK2DMS)F0o@QxxHu9z7l7ckeMpa9a~9?gSWqU7^uMOa@5M+yg$66BwxTR2Y2 zfe4k=h0txjFetarB_uwN0_y-ooc^j5ImSkRn1^l;p0$*4W<@(-u8ie49{F{>r5wkQ zbOipM(M;%Gn1V0@)R4h5J03-XQfj3^3E{utr~kccX2u@m>LB<*Wf%>JSFujj08hCX zR%%L(<+fN^h@wZ-vw`NUQ~;DX_z3_0E)AOtVdG*K64LNavmMeTO!N0>5!=+%R2^0H zAh+(UpKC8cRHWa!H4?;HA4=pCPftJAp_UwV!4~W12?UC~5%phHsIKcke0#!{U5|(Z z58TALY-?kCDCtQYXefna+FK~xy1B24nQzH%Jt(UPfg_|AcB&Hk19*#e32jT-2oOD> zf`^~T0THw_YTIr-hk}`Q=vjm3afd8~M~Q*ChusP63+xdwF?P%@ICGHJY4s z5bB(E%8N&j9^HBRreOjKdge>_eZFB~Tp<0Tqoco6R3v{c)?4U)wr}U57z2-eJJ(R$ z-bQ3DyZEo>5}^+8@#%{H{qY>ywzQX$pg;6s^i+XKF+q?PACPsFf@oJm9QlEt#5@tP zUy3WV)#=CMLlY2Q|Gusol-)>${KY9PlZLlvL34Rdf!!pi@9(Q&OWO`=Ee#D97gk0f zxV+M(^hib@VC+AhvGNy4W@U)aV`Wa5HS(dssP*^jdYJOgv_+GMin@;d2WpaZv|v|K zhil6ReqoeAoXL5P_;t|-4*IU9L>xBPp!h`L*Tzc=vq{aYSc#E-`; z(IrUjq@V+E0DTd2By22-H*daXcI?}$?u#J1oQS&i)Hk|vjtc&EX28I}03Cvl)H6n@vXU1<)4;D}&=7!ZpPCHl*gr%$Uy zMskT(k)x;&GFcAJ1w}wM%+*-7j5RLDvaGf(X+u#>>edUJhl4BZhv=KVT_=-0KN zPhrC2l>QDrfJiS7nH|rO$!+2mtTJZyyHG!6E9xdP7h3lixn~(> z=BkDNHKCpxz=yK2bA_(?Mvr|F|Bk#F)v&tlLa5{n~`B>Ve+;6dsS8U$VhTpTG|yMrrep*{b$8{^rU4=84u`CQwr}q znIEw;QhZ%US%TM97^KE;H$VD5+t+f^u!Oe_vbO^s^bzATq`JNO?xeKBD!;caAM%g3 zg&zvI+cH}%kDGosOx;YTVeP1kD=tClnSf|{&Kq$Pi|mR= zqyCg7jWf}{TRKer^~)EFnixDXr{@Y?-Gjd2I9a1U2{+Ye!_wH zRtmCxe!dUGjXniyRo#itWMMttVz^TM>=a}n1*87eVPN-kfI$^zoH#; zv`v#qh7tG}m{5M(4nR8+BLWJb5}K(h5YP`U90Fxo>1V9l;=)2T{Ltw}Rgz?^g6$xt0k4j?mKU1-t0#3aPPq!Z-g6jb2Tmq2M28J^` zJ^%6kR7c_dCoKYsdTw4GXj3i^aKjiiuv>xbApuMOG>3%K!o$)-cc!MN)eBq}$&MiC zD(Jz|$YLuS^5K4}KkF#E;L-Dy0dH8PF^`jR8YCOx*46#6=9i4PXx+d&c)A`scre?t ziX1Xs{(1&nv;T41nt=cXM4Pfe@R15$rx|nU?;usiUf)0|U8fF|rnsOjOc%k4tzrXsEO+S zK*8W7f3mli>SUUq1Rku0lD7}D;Pp0aDS&(7C3pd2qC2}R%%~QF&>?7`3Yo6hcyS*k z>0-($WDhyO-6mi(iTXrbd~oty$B__PgqM$R{q93tT=K-%+C7&1j@0raH;WjQzn|Tf z0dwhRSQ-dGx=o1lZOqI`5a~{!)_%=Ufb&QBgzRsYG2r|6?_JPCx@@?Y#?C$SnpNLY z^;S~aA2=)m`U3nvJDM=vN0jNV2p~I$2;YNAz_!K(0x7JRbzm82enPA;$Y^?+eWsM7 z#O-{*N=o~j@2qkm{3NOXqSn9{dg*PzUp{D-*ax(Oz%WH#Fq>sC2{dO}@nf39&a1Y& zp*wf(B!wNO&y7sNQ4k1TL7w`H1d(}j1}(vr#E_k zXBDOm9}Iiju*DBX%#ymgjC=8bI_4g{OcW8j;nxep7Nf`*EWS*{KD?PXc{8ls9&YMA zc=(^UIU39z+;mY&NsgoZ-ggRxvwiJfqK!*y%JRt zE=~?^Zf?Y{Jji11_fJSCi7Z=vLfZIS#O>eg15ottK{B2ZvE$cc@s*=rQ2s<@U-aQrJ$%OA1{qgC5HfY}yqa0OK zzrY!zVyi*~nwuU;)^nLX>+J(r0Q_x(9V(S|e={Btx6|!T14Bb_Q7uL|NH!Ck&`qc? zgxmt~=*M`V3iS--GLaap{-J>Nsw8UGq;N@CSXhirPum?v_4qXRn=3pWNb82|B9t4W zz|B`=)$Hpa}pWzmu7(N2uy?A{{fl~T*Nqg zVw4l)&NhG8?ub-E@DN;^#Qzf=8+)U@@HUhaxDI?EX2m1UUqRan61Is)$;F(l{5=|B zye)xu7sVBR5n^thLyuL3J%db&hBdV?v}2fmbBr%I3yqrp`03E0&K zC+wj;5*{54cw)|Qc4?()MiV_UcYx@WGO~eU)b`Op4bm5&-~8U^NmOeB8X%5U62tFH z#45#~JynXrea*eHuQJl6n8}Wfm-l-mGbSU9jOysH5TMBp-D;R`$aG?xBUW)7sH3E; z%$g`scG8P@le1s8P?#@=LOu26afjKkP` z>y2&PkW6C9zGjRj*x0S4qT&OyF$YPXR|JFbqb40I(zeNi42`Sa`_dRkxFlS%Ah!oU zm=g=Y{}AJYN$Dpkl+Enqz5xGvNB-~Bp^RmaQA0ot8F^MFi?V@#HEh zCcXPFtilvd{ScdO9u!h28npXbmbYp1v#S>=Tfp>5KUlQ~-=1BaVPM$zmd$VJI^({V z{eVOVUzOnAy{66NU?G!{q=J4OcOV|LtB+@{YifU`+N6& zc|&2vH}4JW|MNpL>b?73?x@u@v+U?Jk}WQ#{&N5S@-d&UXv|GFHV)cx$1Og0S!E8V z)vwXf+o7RMP(RJFU&M;;G5En(s?vGXnPzR`*c5mL&sj@L`w1~II@M&>iIMBqn6c}~ z#G^-j1qu&TbNu;>r!ohO*`su+v@=YZU?RK?JJd^%svtO5WZR8+VxN!|odP4@_okr{RWw-+mLCH@EvHTf168tMUIF~A6B|1)P26&mSJP#!&fdRj$g z8+Z&J?CktmpRxj!MF7a|tcQ|#Qnm5xt@u|iw$=)UwTB4_5N|Ht2I~2Noi_EU*XTg? zm+>;+xN!qdE-aE9$Zn?a@f69r^K3m`_JM)?*cG#hw{3+S{?5ABs9?qKNag%1QvXZf zUZ$#l00;M`EnDsYRJ?=*)E*evVsXyquK207*k5)kQSC!(D>E`7(`oi?pk(tDo*tpd zwh49=v*$d|(XTH-PBFK!xqtWW3i!ugR9>H$HznfPKFPWLqVTQ$S-fePn za&zK-VP6y^7Vlv@>l)*9Mw_)Ysy{~U=x~2!{vUVt?t9#!>$%aibI(!F@z2W|;+fW)A(zGrxPgozO zG%3ov=m=!@GgU+%+gB^W1C|8|mxIXTxO`fp6tMQGp@H{2V>UFwys{4}wTNB>Joe4F}^B63&9NI18I3gTT#z zM~@$GKJ_{93kLnOaBw`GHz>ObE%pjF`G6na=a?`e4amJA8N> zJS|tiF3dUmg{;}xNfWVCRZWeWg1ukRj}9DQ4ED_JN}r+xp_3q3jiBhO=E4xTxC7I>EmI9L*K zd)n4%AtP|@-c37pJcN{mO}r~8C`wcmFomm~u`%5O*g$|qUrQPn(Zxj^JO0el6pCbt z_cU^QX!2?vx&KD@*Dh{h)WrmsFMJ=t!l$6cbX;TcfiR$yL zFgZx{e<_ICze+fh873W^au3{^=-Z5W!43TN3kc$ruF|U*|KFsfoV2#KPW^qLhbiS( z0lYG;`?tV+?lHx_jpnY&-g`>H(U~@b+i}MRbFd@z4Grn_3Nf;XjZM3EyF*i@P_Wnd z9zGxD%~i@VvX#@%*`H%7-FxH+ZJX0L?ZU!BM2+$0UAxvJ%b?i}%zyEbwpx-H_um^i zH|HoDaFhyZT#w0=Z(i%lK}L_&kVVh9xGY&!ly4c|)4KA?b}Z2c6m|>1Aoyknnp8RH zDGYg4@vmMTb-ZTAevFZpgM(w!wrwkS?b=meU%%(jp*3*V-9aL_wDKDK=;*kOSMN%L z+E0htNa=D41(LdisX5^2W$M)%7~DXdI>FR`PDX}$&$WXJyar1gzBIz28===+Pw_es zpPXC_(D=~Xd&|{p*9O&>DOtQiHUr!6sqMINL_sZ_msD?Lxw>(QiT8bdJGx668#R|17^fl8hqRV9POOD(9 zKe(Ug=89g{*WV8%k_!ULC%D8=x$mAkf4*uO^$ZvM32vbC7$xrsmJc;(FK!F-5qmb0 zM)dmwRn*m=f+Tu`ueFpb*c|Z#T-%(8rd;4M6!LqxBL&%5h?`&RAmcY~OSK6#wY+q}HE5O?oF+%|ysmk|+#PIm?Fy6$k}HMDFmq#`NpBNN?+ZNKouKYZ`fz>Ai@ z1%ln5*h7NoUx&+*f_-0imp>m7dyaYg*sa9?Orx9rOq-Q(no-1kHqT1u{?lKHZ!@U_R+xvtZs?qH;#~XoQG<$1JkGoCW$g#*KOQY@MT=X5zJt#25*^jk z)06GU5$BF^E{(S<@gUP>QP`73Vrh${%$e$?6bkFh<$JJ%?m2pN)5eV(zg<6OZeelT z-@k5s_%sOTwd>d4&Bzec*3r@X^W5V{jS|2F6XE8tuity{fSQ6LGU@6Pgf%Cv8giW? zhws`A8$JTyP0Z8NtU}sigrGr>#l0B}E0odO(1Z)Wd|8JLSm$t8j)CWXeh)ymSpTfw zjC{lf;!|hOuA-b+dmDRKE`zRIU5sG=v!sMZ(6pIC0lL67T91we7yx$Tpuy=g6S+X& zN;#^Unl#t0T{|u!qK_D>&vzCH2@OrTUb*k8fx-KRhMnl=rlT`Z4fY8M?Zhg60*Cgi zR?BQJKm!aOki6gW&dkp46&GhVFff=Q8xO=3PMxBb4>^7tyF7P`Ic~#tiF=ZfJbjT> z{cmnI@RGg(9Ky!V{vNxEC=1HhamxaxxVX60#XlR<*F%mx3#ZE{m#0;a1rQAxZ_CFCo7U+U=$yL({ygatiJc>(9l^~S=pz>-QC^m zC@-_KHP`O_h?~^hPft%jZZ{mV>D1a#`9rX_?IQ-Xdz-K%8sr`nr(&zpcdy-Cd7exk z#u+zn-u#B0I@&q5v}kw8%o7d{LfEabnVA`P!f`AtFHdaHPl%5j}HKgbDuqu;_jenGg0;NwMKCFPcScLXW zp+HLhhzCb;8xu+)y7TC-j?*)9b3dTqoG~;!$jHbj%Y0~a<@hB`QbX84z!DM?vWcGl zKFZT^A)(9I)IS43xV@`uCG3^g!cMa;KM-?3hWq*W zwiZgT&`Jnc^~grW#&!ZNW5?vD`H=#;HEY&TPGFQEu+|B81bCB?QH&dhkTb{idR{DZ z<|+qLq1qHT16Zq>xE-+z7x_2i^k37{#XuxzhZfjdy4`E2I&N#VF+UQ8woxHm+ylGX z14SPoxZl^-ZigUUlObmRivla}+z?4^G_i|WmIvUze*$K5z>MPX9l9a#>my841WA&8 zLiJcfF9@u-hs!#|zkTBG&MDKrEJ+8_ym(`HJm~YTM$)mQd2*mi++P7^!$kg<&meSq?3~M@woxQVu^X9y5Ff8(6&NiQ) zGRe%z3BGmw3@bYse@r5hk~U%s{GZq~x{{90t9One=uw|Rr&J;S){c!=XT!yv6*V*- y1I7P-mC!%W^8bD6|EF4xM(|%bf&cQ?S(d769A`dVIY{&)iu76eGfAf|-T6NXRR3%M