Skip to content

Instantly share code, notes, and snippets.

@xmnlab
Last active May 31, 2020 22:21
Show Gist options
  • Save xmnlab/d676ff1b0ff474c634d62010ebca8b07 to your computer and use it in GitHub Desktop.
Save xmnlab/d676ff1b0ff474c634d62010ebca8b07 to your computer and use it in GitHub Desktop.
ibis cumedist percentrank review
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Cume Dist and Percent Rank"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Currently Percent rank implemented by SQL Databases and Pandas Percent rank use different algorithms.\n",
"\n",
"Pandas Percent Rank uses the same algoritm used by SQL Databases CumeDist function.\n",
"\n",
"The calculation used by SQL Databases for CumeDist and Percent rank are:\n",
"\n",
"\n",
"$$\n",
"PERCENT\\_RANK = (RANK – 1)/(COUNT -1)\n",
"$$\n",
"\n",
"$$\n",
"CUME\\_DIST = RANK/COUNT\n",
"$$\n",
"\n",
"\n",
"Current, [Ibis](http://ibis-project.org) uses the approach of Pandas, but probably should use the same \n",
"behavior used by the SQL Databases.\n",
"\n",
"More information about the difference between Percent Rank and CumeDist [here](https://www.sqlservercentral.com/articles/whats-the-difference-between-percent_rank-and-cume_dist)\n",
"\n",
"\n",
"Some Ibis reference about the current implementation:\n",
"\n",
"- https://github.com/ibis-project/ibis/blob/1.3.0/ibis/pandas/execution/window.py#L360\n",
"- https://github.com/ibis-project/ibis/blob/1.3.0/ibis/tests/all/test_window.py#L41\n",
"\n",
"\n",
"This notebook aims to implement a function for sql percent_rank using pandas and test it against \n",
"[OmniSciDB](https://docs.omnisci.com/)."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from copy import copy\n",
"\n",
"import ibis\n",
"import pandas as pd\n",
"# local\n",
"from settings import conf\n",
"from utils import cursor2df"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Setup"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"BACKENDS = ['omniscidb']\n",
"con = {\n",
" backend: getattr(ibis, backend).connect(**conf[backend]) \n",
" for backend in BACKENDS\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAZgAAAFACAIAAAAONP1AAAAABmJLR0QA/wD/AP+gvaeTAAAgAElEQVR4nOydeVwTVxf3zxBCWCXKIpsL4samgKAWFyi2agtqLRa1tiq4PT4utYK4K622j8tLa+1iW2pLLa7UuuJSFIVSUBZBlEVlkUVAQUmAhEAg8/5x3847nZAQSEgC3O8ffsidM/eeGTMnd86993cJkiQBg8FgejI6mnYAg8FglAUHMgwG0+PBgQyDwfR4dOkfKioqUlJSNOUKBoPBKIi3t7ednd3//0zSOH36tOYcw2AwGEU5ffo0PXbpSlvgcUwMBqPNEATBKME5MgwG0+PBgQyDwfR4cCDDYDA9HhUEssbGxvDw8GHDhrHZbIIg/ve//ylfp8o5deoUQRAxMTHqb9rS0pL4h3fffVcjTajBB+3njTfeQHdAT0/Pyspq+vTp33//fXNzszp9WL58OfKhurpane1SPHz4kJDN8uXLFalE/lVIN3H+/HlVXwcTFQSyZcuWHTx4sKSkpLW1FQCGDx+ufJ2dIjAwkMViCYVCOTbp6ekAMH78eHU59f8QCAQ1NTXUx9dee039TSjugyJ3shcgFoufP38eHx+/evXqSZMmvXz5slOnnz9/Hj2f33//fTd5iOksygay0tLS2NhYLy+vkpISiURCkuR7772nEs8UJysra+TIkYaGhnJsIiMjSZIcOXKk2rxCGBkZoeHhc+fOQfcEsg6bUNwHRe5kL0AsFldUVKxZswYAMjMz169fr2mP1IeLiws1ZWH+/PmosKqqCpX89NNPKmziyJEjytemIMoGsvj4eJIkN2/ePHToUOkxUTXA4/FKSkrc3NzU33SnSEpKYrPZ48aN02AT8g16yp1UCba2tt98842npycAnD59uqqqStMeYZSii4HsxIkTqHe9YsUKAJg3bx76+M477yCD0NBQgiByc3OpU86dO0cQBBWk4+LiCIL44osvUlJSJkyYYGRk5OnpmZqaymjozp07QUFBVlZWBgYGzs7O27dvf/XqFQC0trbq6uoSBNG/f3/4JwWGOH78OHW6n58fVb5u3TrpC+HxeOHh4cOHDzcwMLC3t1+9enVtbS11tEMnSZKMj49fsGABqmHw4MHLli2rrKyUbigxMdHNzc3AwKBzN1qlTbRr0OGdTEtLIwhiz5499LMkEom7u7upqSny5I8//iAI4ttvvz1x4sTo0aNNTEwmT56MXucpGhsbP/30U0dHRw6HY2Nj8/HHHzc1NdENampqdu7c6erqampqamlpOWPGjNu3bzMuYd++fQRBDB06tOMbpwBz5swBgLa2tr/++guVXL16df78+aNHjzYwMDAyMnJ0dNy0aROPx0NH9fX1CYKYO3cu+rh69Wp0o4yNjak65ddA8ezZs1mzZhkaGlpaWv73v/9taGhQvIaSkpL//Oc/Dg4OhoaGgwYNCggIuHTpEn36Z319/bZt20aNGqWvr9+vX7+pU6eeOXOmU3dG+avoEOWd/BfSM/tJBdi7d2+7ta1btw4ZeHt7m5qatrW1UaeEh4cDQFpaGvoYEREBAGFhYfr6+tTp1tbWra2t1Cl79uyR7uUdOHCAJMmSkhJZV5ScnEzVQP/GHz9+nHEVpaWlQ4YMYZzu6+uL3pEVcfL69evSDri7uzMa4vP5LBZr/fr1itxbBqpqQpZBh3eyublZT09v1qxZ9LPQD9Lhw4fRx23btgHAggUL6KebmZm9fPkSGVRUVIwePZpRv7+/P1VhTU3NoEGDGAZWVlYMb9FQ0pAhQxS/gRTTpk1D1YrFYlRy6tQpVPLJJ5+QJCmrX/bGG28gew6H064B9fLeYQ3Lli1DJf9aXgMwc+ZMBWuoqamxsLCQNsjKykIGdXV1Tk5O0gY7duxg3BDpV0tVXQWC6rWcO3eO0bTiTrYLSM3s72Igo7C1taW/dSNaWlr09fWnT59OL5wyZQqbzRaJROhjQEAAAAwZMuTs2bN8Pv/x48cjRowAgOLiYmRw7NgxAOByuYcPHy4vL29qasrJyQkPD//zzz/p1YaGhgLAw4cP5Tj53XffAUBRURG9UCKRTJo0CQACAwPz8/NFIlFaWhr6j8nLy1PQyaNHj4aEhMTHx1dWVra0tBQWFk6cOBEABAIBva24uDgAOHnypCK3lIGqmujQQM6d9PT0pMeUV69emZube3p6Uj9UM2bMAAAHB4fr1683NTUVFxcjJ0+dOkWSZEtLi5ubm46OTmhoaEFBgUgkevToETrl7t27qIYvv/wSAAICAnJycpqammpqaqKiohYvXszwRLWB7OrVq6hk48aNJElWV1e/8cYbMTExeXl5QqGwurqaesN4+vQpVQ9KNQLAkSNHGE10WAMVAhwdHZ88efLkyRNHR0dUkpKSokgN1Mj7//k//wcN4yQkJAQGBubk5CAfVq9ejQwiIyPr6+tLS0vffvttACAI4sGDB3RvZQUy5a8CISeQKe5ku6g4kJWVlQHAihUrGOVpaWkAsHv3bqqkpaXFwMDAw8ODKrG2tiYI4s6dO1TJypUrAaC6upokSZFIZGVlxWKx6AbtMm7cOEtLS/k2QUFB0jZXrlwBAH9/f6r/Rf7Ts7h8+bIiTpIkmZ+fv2LFCgcHB/pvNZfLZbSFeqP0h0FxVNVEhwZy7iT62pWXl6OP69atY7FYGRkZlIGFhQWbzX78+DFV8ssvvwDAF198QZLk119/DQDffvstvc5bt27RCw8cOAAAcXFxstxTHulAhr4DABAaGkqSpEQiOXr0qI+Pj5mZGYvFAhq3bt2i6pETyDqsgQoBsbGx6BSqV/j5558rUsOlS5fQx8WLF9NvOKKtrc3U1BQAJk6cSBU+fPgQnYI6nhSyApnyV4GQFcg65WS7SAeydtZaKg7KFqHfXjp37tyBf4+O3b9/v6mpCeVWAaCysrKqqmrKlCkTJkygbPLy8vr37z9w4EAAuHnzZnV19apVq+gG0tTX12dnZwcGBsr3MykpSdrJEydOAMCnn35Kf3uVSCQAYGJiooiTly5deu+996QnIo0dO1baAWtra+nX2A5RYRPyDeTfSS8vryNHjqSnp9vZ2eXm5h45cmTt2rXUoEFZWVlNTc3SpUtRdxUhEokAwNzcHABQUFuzZg0aKKRD/pPZCQkJOXXqVGBg4KxZsyZPnjx16lQ1DDvU19ejP9BztWXLFhRPpUGX0yGK10D9D44ZMwb98ezZM0VqmDFjxmuvvZaamnrs2LFjx47Z2Nj4+vquXLnSx8cHAKqrq/l8PgDcvXtXV1eX/uQDgJw0gmqvQj4qcZKBUqOW0gELgdLD9OcfJXqoQIbSwOjlAiEWizMyMqhTsrOzAQD1NuWQlJTU1tbm6+srxyY/P7+6ulrayYyMDENDQ3d3d3phcnIyi8VChfKdFAqFS5YsYbPZX331VXl5eUtLC/WrwqhTKBRmZmZ2YeKFCpvo0ED+nfTy8oJ/bsiGDRsGDhxIz/1nZmYCAHpPp4iPjwcAT09PsViclZUlq11bW1v0h5mZWUZGxo0bN7y8vK5cueLh4REUFIR+V7qPx48foz9Gjx7d3NyMeo5sNvvYsWO1tbVtbW379u1TvLZO1dDuEL8iNbDZ7KSkpOjo6Hnz5tna2lZWVp44ceL111//7bffgPbDgDo+6G2DOpcxutJNV9HhUeWdlEapQJaamsrlcqWTuE+ePDE3N+dyuehjQ0MDmp9CBbKMjAz4dwTMysoSiURUiUAgAAUuCamnod8iWaCRL+keWWNjI+NGX79+PTk52d/fH/XI5DuZmppaV1e3du3a9evX29nZsdlsiUSydetWkIoyDx48EIvFjEJFUGETHRrIv5OOjo5GRkbp6ennzp27cePGoUOH0C1CoBvVr18/qiQ3N/f8+fNOTk6Ojo4NDQ0kSc6ePbvddwQq+QIABEFMmjRp06ZN165dW7JkSWxsLPo96z4uXrwIALq6ulOmTKmrq0PfNycnpw8//NDMzExHR+fevXvSZ1FfG/LfOjGK1wD//FQDQE5ODvrD1tZWwRp0dXXR/amoqMjJybG3tydJ8tChQwAwcOBANIRKJebpUC+AclD+Kqij1BBZXV0d/SzlnWwHehWdypE1NzdzOJwZM2ZIH3J3dycI4tKlS6gj4Ovry2KxOBwO6lOQJDlz5kwWi4W+4oivvvoKAKhEfnR0NAA4OjqmpKQIhcJnz55dvnx51qxZ9DFNkiQXLlwIAFeuXKGyHtLMnz+fxWI1NjYyyqdOnQoAe/fu5fF4PB4vKirK2NiYxWLdu3dPEScTEhIAwNvbu7S0tLGxMSUlZebMmXp6eug/ld4Qyij7+/vX1tZ2dFP/hQqb6NCgwzs5ZcoULpdrb28/ZcoUxiHUaX3ttdeePHnS1NR0/fp1e3t7+CfTL5FIzMzMjI2NY2JiamtrUaY/JiZmxowZBQUFqIYVK1bs2LEjOztbIBDU1tbGxMQMGDCAIIiKigpGW6pK9ldWVq5duxZ9XLRoEfITPV0GBgYpKSn19fU///wzlSG6evUqVU9iYiIqXLBgAf17pUgN0mlyavAuNTVVkRpSUlJmzZp1/vz5p0+ftrS0PH78eNSoUQDg4OCA3Fi6dCkAEATx2WefVVRUiESikpKSCxcuzJs3j5ozgGg3R6b8VVBVUZNapk6d+vz5c3rTijvZLqDCZD9KkNEz+hT/+c9/6LEyLCxMR0dn/PjxlIGFhcXYsWPppyxYsEBHR4fH46GPjY2N6GGgIz3nAHVPKFauXInKqa+aNImJicjm5MmTjENompuCTjY0NNjY2NBPnzt3rpubG4fDYcSC8vJyNptNmbHZbEY4loUKm+jQQNadpNi4cSO6Renp6YxDFhYW8+bNQ+kwiuXLl1MG7b6Y6OrqUj9s9OQaxZYtW6TviUoCGR0PDw8quG/ZsoVx1NLSEv1BD2QNDQ0op0ZBTb/osAYqBAwePJhuRk1c6LAGKjow2Lx5M6rhxYsXslaw0KMMKTvZr/xVIFpbW4cNG8aoKj8/v1NOtguoMJCh8XL6fzBFbW3t3LlzTUxMBg8eHBkZWVBQAAD//e9/0dHS0lIAWLVqFf2UIUOGODs700sqKiqWLl1qY2PDZrOHDh0aEhJCTXqgqKurCwoKMjc3R719NMWM/KdD1y7U0BsyGzNmjKGhoZmZWUBAQFJSEnVIESezs7P9/PxMTEzs7OwiIiLEYjGXyx03bpz0DYmNjR0zZgyaiTpy5EjZN5WJCpuQbyDrTlKgW4o6L3TQjYqMjExJSXF1ddXX13d1dT1y5Ah9LJgkyVOnTnl7e5ubmxsbG7u6um7YsCE3N5c6mpWVFRISMmzYMH19/aFDh86ZM4cxyYZCJYGMzWZbWlq+8cYb3333XVNTE2UgFosPHjzo6OhoYGAwZMiQTz/9FH3Jpb/niYmJr7/+OpfL1dHRAVog67AGKgTcvXv37bffNjQ0HDBgwIoVK/h8voI1tLW1Xbt2bdGiRSNHjtTX1zc0NHRzczt48CD9l4nP50dERLi5uRkaGhoYGIwcOXL+/PlXr16lz+skZQcy5a+CoqCg4O2336YnIlAgU9zJdlFlIMP0HSQSybRp0wwMDEpLSxmH/vjjDwC4du2aRhzD9E2kAxnWI8N0QHV1dUhIyM2bNzdt2sR4lYB/hixdXFw04RoG8//AgUxjyJGFQqBxKA2Sl5dHEIS1tXV0dLSrqysjj4bIzMw0NTWlj1VhMOoHBzKMTNDURBMTk6CgoPj4ePqCU4rMzExnZ2e1u4bB/AulZvZjlIHU+t2q0KJu+TYvXrxQjzMYjBy0pUemQinq3NxcgiC2b9+ufFUqR19fX/46BGUoKSlZunSpra0th8Nxdnb+5Zdf2p0ZT5LkxIkT6ZpLGExPR1sCmQqlqB88eAAA3SphqBFaW1sJgkCCHNJkZ2e7u7v/+uuvSCQjLy8vJCSEmm9NJyoqSs6aIQymJ6ItgUyFUtTaHMhEIpG0WKDySCSSxYsX8/l8f3//nJyc5ubmJ0+erFy5kiFdAAAvX77cunVrWFiYyn3AYDSItgQyFZKTk2Nubt4FqYmey+3btx88eDBu3Lhz5865urrq6ekNHz78hx9+cHV1ZViGh4e7u7urf18FDKZb0XAgky9FrYgcNloAMHDgwH79+q1atUosFqNHmjKQo7Dc2trq7Ow8cOBASs4FAI4dO8ZisWbPni0Wi+kNKSOyvHTpUuoypXNk2dnZBEHs2LEjPT3d29vbwMDAwcGBLtiNUlpojRG6JwhqteCNGzcAIDQ0lL4OSZrU1NSYmBikbYDB9CY0HMjo2kPtKu0AQFVV1bRp09LS0tAS9MDAwLa2NmRQVFTk4eHx66+/vnjxoqGh4ccff/zoo49KS0spmY1nz555eXnt3r27oKCgpaWlqqrq0KFDVH9EV1f30KFDL168oERpTp8+HRIS8uabb8bGxsoPCiqnsrLyzTffTE1NFYlExcXFixcvVlz74dGjRwDg7e0dFhZmYWFhZGQ0adIkJAlL0dbW9t///vejjz6ixDwxmN4DfZq/ppYotStFTXakNC2RSJA02LJly4qKioRC4alTp3R1dQHgjz/+IBVTWCZJcs6cOXp6eoWFhefOndPV1fXz86Ovv6NQZpUfBYfD8fHxYRSi1Dubzd6+ffvz589fvHjx4YcfAkBYWBjdDPUQ6Tr3FH5+fiwWi9GlJQjizJkzlM2hQ4dsbGyQmAdqcc6cOcpcCwajKUA711q2K0VNdqQ0jcQap02bRj/F29sbAMrKykjFFJZJkiwqKuJwOO7u7np6elOmTJEW/FEhcgIZXfYXbeAcEBBAN5MTyJBQEpfLjYmJ4fF4VVVVmzdvBoBhw4Yhg8rKyn79+lGC/TiQYXo00oFMK5L97UpRI6XpyZMny1Ka/v333wGAMV+subnZwsIC7cdDKSzT1/28/vrr8O/JqMOGDfvwww+zsrLGjh0bFxdnZGTUXdcpF/plDhw4UE9Pr7GxUcFzjY2N29raNmzYsGjRIlNTUysrq3379rm4uBQXF5eXlwNAaGiou7s7Y5cjDKbXoPlAJkuKukM57Hv37jEEtZ8/f56Tk4My/QoqLANAfHw8mohLkiR9g0I1w2iaIAhS4an/aIiWMUaJ5rK8evVKKBSePHkyMTGRiuZIKvbChQsEQUiLT2EwPQ7NBzJZUtQdymHX1dVxOBz6drOff/65WCxGmX4FFZZv3bo1Z84cFxeXvXv3ZmRkoE6cdoKkr1paWqQPoUum9qFBIEF6c3Pz7la+x2A0juYDWWJiIovFQttb0MnIyGCxWPS5/oy9TmxsbEQiUWRkpEAgqK6u3rVrF0qKoR5Z//79zczMEhISjh8//vLly+bm5sePHx8/fnzmzJlojA8AkpKSAgICRo0a9eeff27evNnJyWnbtm2ydktW7R7XXUBHR2fAgAFpaWnp6emtra30QwEBAQYGBgcPHjx16lR9ff3z58+3bNny8OHDESNG2NraGhsbM+I4PUfWqf01MBgthf79VluyXxEp6g7lsL/99lv6ifb29ijTT2nAyldYTk5ORmqllNLxtWvXAGDTpk3t+tzlUUtpTW2KkpIS8p+wsn37dvpZ7Q4LvP/++/TT16xZQx3av38/o3IdHZ3z58+36xJO9mN6NKAlyX45W9chkW+0VSLjfTM1NdXR0ZGSS1+1atXevXuHDh1qaGjo7++fmJjI4/EsLS2pbdw3b94srbB8//59Npt9586dt956a9CgQTdu3DAzM0P2M2bMePvtt7/66qvCwsJuuWylOXz48KJFiyhBajrh4eHR0dEeHh4GBgZGRkY+Pj7Xrl2bM2eORvzEYNTMvzLKZ86cmT9/Pqn18jIYDKYvQxDE6dOng4KCqBLN58gwGAxGSXAgw2AwPR4cyDAYTI8HBzIMBtPj0WQgO3r0KEEQu3fvpheuX7+eIIhz585pyiuV0B1y2xcvXpw6daqpqamJicns2bPljPwqAxbjxvRENLn5CJq7z5gKiwopHZ4eispVag8ePBgeHk59vHTp0qNHjx4+fKhmrSH5tLa2stlsf3//y5cvSx/Nzs729fXl8/noIxLjdnd3d3NzY1hiMW5MZ9Fkj0w6ZrW1td2/f9/S0hKt+u65qDaQJScnb9myxdDQ8Oeff25sbHzw4IG9vf3jx48TEhJUUj8dLMaN6YloLJAhKVc7OzsrKyuqMC8vTygU9vTuGKhabnvHjh0SieTbb78NDg42MjJycXFBckb3799XSf1qAItxY7oVjQUy9LMs/72yQ6nrDg1IkoyPj1+wYMHw4cMNDAwGDx68bNmyyspKdDQtLY0giP3796Pc04kTJzIzM52cnAYMGHDmzBmqEjli2YgO5baV4fHjx4mJiQ4ODosXL6YKkZARY1koFuPG9Fk0liOTkyCjCulS1yKRCACQ1HV5eTl6JenQID4+ni4EVF5e/vPPP2dlZd27d486/eDBgy9fvgSAbdu2EQTx9OlTANixYweaN/zs2bM33nijoKAA1YDEsp88eUKlgYqKil577bWamhr08ccff2SxWKWlpR988IFKbhSSXfvggw9ycnJmzJixdOnS/fv3o0wTtVpLPSAxbtQ0EuN2dnaWznC1C12M+9dffxUKhW5ubtu2bfP396ds6GLcist8YzCgwR4ZkhtjvEVmZmbSC1GgiY2NPX78OCV1XVVVVVZWpqBBRUVFSEhIfHw82u2xsLBw4sSJWVlZQqGQam7z5s21tbUuLi6lpaVr165taGjw9PREcqxisTggIODx48cMsey4uLi0tDQAIEly0aJFNTU1dLntqKgoUF2CLCkpCQD8/f0vXrz44sWL6Oho+Ccu2NjYqKQJAIiOjkaLbzkcjiybmJiYtWvXUmLcEomE6pQhFV9pDdtvvvkGGfB4PBaLFRkZGRkZWVtbKxQKU1JSZs2aFRsbS9X/zTffvHjxYteuXaq6KEwfgr6CXJ1S12PHjgWAV69eUSVisVhfX9/W1pYqkS91rYhBfn7+ihUrHBwc6M8nl8tFR8eMGTNgwACxWEySpJ+fn5WVVVtbG0mSvr6+kyZNIhUQy+5Qblt5zM3N2Wy2SCTKzMy0sLD4+OOPSZL08PAAgIKCApU0QQeLcWO0H9ASzf6WlhY2m+3g4EAvRK971Hf32bNnADBlyhS6zeTJk/v376+gwcWLF9vtX6AHtampSVdXNzAwEBkPGDBg0aJFJElKJBIul7tixQryn3jRLt988w1JkitWrACAhIQEug/jxo2zsLBQyY1qbm4GAMaNqqysJAhi4MCBEolEJa3QkRPIPvroI3qhnp6er68vvUROIEP7yERERNALXVxcqIi/cOFCers4kGHkIB3INPNq+fTpU7FYjL7HFOgdilJS7FDqWr6BUChcsmQJm83+6quvysvLkQYZ0lBFQs/Z2dmtra2TJk0CgEePHr169Qr9nZeXx+PxvLy8FBHLli+3rTxow01GLuy3334jSXLu3LnSYj7dChbjxmgtmglktbW1ANCvXz96IUqXTJ8+HX3sUOpavkFqampdXd3atWvXr19vZ2fHZrMlEsnWrVvhn0CGTkevgWigk/73a6+9pohYtny5beUxNjYmCIIaSQAAHo936NAhgiDQS7RWgcW4MZpCM4Fs8ODBAHD16tWUlJSWlpaKioo1a9b8/fff48aNo2f65UtdyzdAD1VSUlJZWZlAIEhNTfX390cpLRTIMjMz9fX10ctjamqqsbEx6iGmpqaampo6OTkpIpYtX25befT19UeMGFFeXn748GGBQPDgwQN/f/+qqqr58+ejq6CDxbgxfRf6t0edyf6ZM2cyPOnfv//9+/cpgw6lruUbNDQ0MAb15s6d6+bmxuFwUHbfxcVl8uTJ6ERXV1c/Pz/0t6Oj4/Tp09Hf8sWySQXktpUnMjKS4YCrqyt1E+hgMW5MHwG0JEcGACdOnFi/fr29vb2enp6tre2aNWsKCgrGjBmDjnYodd2hgbGx8ZUrV/z8/ExMTOzs7CIiIs6cOfP06VMXFxddXd2mpqb8/HwUdBoaGnJzc1E/jsfjFRQUUJ0+OWLZyKBDuW3l+fjjj/fs2TNo0CA9PT17e/vw8PCUlBQ1zyBTHCzGjdEIWOoag8H0MLDUNQaD6YXgQNaNEEpz6NAhTV8EBtMDwIEMg8H0eLQlkJ06dYogiJiYmA4tu0N8FbpHGZUkSbFYfPHixffee2/w4MFIu2bz5s1ohhqisbExKirq9ddft7a2RgabNm3i8/no6IYNG1TrEgbTK9GWQIam6dMnhclC5eKrytPa2koQBFqFw+Dy5cuzZ8+OjY0tKytDq9b3798/ZcoUSgjoyy+/XLFixa1bt6qqqpDBwYMHJ06cSCmpYjCYDtGWQBYZGUmSJFqzIp9uCmTdpIzKZrMDAwPj4uLKy8sFAkFycrKTk1N2djZSyAAAY2PjJUuWXL9+vby8vLGx8fr167a2tvn5+Tg7hsF0AvqkMnVOiO0yAQEB5ubmmvbiX8hZLC3N1atXAWDhwoWyDND7NUNYAoPBUID2TIhF+Pn5USN069atkzZQRHxVm5VRZcFYZ0rH3t4eAMzNzbtwORhM30STuygBAH1PM/ryb0R3i68qiDLKqHQEAkFaWtpHH31EEMSiRYtkmV27dg0A5BhgMBgGGu6RoaV83333HQAw1huR3S++Ct2vjIr4/fffCYIwNjb28/OTSCRnzpyZMmVKu21lZ2cfPHhw4cKFb7zxhqquEYPp9WhFsv/27duWlpbDhg2jF8bHx9+9e3fatGk//fTTsGHDDAwM5s+fj4Y1GSI5W7ZsIUkSae13B+PGjdu7d6+lpaWFhcXBgwcBgJLw7wIikai8vLzdQyUlJbNmzRozZsxPP/3U5foxmD6IVgSypKQkRncM/tl0gzFfrLm52cLCQs27XtJ1EwcOHKinp9fY2NipGubNm0eSZGNj4927d11cXDZu3Citq1FSUuLr68vlcq9evWpoaKgCvzGYPoPmAyzZmMAAACAASURBVFl+fn51dbV0gqy7xVcVRxllVDpGRkbjx4+/cOGCiYkJ492zqKjIx8fH0NDwxo0bAwYMUMpdDKbvoflAhmZvSffIult8VYXIUUZtF4lE8vz5c+rjo0ePfHx8DAwMEhIS0IaVGAymU2g+kCUmJrJYLMYGl9AZ8VVtVkYNDg6OiIjIzMzk8/mNjY1paWlz584VCATUoGdubq6Pj4+RkdGtW7esra014T4G0+PRTCBLSkqiplydPn26ra0NidMTBIG2IAGAhQsXAsCmTZuMjY2tra1jYmLQ66cKe2RogSeiubmZvvlFZ4cOZs6cyefzx48fz2az6fPIysvLP/nkE09PTy6Xa2JiMmHChCtXrujr61O6r19//fXz588fP35sa2tL171g7MyCwWDkoJlARp8+xoAau1SD+KoKkaWMeuzYsc8++2zChAkWFhaGhoajR49euXJlbm7u1KlTNeUqBtP7wAqxGAymh0FghVgMBtP7wIEMg8H0eHAgw2AwPR4cyDAYTI9H8zI+HA6HMfdKtVhaWlJzGt59993ua0iDPmRkZBAEERERofKaKXbs2EFdApYYwmgbmgxkJEneu3dv1KhRurrdpSYkEAgoFSBoTylIDWiDDxhM70aTgaywsJDP53frzE8jIyOkq3Pu3DnQUBDRBh+UZ+/evegq6KtfMRgtQZOBLDMzEwDUM4U9KSmJzWZrdssSbfABg+mVaDKQZWRkAICTk9OuXbusrKzMzMzeffddhlYXj8cLDw8fPny4gYGBvb396tWra2trO2WASExMdHNzoy9B7xR37twJCgqysrIyMDBwdnbevn37q1evVO6D8otGL168OHr0aAMDA3d3d9QBpBCJRDt37hwxYgSHwzEzM5szZ879+/c7ZaAIv/zyy4QJE4yNjY2MjKZOnXrjxg360YyMjGXLlo0cOZLD4ZibmwcEBNy6dYs62qGwOAYjE7qAv5o3H0Ea+e+88w7dH09Pz7a2NmRQWlo6ZMgQhsO+vr4SiURBAwSfz2exWOvXr++an3v27GEsPAKAAwcOqNyH//3vfwAwZMiQznqINtObM2cOPdtIEERsbCwykEgk06dPZzhpaGh47949BQ0oJkyYYGZm1q4by5YtY9Sgo6Nz/vx5ykDq2wc6OjrXrl1DR7OysgAgODjY1NSUbpCVldXZG4Lp3YDU5iMaC2QSiQR9XydOnHjnzp2mpqa0tDRLS0sAKCgoQAaTJk0CgMDAwPz8fJFIlJaWhhZa5uXlKWJAERcXBwAnT57sgp/Hjh0DAC6Xe/jw4fLy8qamppycnPDw8D///FPlPigZyABg+/bt1dXVz58/3717NwAMHToU/SpcvHgRAKysrOLi4hoaGgoLC9HyjjfffBPV0KEBhaxAduHCBQAYPnz4lStX+Hx+XV3d6dOnTUxMBg8eTP0yeXl5RUdHP3v2TCwW83i8Cxcu6OrqUhLhKJCx2ezt27dTwuIAEBYW1tkbgundaFEge/ToEQBYW1vTt91etWoVACQkJJAkeeXKFQDw9/end222bdsGAJcvX1bEgCI8PBwAnj592lknRSKRlZUVi8VC2vzSqMEHRUCBbOLEifTCyZMnA0Bubi5JkqtXrwbaBgUkSTY1NVlZWenq6gqFQkUMKGQFsnnz5gFAdnY2vXDnzp0AcP/+ffQxNzf3/ffft7Ozo/cc3dzc0FEUyOhXUV1dDXhnPIwU0oFMYzkylOnfvHkzXX9VJBIBgIWFBQCcOHECAD799FP6a51EIgEAExMTRQwokpKSrK2tpV8AO+TmzZvV1dXLly+XNVSnBh8UhyFOiXwuKyuj/kWdR4S+vr6Hh0dra2tlZaUiBh2CfpnGjRunq6vLYrF0dHR0dHT27NkDABUVFQCQl5c3YcKEEydOVFRU0GcOoq1bGG4juiYsjumDaCyQoUz/zJkzqRKxWHzz5k0TExNHR0dkYGho6O7uTj8rOTmZxWKhwg4NEEKhMDMzs2uTHrKzswHg7bfflnMV3e2DkqAIi37EpDN9FB0adAiqoa2tra2tjeqfokNIO/fQoUONjY2rV6/Oz8+nenlcLpdRj6qExTF9Cg33yPT09KiSn3/+uaKiYt68eSwWCwAaGxsZz9X169eTk5P9/f1RZ6dDA8SDBw/EYjEj1iiIQCAAgKamJlkGavBBce7cuUP/ePfuXQBAfUD0799//00dbW5uzsrK0tXVtbGxUcSAgsvlNjQ0oF4nnVGjRrFYrFevXkm/CKDxnOLiYhaLdfjwYTSuCgDp6ek8Hk+VtwDTZ6F/4dSWI5NIJGir7eDg4NraWh6P9+OPPxoaGurr6xcWFiIbJD24d+9eHo/H4/GioqKMjY1ZLBY1jtahAeLq1asA4O/vX1tb21k/o6OjAcDR0TElJUUoFD579uzy5cuzZs1qbW3tDh9UkuxHafJPP/0UAOzt7VHnCOXyra2tr1y50tDQUFxcjAR4p02bhmro0IBizZo1AHDgwAGRSEQvR7M9vL29b9y48fLlS6FQ+ODBgy+//HLq1KnIIDg4GAD27dtXV1fH4/EuXbpkb2/PZrOdnZ2RAcqRbd++nV4th8Px8fHp7A3B9G5AS5L9aF/IwMBAekhlsVj0Qb2TJ08yYi5BEEeOHFHcAFFeXs5msykbNptNhaEOaWxstLe3Z7Ti7u7eTT4oGcjeeecdxvSL33//HRm0O7vCwMAgPT1dQQOK+/fv0zuhq1atog6FhISAFNTIQEpKCtqlhSI4ONjW1hYHMkxnAS0JZGiW49mzZw8ePGhmZmZqaurv7y89MhgdHT1mzBhDQ0MzM7OAgICkpKTOGiBiY2PHjBmDXmdGjhzZKVcrKiqWLl1qY2PDZrOHDh0aEhJSXFzcTT4oGch27979xx9/DB8+nMPhuLu7nz17lm4jFAq3b9/u4ODAZrO5XO6sWbMyMzM7ZUARExPj4OCAwhk9kJEk+dtvv02dOrVfv35o5vD69esfPnxIHY2Li/Py8jIyMrK2tt60aVNLSwsOZJguIB3IsNQ1BoPpYWCpawwG0wvpu4GM6IhDhw5p2kcMBqMQfTeQYTCYXkN3KRpqPzgViMH0GnpMjwztCh4TE9Ojm+g+1Kbora+vj2RLVE5tbe3Ro0f9/f05HA5BENeuXZO2KSkpWbp0qa2tLYfDcXZ2/uWXX6Sn5mL6IFoRyAIDA1ksllAolGODZhiMHz+++9xQsglFrqKb6Clq2q2trQRBBAQEtHv0gw8+WL58+ZUrV9CSJmmys7Pd3d1//fXXysrKlpaWvLy8kJCQnJyc7nQZ0zPQikCWlZU1cuRIQ0NDOTaRkZEkSY4cObL73FCyCUWuoptQp5q2SCS6fft2d9RsYWEREhJy+fLl5cuXSx+VSCSLFy/m8/n+/v45OTnNzc1PnjxZuXIlWtCG6eNoPkfG4/FKSkoWLFigaUeUQkuuokeraf/222/oD7Sii8Ht27cfPHgwbty4c+fOoWUSw4cP/+GHH9TqIkZb0ViPrLW1VVdXlyCI/v37wz/5KQRd3djPz48qX7duHaOSuLg4giC++OKLlJSUCRMmGBkZeXp6pqamoqNpaWkEQSAlGQqJROLu7m5qakqp0yjThIJXoQhqUNNWRkp76dKl1KVJ58g6VKmeOHEiQRAoAKFbili7dq2CDiDV7NDQUPpiLwwGobEeWUVFRVtbW7uH6E9aSUkJ9bf0GxPSAqqqqpo2bRrSMsvMzAwMDCwvL2exWG5ubnp6etSCasSPP/6YnZ19+PBhStRBmSYUvIoO2bt3765du6iB1Ly8vLy8PC6Xu2nTJgAoKyubOnVqaWkpOvr06dPvv/++oKAgISGBvuyxvr7+/v37aFG3RqisrHzzzTf5fD4AFBcXL1682NnZ2c3NTSWVI70zb2/vsLCwX3/9VSgUurm5bdu2zd/fXyX1Y3o29PVKatbsR4SGhgIAfUWeNN999x0AFBUVMcpR2njIkCFnz57l8/mPHz8eMWIEAFDLIT09Pa2srCj7V69emZub07cFUL4Jxa9CFmpT0+7yWk467S5+VFClGmkoUtrWskCx+OrVq/RCPz8/FovF6DITBHHmzBllLgfTEwEtWTROZ9y4cZaWlvJtgoKC2rWxtrYmCIK+2nzlypUAUF1djT4iBefy8nL0cd26dSwWKyMjQ4VNKH4V7dJT1LQp5ASyDlWqlQlkvr6+LBaLy+XGxMTweLyqqqrNmzcDwLBhw5S6HkwPRDqQaXjUsr6+Pjs7u8N5SUlJSQwdZwCorKysqqqaPHkyXRw5Ly+vf//+AwcORB+9vLzgn3kVubm5R44cWbt2bbu58C43ofhVtEvPUtOWT7eqVBsbG7e1tW3YsGHRokWmpqZWVlb79u1zcXEpLi5mbCGI6YNoOJAlJSW1tbXJDwH5+fnV1dXS2SsUnmbMmEGViMXijIwM+uNED2QbNmwYOHAgI/evfBMKXoUseoGaNkW3qlSjAO3q6kovRHNl6KMimL6JhgNZSkoKAPj4+MixQbOWpLtLKA1Pf3SzsrJEIhG9xNHR0cjIKD09/dy5czdu3Dh06BCjF6N8EwpehSx6lpq2kiBhRVnzXeXj6ekJAA8fPqQXPn78GADMzc1V4R2mB6PhQPb06VMAKC0tpW+rwyAxMZHFYqG+FZ2MjAwWi0WfiI9E6+lRhsVieXh4ZGRkhIaGTpkyBW1ZptomFLwKWaA+xSeffJKamtrU1FRZWRkXFzd79mxqMHTYsGECgeDzzz/n8/l8Pv+nn35C2xpERETQ66mrq0PuvXz5UlZbyu9kriQ6OjoDBgxIS0tLT0/v7L0KCAgwMDA4ePDgqVOn6uvrnz9/vmXLlocPH44YMcLW1rabHMb0GOgJM/Un+7du3Up3ZuXKlag8MTFRlsOJiYnIxsLCYuzYsfTaFixYoKOjw+Px6IUbN24EAIIgGKrNKmxC1lUogjrVtLs8aintA0VJSQnZGXHX999/n376mjVrqENoU2Fp8vPzkcH+/fsZhxg7mWP6CKBtyf7w8PCgoCBzc3P09jR8+HBUTp/bxWDYsGEAUFZWVlNTw3gZTE1NdXR0RBuYU4wZMwYA3n//ffRuQqHCJmRdhSIYGRn99ddfDDXts2fPUgYLFixgqGknJib+5z//YdRjZ2d34sQJSk3b3t5eO9fuHD58eNGiRdS96hTh4eHR0dEeHh4GBgZGRkY+Pj7Xrl2bM2dOd/iJ6Vn0cqlrkiTffPPNlJSUgoKCwYMHa9odDAajAvqW1HV1dXVISMjNmzc3bdqEoxgG04vpnYEsLy+PIAhra+vo6GhXV1dGDkttYDVtDEY99M5AhvJfJiYmQUFB8fHx+vr6mvYIg8F0I70zkKFFMPX19adPn3Z1dZUvnaq4tmpnJWQ7HHzZsGFDVy6vS6hNQhawiixG7fTOQEbRoXRqp7RVsYSsGsAqspgu0MsDWYfSqZ3SVsUSsgqCVWQxakbzCrHqoUPp1O7WVsUSsioBq8hi2kXDPbKampqdO3e6urqamppaWlrOmDGD+iVH+q779++fOnWqqanpiRMnMjMznZycBgwYcObMGWRDkmR8fPyCBQuQdOrgwYOXLVtGSb/SkS+dKt+g70jIAlaRxfRQ6LlnNS9RqqmpGTRoEMMfSgfx22+/BQAzMzNUPmTIEOrpGjFiBLK5fv269BXR1/cg+Hw+i8Vav369LE/kG9Cf6uPHjzOOojWPYWFh9LFRa2trtEJIzvqB5ORkxe/Vnj17pKfCHzhwAB0tLS2VVu/x9fWlS5gpch9I5cQXlyxZQrUuS7MsODiYvi5CR0cnKysLGcgSMqIvY6JoV7MMDWI8ffo0NDTU3Nzc0NDQ29ubodqG6QWAVi1RiomJKS8vDwgIyMnJaWpqqqmpiYqKmj59OjqamZkJAJs3b66trXVxcSktLV27dm1DQ4OnpyfS5wOAioqKkJCQ+Ph4lNktLCycOHFiVlYWI6GenJzc1tYmJzEk3wAtJ0QSsrIUMmJjY48fP05JyFZVVZWVlQHA0KFD0Y2WlpBFuq+K8Ntvv+3cudPU1JQhIYtUpEmSfP/990tLSxkSsrdv3y4oKOjUfVCS6OhodGkcDkeWTUxMzNq1aykVWYlEQnXKkLSktPjiN998o6ADPB6PxWJFRkZGRkbW1tYKhcKUlJRZs2bFxsYqf3UYrYYe1dTcIztw4AAAxMXFtXt0zJgxAwYMEIvFJEn6+flZWVkhfWpfX99JkyYhm/z8/BUrVjg4ONCfHC6Xy6iqQ+lURbRVsYSs4mAVWUy3AlrVIwsJCfHw8AgMDAwKCjp8+DCSGESIRKK8vLzXX39dV1cXALKzs6dNm6ajo0OSZHZ2tpOTEwBcunTJzc0tKiqqqKioubmZOnfs2LGMhjqUTlVEWxVLyKoErCKL6Q40GcjMzMwyMjJu3Ljh5eV15coVDw+PoKAg9ARmZ2e3trail69Hjx69evUK/Z2Xl8fj8by8vIRC4ZIlS9hs9ldffVVeXt7S0kKSJFLdY4gLdiidqoi2KpaQVRVYRRbTHWh41JIgiEmTJm3atOnatWtLliyJjY1Fzy1KPHl7ewMAGgGk//3aa6+lpqbW1dWtXbt2/fr1dnZ2bDZbIpGgNZWMR7pD6VRFtFWxhKzawCqymC6gsUC2cuXKnTt33r9/XygUvnz58vjx4xcvXiQIAr2RZWZm6uvre3h4AEBqaqqxsbGLiwv629TU1MnJCX3dk5KSysrKBAJBamqqv78/GsRkPKsdSqcqoq3aRyRkAavIYnoo9ISZOpP9aHdIBlu2bEFHXVxcJk+ejP52dXX18/NDfzs6Ok6fPp0kyYaGBmqHXcTcuXPd3Nw4HA4aH6DoUDpVlkEflJAlsYospicA2pPsP3PmTEhIyLBhw/T19YcOHTpnzpw///wTPUVNTU35+fnoXbKhoSE3Nxd1cHg8XkFBAfrb2Nj4ypUrfn5+JiYmdnZ2ERERZ86cefr0qYuLCxofoOhQOlWWAZaQ1RRYRRbTWXq5QiwGg+l99C2FWAwG00fAgUyTYAlZDEYl4ECGwWB6PH1Fxkc7welIDEYlaEuPrLMq0lqF2lSku0lCurW19dKlS0FBQUOGDOFwOCNGjNiyZQt95ZBAIPjpp5/8/PxsbGyQQXh4eH19vco9wWC6CH0uhvp3GqdA+4E/evSoa6e/++67Ojo6AoFAtV4pAmOpICWt0x20O+tKQeQsxkaysQzc3NyEQiEy2LNnj7SBo6MjY7ocBqMeQHvmkTHAKtKK0E0S0mw2OzAwMC4urry8XCAQJCcnOzk5ZWdnR0VFIQNjY+MlS5Zcv369vLy8sbHx+vXrtra2+fn5eCwCoy3Qo5oGe2TKgBbfLFiwQLNufPzxx2w2m+rFaBsKyuMgkJD0woULZRmgJABDgQeDUQ+gbT2yvqMi3RMlpPv16yfrEFo1hVdiY7QEDY9a0pcBSb+UIW2JqqqqadOmiUQiAMjMzAwMDCwvL2exWBUVFdTCaQadihd79+7dtWsX+c8AYl5eXl5eHpfL3bRpEwCUlZVNnTq1tLQUHX369On3339fUFCQkJBAX0BTX19///59pPanESorK998800+nw8AxcXFixcvdnZ2RhKynUIgEKSlpX300UcEQSxatEiWGdpQUo4BBqNW6N0zTb1aIhXpoqIiRjna3HDIkCFnz56lVKQBoLi4mG4mrSKtOMeOHQMALpfLUJH+888/SZKUSCRIB42hIg0AeXl59Hri4uIA4OTJk7IaUkYLn0KO8iqbzd6+fTslIQ0AYWFhdLMOXy3petDDhw+PjY2VZZmVlaWvry/nxROD6VZA6tVSKwIZVpFWkG6VkKYHMjs7uy+++KJds+LiYjs7u/Hjx2tkjBiDIbUwR4bAKtLKo7yE9Lx580iSbGxsvHv3rouLy8aNG/ft28ewKSkp8fX15XK5V69e1cgYMQbTLpoPZFhFWiWoSkLayMho/PjxFy5cMDExYWxfVFRU5OPjY2hoeOPGjQEDBijlLgajUjQfyLCKtHrorIS0RCJ5/vw59fHRo0c+Pj4GBgYJCQn0HjEGow1oPpD1ERVpbZaQDg4OjoiIyMzM5PP5jY2NaWlpc+fOFQgE1KBnbm6uj4+PkZHRrVu3rK2tNeE+BiMXesJMbcn+Pqgirc0S0tOmTZOuXF9fn/q/WLVqVbsOODs7d/ZyMBjlAS1J9mMVaY0gS0L62LFjn3322YQJEywsLAwNDUePHr1y5crc3NypU6dqylUMplNgqWsMBtPDwFLXGAymF9JrAxlWkcZg+g69NpBhMJi+Q6+VusaZPgym76B5GR8Oh6PIFDDltbCxmrYidJOaNqKkpGTp0qW2trYcDsfZ2fmXX35B670YkCSJpIfeeeedbvIE08vQZCAjSfLevXujRo1i7A3eLmjFEn1+bGdRsobAwEAWiyUUCrvsQJcRCAQ1NTXUR82uhZJDa2srQRBIs0Sa7Oxsd3f3X3/9tbKysqWlJS8vLyQkJCcnR9oyKioKTY7DYBREk4GssLCQz+e7uLgoYqykFrbyNWA1bWWQSCSLFy/m8/n+/v45OTnNzc1PnjxZuXKl9IS7ly9fbt26NSwsTOU+YHoxmgxkmZmZAKBgINMsPB6vpKSkCzqFqiUpKYnNZo8bN06zbnSB27dvP3jwYNy4cefOnXN1ddXT0xs+fPgPP/zg6urKsAwPD3d3d3/vvfc04iemh6LJQIaWhTs5Oe3atcvKysrMzOzdd98tLy+n28jXwgaAoqKihQsXDhgwwNLS8sCBA0VFRUj3WcEasJo2orvVtG/cuAEAoaGh9LVc0qSmpsbExHz99ddduARMn4a+XknNworogWEkdD09Pdva2igb+mN5/PhxRg3p6ekMXTC0KvDChQsK1oCWf4eFhenr61Nm1tbWaLGknKVUycnJil/pnj17GKuCgLZxXGlpqbSQma+vL13NkSRJPp/PYrHWr18vqxVlRGiXLFlCNS1LuzE4OJi+PkxHRycrKwsZyBJ0o5ZzogGKp0+fhoaGmpubGxoaent7M8QpW1tb3dzcNm3aRLU4Z86cLlwLptcD2qMQK5FI0FMxceLEO3fuNDU1paWlWVpaAkBBQQHDuF0tbKFQiILU1q1bKysr6+vrIyIikFhNVVWVIjWQWE1bim5S0/bz82OxWIweMUEQZ86coWwOHTpkY2PT0NBA4kCGkYsWBbJHjx6hvg/64iJQfyohIYFh3K4WNopNH330Eb2wX79+gwYNkm4Oq2krSDepafv6+rJYLC6XGxMTw+PxqqqqNm/eDADDhg1DBpWVlf369aPCNA5kGDlIBzKN5chQpn/z5s10aVO0VZKFhQXDuF0t7NOnT+vq6u7cuZMqkUgkra2t7U6wwGrayqOMmraxsXFbW9uGDRsWLVpkampqZWW1b98+FxeX4uJilBUNDQ11d3dfsGBBt7iO6e1oLJChTP/MmTOpErFYfPPmTRMTE0dHR7qlLC3sjIwMe3t7MzMzqiQxMVEoFEoHMqymrRKUUdNG8ZcxRommwrx69UooFJ48eTIxMZEaJUDXfuHCBYIgtmzZopoLwPReNNwj09PTo0p+/vnniooKJMFKt2xXC1ssFgsEAiMjI3rh3r17ob0pr1hNWz3IUdP29PQEgIcPH9ILHz9+DADm5ubtzu/HYBRHM4GM/CcJsmfPnpcvX/L5/KioqI0bN+rr62/fvp1h3K4WNpvNHjhw4P3793/++WehUFhaWrps2bJbt27p6OigZ6bDGgCraasaOWraAQEBBgYGBw8ePHXqVH19/fPnz7ds2fLw4cMRI0bY2toaGxszkiD0HJn0Zk4YDBP6t0dtyf6CggIACAwMpHvCYrGoXK8iWtgo+U0xceJEOzs7SnwZq2krjhrUtEmS3L9/P6NyHR2d8+fPt+sSTvZj5ABakuxH75Xvv//+wYMHzczMTE1N/f39//77byrX26EWNgB88sknGzZssLa2NjU1/fDDD48ePfrs2TNvb28Fa8Bq2t2BLDVtAAgPD4+Ojvbw8DAwMDAyMvLx8bl27dqcOXM04ieml9F7pK737Nmza9eu2NjYefPmadoXDAbTjfQeqet79+7Nmzfv77//bmhoqK6u/vrrr/fs2WNnZzdr1ixNu4bBYNRNTxVWfPjw4dmzZ+nvaGw2++jRoxwOR20+SL89Mfjyyy83bNigHmcwmL5MTw1kc+fOLSkpOX36dHFxcb9+/aZMmbJt27aeKAuBwWCUR1teLRsbG8PDw4cNG8ZmswmCQANwIFsZ1cTEZPfu3Xl5eSKR6MWLF2fPnlV/FOtwbEXx7pjaBGABIDk52dvb28TEhCAIauBix44dlAPm5ubd6oAsWltbL126FBQUNGTIEA6HM2LEiC1bttAXDwgEgp9++snPz8/GxgYZhIeH19fXa8RbjHZBf/DUrH5Bh563AwC0lpixAoaSi+gO3n33XR0dHYFA0H1NyEKdl/nq1SskSYRwcHBA5fTpe2ZmZt3ngJz1mEg2koGbm5tQKEQGe/bskTZwdHRkTJfB9HpAS6ZfMCgtLY2NjfXy8iopKUFrp5GunjqVUfuIAGxycnJdXd3q1asbGxtJkiwsLETle/fuRT7IWhOqBthsdmBgYFxcXHl5uUAgSE5OdnJyys7OjoqKQgbGxsZLliy5fv16eXl5Y2Pj9evXbW1t8/Pz8c5+GK3IkcXHx5MkuXnzZlmz0rtbGRUJwGp8xbIaBGCfPXsGAG+99RZjdZc24O/v7+/vT32cNGlSZGTkW2+9defOnfXr1wMA41V9+vTp+/fv/+CDD9BSM0xfRpM9shMnTqCkzIoVKwBg3rx56KP03jlylFHla6uSJBkfH79gwQJkMHjw4GXLllVWVqKjPUsAFpRYhIQkXgmCWL16NQDMnj0bfezU5F6RSLRz584RI0ZwOBwzM7M5c+bcv3+fbpCRkbFsGnvfAgAAIABJREFU2bKRI0dyOBxzc/OAgIBbt25RRztUkZVFv379ZB1CqyY0ldTDaBH090w158jQGm9p1q1bRzeTo4zaobbq9evXpeunVgj1LAFYUolFSLI2JaJyZBQTJkxoN0cmkUimT5/OON3Q0PDevXuUjXT9Ojo6165do2pu1wf6MiaKxsbGhISEkSNHEgSRlJQk67qQiBPq0WP6DqA9wop0bG1tXVxcZB2VpYyqiLbq0aNHQ0JC4uPj0RZkhYWFaE0SI6nfIwRgSVVowCI5/EuXLskykBXILl68CABWVlZxcXENDQ2FhYVofObNN9+kbLy8vKKjo589eyYWi3k83oULF3R1dRl5fTnJfkRsbCwV44YPHx4bGyvLMisrS19ff+HChR1cM6bXoY2BrKysDABWrFghy0CWMqoi2qr5+fkrVqxwcHCgT5TlcrmMqvqOAGyXAxl6J42OjqZKmpqarKysdHV1qVHF3Nzc999/387Ojr5RqZubG72eTgUyOzu7L774ol2z4uJiOzu78ePHa2SgGaNZpAOZ5pP9aMsiabEwClnKqB1qq166dOm9995rbm5mnDh27Fj6RyQAy5DiUBAkALtq1apeIAArH/R7g3qXCH19fQ8PjytXrlRWVjo4OOTl5U2YMEFaMxZFLsWZN28eSZICgSA3N3f37t0bN25sbm5maCuWlJT4+vpyudyrV69qZKAZo21ofvpFuxJgFHKUUeVrqwqFwiVLlrDZ7K+++qq8vLylpYUkSSTsxzgFC8AqAvoZlLMq69ChQ42NjatXr87Pz6f6aFwut2vNGRkZjR8//sKFCyYmJt988w39UFFRkY+Pj6Gh4Y0bNwYMGNC1+jG9DM0HstTUVC6XO3r06HaPylFGla+tmpqaWldXt3bt2vXr19vZ2bHZbIlEgpTFGLVhAVg6XC63oaFBWrIVdRX//vtvqqS5uTkrK0tXV9fGxgYAiouLWSzW4cOHR48ejQZe09PTeTweox45KrLtIpFInj9/Tn189OiRj4+PgYFBQkICfV8FTB9Hw4GspaUlKytrwoQJsn7q5SijytdWRQ9MUlJSWVmZQCBITU319/dHg5iMeNFTBGBBLRqww4cPb2lpiYyMZLySv/XWWwCwZcuWq1evNjY2lpSUBAcHV1VVobACAIMHD25ra4uMjOTxeHw+//Lly/Pnz5fejleOimxwcHBERERmZiafz29sbExLS5s7d65AIKA2eM/NzfXx8TEyMrp165a1tXX33QRMz4OeMFN/sh8lyHbv3i3LQI4yqnxt1YaGBtRToJg7d66bmxuHwxGLxfQmeooALKmWUcv79+/Tf1RWrVqFytudfmFgYJCeno4MUlJS0I8HRXBwsK2tLaXZSyFLRXbatGkghb6+PqXoi3YLlEa6CUzvBrRtiRJKkMnJ9MtRRpWvrWpsbHzlyhU/Pz8TExM7O7uIiIgzZ848ffrUxcWFPqYGWAD234wZM+a3335zcHBg9JEJgjh//vz27dsdHBzYbDaXy501a1ZycjK1Q8Jrr7126dIlLy8vIyMja2vrTZs2/fDDD+02IUtF9tixY5999tmECRMsLCwMDQ1Hjx69cuXK3NzcqVOndtPFYnoNvUchFoPB9BF6j0IsBoPBUOBAJhOiI7DoAgajJeBAhsFgejyan9mvteBcIQbTU9C6Hllubi5BENL7jXcZPz8/DocjZ46YOmWmu4zanNRaIWxESUnJ0qVLbW1tORyOs7PzL7/8Ij13FwBIkkSqQdKSUJheidb1yB48eAAAqhIXJEny3r17o0aNYky5oBAIBDU1NdRH7VwkpDYn6+rqZs+ejWbnaoTW1lY2m+3v73/58mXpo9nZ2b6+vnw+H33My8sLCQlxd3enZsxSREVFyVIuwvRKtK5HptpAVlhYyOfzXVxcZBmoU2a6y6jNSW0WwpZIJIsXL+bz+f7+/jk5Oc3NzU+ePFm5cqX0hLuXL19u3bo1LCxMI35iNILWBbKcnBxzc3NViUBkZmYCgJxARqEGmWnl6W4ntVkI+/bt2w8ePBg3bty5c+dcXV319PSGDx/+ww8/uLq6MizDw8Pd3d3Rtg+YPoKGA1lFRcXSpUsHDhzYr1+/VatWicVi9GWlDOSLRIeGhhIEkZubS5WcO3cOrQFCH5Gau5OT065du6ysrMzMzN59993y8nJpT+TITDc2Nn766aeOjo4cDsfGxubjjz+Ws0pcFmrQwu7dQtg3btwAgNDQUOn1m3RSU1NjYmLQSixMH4K+XknNay0LCwstLCzozqAHafv27cigQ5Fob29vU1PTtrY2qk4kT5iWloY+InEeRsbX09OTfgopV2a6oqJCWplDji5gu6hHC7t3C2GjIY6nT5+Ghoaam5sbGhp6e3szxClbW1vd3Nw2bdpEXdScOXM6ezcw2g9oj0KsRCJB391ly5YVFRUJhcJTp06hlPwff/xBKiAS3dLSoq+vP336dHq1U6ZMYbPZIpEI1WBqagoAEydOvHPnTlNTU1pamqWlJQAUFBTQz5IlM93S0uLm5qajoxMaGlpQUCASiR49ejRjxgwAuHv3roJXqjYt7N4thO3n58disdatW0cPcwRBoC1QEYcOHbKxsWloaCBxIOvVaFEgQ4o606ZNoxd6e3sDQFlZGamASHRaWhr8WzmjpaXFwMDAw8MDfXz06BEAWFtbo282AikoJCQk0NuVJTONHuxvv/2WXoheiBiFsuhZWtjaLITt6+vLYrG4XG5MTAyPx6uqqtq8eTMADBs2DBlUVlb269ePCvQ4kPVipAOZxnJkv//+OwAw5os1NzdbWFgMGjQIFBCJlpaWvX//flNTE6XHgDL9mzdvNjY2pmxEIhEAMF5pZclM//LLLwCwZs0a+sqk119/HRSeLou0sJcvX967tbBlCWG3traizfeQEPaJEycqKiroc/oUF8I2NjZua2vbsGHDokWLTE1Nrays9u3b5+LiUlxcjJKeoaGh7u7uGt+cFKMRNBbI7t27RxAE/fF+/vx5Tk4OlenvUCQ6LS2NUQPq5VGBDGX6Z86cSRmIxeKbN2+amJg4OjpShbJkpsVisZy5SLa2topcZh/Rwia7XwgbRXDGGCUStnz16pVQKDx58mRiYiL1e4Pu3oULFwiCYEj+Y3ofGgtkdXV1HA6HPgD3+eefi8ViKgx1KBL95MkTc3Nz6mFoaGj46aefgBbIUI9MT0+PquHnn3+uqKhAGq1UoSyZafRCOnv27HY7twpOGe9lWtgaFMJG/61o1wWKx48fA4C5uXm78/sxfQj6w6nOHNnkyZMB4ODBg42NjVVVVTt37kTP87lz55ABktPbu3cvj8fj8XhRUVHGxsYsFosaCHN3dycI4tKlS6i3gnIoHA4H7TMikUjQDtXBwcG1tbU8Hu/HH380NDTU19cvLCyke3L16lUA8Pf3r62tpZdLJBIzMzNjY+OYmJja2lqU6Y+JiZkxYwZjrEAO0dHRAODo6JiSkiIUCp89e3b58uVZs2ZRArAdXqZ8J+moIdm/Zs0aADhw4AAaTqFAyX5ra+srV640NDQUFxcvXLgQaDnQ4OBgANi3b19dXR2Px7t06ZK9vT2bzZYWdx0wYICpqWlaWhpDyLempsbAwMDY2PjkyZN8Pr+6uhrlyEaMGNGuqzhH1osB7Un2f/vtt/R4am9vjzL95eXlyKBDkWiGzmpYWJiOjs748ePR0YKCAgBgbPLGYrGkR/3kyEzv27dPOvTr6uqiWKkI6tTC7t1C2CRJ7t+/n9GEjo7O+fPn23UVB7JeDGhPIGttbd27d+/QoUMNDQ39/f3LysqcnJwYu+QyRKKTkpLoR2tra+fOnWtiYjJ48ODIyEgUuf773/+io8ePHweAs2fPHjx40MzMzNTU1N/fX9bQYWxsLCUzPXLkSPqhU6dOeXt7m5ubGxsbu7q6btiwITc3t1NXiib90rWwi4uLFb9MRZxEqCGQkSQZExNDCWFTgYwkSaFQyBDCzszMpJ8YFxdHF8JuaWlpN5DV1tbShbDpgYwkyejoaA8PDwMDAyMjIx8fHzSFpV1wIOvFSAcyLHWNwWB6GFjqGoPB9EJwIOs6WAsbg9EScCDDYDA9Hq0TVuxB4GQiBqMlaF2PTOVS1z2LU6dOEQQRExOjaUe6Qo8QDVcQfX19JJ2C6RFoXSDrmkJsYGAgi8USCoXd45T6fEhPTweA8ePHq8gp9aElouGtra0EQQQEBGikdYym6CWBLCsra+TIkYaGht3jlPp8iIyMJEkSLSHsWfQI0XDFEYlEt2/f1rQXGEXRukDWBalrHo9XUlIivQOFOtEGH7SEHiEajullaLXUNUmS8fHxCxYsQBrQgwcPXrZsGZKFAYDW1lZdXV2CIPr37w//ZJcQaFo/Qnmh6pqamp07d7q6upqamlpaWs6YMYP6rVbQh1evXhEEERQUJBAIwsPDBw0aZGRkFBISQr2H+vn5UScytAPhH+nnL774IiUlZcKECUZGRp6enqmpqXSboqKihQsXDhgwwNLS8sCBA0VFRQRB7Nixo1NXqs163ADQ0tLy2WefOTs7GxsbW1lZvf322wkJCdTRDsWyER988AFBELW1tZcvX3Z3dzcxMZk8efLdu3fR0aVLl1InSufIkCb4jh070tPTvb29DQwMHBwc6P/RAFBTU7N06VK0mCQ4OLimpoYgiHnz5nXhejGdgD7NX9ukrpEsDwNqoWJJSYmsi0pOTkY2ygtV19TUIH00OlZWVor7QF3Ixo0bGc/G33//jQzoD/bx48cZPkRERABAWFiYvr4+ZWZtbU0tt0xPT2eIlyH9yAsXLih+pVqux03+I9/IoKmpCR3tUCwbsWjRIgA4c+YMXQFlxowZ6OiSJUuoQh8fH4YDaNlTcHAwUh5G6OjoZGVlIQORSDRmzBh662jxfGBgYBeuFyML0J61lh1KXZMkefTo0ZCQkPj4+MrKypaWlsLCwokTJwKAQCCgVxUaGgoADx8+ZDShEqHqL7/8EgACAgJycnKamppqamqioqIWL17MMJPlA+Lzzz8HACcnp0mTJt2+fZvP5z969Gjp0qV1dXV0s++++w4AioqKGKej1PWQIUPOnj3L5/MfP348YsQIAEBrNoVCIYqDW7duraysrK+vj4iIQCu0q6qqFLzMHqHHbWlpyeVyb968KRAIamtrr1279sYbbzB0OORozCJQIHN2dg4LC3v8+HF9fX1CQsKOHTsYZhwOR1YgY7PZ27dvf/78+YsXLz788EMACAsLQwbof9DZ2fnOnTuNjY1//fXXsGHDcCBTOVoUyDqUuiZJMj8/f8WKFQ4ODhwOh/qJ43K5jKrGjRvHWG2OUF6omiTJAwcOAEBcXJx8M1k+INBchNdff11asoJOUFBQu5VYW1sTBEFf8b5y5UoAqK6uJv95eD766CP6Kf369Rs0aJB8nyl6ih73yJEjHR0dGRvHMFAwkC1fvlx+W3IC2cSJE6mS6upq9DuHPvr7+wNASkoKZXD27FkcyFSOdCDTXqnrS5cuubm5RUVFFRUVNTc3UzZjx46ln1JfX482oJZuQnmhagAICQnx8PAIDAwMCgo6fPgwUnxlIMcHBNKq/fHHH6V3k6WTlJSEupx0Kisrq6qqJk+eTH91ysvL69+//8CBAwHg9OnTurq6O3fupI5KJJLW1lbF53D0FD3u77777tWrV6NHj165cuUXX3zx119/dVlPEemjdQ36XRo4cKCenl5jYyP6+PTpUxaL5eXl1a4xpvvQUqlroVC4ZMkSNpv91VdflZeXI/0vpA7KUElNSkpqa2uTDiIqEaoGADMzs4yMjBs3bnh5eV25csXDwyMoKIjx/MjyAVFbW1tWVjZhwgT520Tm5+dXV1dLz1pAM8vQGzFCLBZnZGRQty4jI8Pe3t7MzIwySExMFAqFigeynqLHPW3atNLS0iNHjowaNSo1NXX69One3t4NDQ1dqKprow0I+hYQAEAQ/19ChiRJRp5R8Z9MjDJoqdR1ampqXV3d2rVr169fb2dnx2azJRLJ1q1bQSqQpaSkAICPjw+jfpUIVSMIgpg0adKmTZuuXbu2ZMmS2NhYRr9Mlg8I1B3z8/OT3woaCZXukaHT6dEhKytLJBKhErFYLBAIGBuD7927Fzozq7YH6XFzOJxp06aFhobGxsaeOXPm7t27SN+cQo5YNh26ALoKsbe3b21tRb89CGo8FNOtaCyQ2djYiESiyMj/2965hzVV/gH8PRvIZFwEJEKHkkq5CUoIKt6F8rHEC0lIWV4wTcuUNNGH1LyUT/fIMsvUfCpM8Kk0L0UIhrcpsJjPIzdT5CYiCmyOyRDh/P74Pp7fcRvbgcnOht/PHz6Hc87efTe37875vu/7eT/TarU1NTUbNmyAkhZckcHH8eTJkxUVFVqtVi6XT506Fcpqet+TsrIyQkh5eTl7bR5CiIeHh5eXV1ZWVkpKSl1dXXNz86VLl1JSUqZMmQLLxHFh8eLF69evv3Dhwp07d+rq6lJSUv744w+KouCezmwMACwdwF5hyCjZ2dl6dyVAXl6eUChkZyX28lGOjo4+Pj4XLlzYs2fPnTt3ysvLFy5ceOLECYFAwKxdYBYYf7tp0ya5XN7U1FRdXX306NHp06e3trbCCQMGDNBqtVu3blWr1Wq1eteuXbDuAXSnMjQ0NEB4dXV17T1Xp4dfNDc3h4eH79279/Lly83NzWVlZVDShSdlEAgEnp6eOTk5ubm5Rv87uhSokb322mvnzp3TarVnzpyBX1+ky2Ffp9iO6lqj0cCiFQzR0dHBwcFOTk56Kne9D8rixYuZQ5aLqqFzUI+1a9fqnWYiBpqmZ86cSVFUXV2dYfvZ2dnt/b9kZ2fDOd7e3sOGDWM/Ki4uTiAQqFQq+BPq6wyjRo2SSCSG5lUT2IWP2+gFo0gkMuwpNiHLpu8X+2/evGn4FIYvk+Hq1av0/WI/MzwIYHcL6HQ6vXWeYDxHTExMR18vYgJiO72WZlXXSqUyIiLC1dVVIpFs3LixpaWlV69ew4cP12unoaEhNjaWMSMz454AC0XV+fn58fHxAwYMEIlE/v7+M2bMMOpWNh2Dn5+fTCYz2j4sTWIUSOjl5eXkQaM0TdP9+/dn56mmpqaEhARfX193d/dXX30VZt0vWrSI+8uk7cTHLZfL58+fD73Yfn5+cXFxzAAuNqZl2V2ayGiarq2tnTt3roeHh5ub27x582DFz1deeaUTrxdpDxtKZEgXsXnzZkLIgQMH+A4EoWmahrkHSUlJfAfSrTBMZDY31xLpEP/++29MTMyZM2c0Gk1NTc1XX321ZcsWiUQybdo0vkN7RFm+fPk333xz5cqVpqam8+fPr1ixghBiuMQU8nB5dMWKhtNx9Pjiiy8SEhKsE0ynuXjx4q+//gqjLgFHR8fdu3czQ4i7x8u0I0pLS6HbimH69Ont9WgjD4tHN5F1D6Kjo69evZqamlpaWurm5jZu3LikpCQ0T/DI9u3b+/Xrl56eXlVVJZFIXnrpJfZYZaSrYN9ndu8aGZRyf/rpJ74D6Qzs2fXR0dF8h2MRRmf/IAh3iJ3WyFC+ivJVBDGBfSQylK/Czw7KVxHEKHaQyFC+yoDyVQQxCs+JDOWrAMpXUb6KWAS7YGblYj/KV+EoyldplK8iHYHY1Mh+lK/SKF+9D8pXEe7YViJD+SqN8tX7oHwV4Y5hIuOzRobyVYLy1QdB+SrSOfhMZChfJShffRCUryKdg+deS5SvonyVIyhfRUzAWyJD+SqcgPJVjqB8FTEFu2BmzWI/ylcBlK+ifBXpKMR2iv1paWmG8lX4KrJJTExky1f1amEKhUIqlXp6ehq2b2IYGnTbV1RU3Lx5U+9+Uy6XS6VSZijTpk2b2PLV3bt3X7t2DZTcHBGLxadOndKTr7KtO3FxcXry1ezs7CVLlui1I5FI9u3bx8hXn3jiCdOdGx1CJBIZylflcvmQIUP0zty2bRtbvmpNnJycMjMz2fLVLVu2QPBWjgSxRdhZrXvbLywH5as2BcpXH1mI7VyR2T4oX7U1UL6KtAeKFdsF5au2BspXkfbARNYuKF+1NVC+irTH/wccEkLS0tJmz55N4zhDBEFsGIqiUlNTY2NjmT12UyMDjc/PP//MdyDdlscee4zR7MAMVh4RiUQm5qUhiB42kci4mKx5F1U/FN22zWI1m7Zd+LLtIkiEjU0kMi4ma95F1Q9Ft22z2JpNG3XYSIfgP5HZhcnaLoJ8KKBNG7FL2IPKrDkgtqWlpb2x6T///DNz2qRJk5j9y5YtY7cAc4Y//PDDcePGubm5paSk5OXlSaVSDw8P9mA5jUazadOmwYMH9+jRw9fXNyEh4c6dO+x2amtr161bFxgY6Obm5u3tPXny5BMnTnAPsq2t7e+//549e/bAgQNFIpGfn198fPy1a9e4BwkjPL7++uuUlJSnnnrKxcVlzJgxOTk5HX1L5XL5iy++6OPjIxKJZDJZUlISe/JWQ0PD6tWrIUh/f/8lS5YYnUsUEhISFhZm4lk6PRGKi2bWtEVWKBS+8cYbL7zwgouLy6ZNm2pqasLCwjw9PdevX88+bc+ePSNGjBCLxc7OzuPGjcvIyGAfbW5ufv/992UymVgs9vHxee655zIzMzsUZG5ubnx8fEBAQI8ePby8vKZOnZqVlcU9yFOnThFCtmzZ8ttvvwUEBIhEouHDh+u55xDTENsRK3I0WZsQVW/fvp0Qwjh2+vfvz5wcEBAA51RVVQ0ePFivfbYa0HLdNri29WBmU3IJEjSKcXFx7Ba8vLyMziFtD+vYtGm+Exl7OlpkZCSzfeHCBThn4cKFeu0LBIKDBw8yjVgu9TY8KhAI/vrrL45BQiKLjo5m/0YKBAKzhlGEgdhOImMwbbIGjIqq4+PjCSEff/zxrVu3AgMDCSGffvqpRqMJDQ319/enafru3bvBwcECgWDVqlXFxcU6na6kpATsY+fPn4dGLNdt7969Oz4+PiMjo7q6+u7du5cvX4b5m1qtlkuQNE1DSAMHDkxPT29qaiotLYUW9u/fz/E9tJpNm7ZMqE1z0MwCRi2yQqFw0KBBtbW18HqffPLJGzduwPbOnTtpmj506BAhZNCgQceOHVOr1Q0NDampqa6urv369WPkt5ZLvcPCwvbu3Xvt2rWWlhaVSnXo0CEHBwfmZLNBQiIjhGzYsOHGjRs1NTWwkI1UKu3wu/moYouJzLTJGjAqqh46dKinp2dLSwtN0xEREY8//jh8WCdOnDhmzBiapmEU+Pbt29mPOnHiBHun5brtoqKiRYsWwXRr5ge2V69eHIOkadrb29vR0fHSpUtMmz/88AMh5PPPPzcdFWAvNm3AwkS2YMECmqbBZbRo0SKapmtrawkhW7ZsoWkaVlRSKpXsR8GgWeaSzXKpd0FBwcsvvyyRSBwc/j+ePDg4mGOQkMjGjh3LbhO640EEgpjFMJHxXOw3a7IGDEXVOp2usLBw0qRJ8GFSKpWRkZECgYCmaaVSKZPJCCGQDt58802KBRTd6Ps3CBbqtg8fPhwcHPz9999fuXKlubmZ2T9s2DCOQYKEY86cOWyvkU6nI4T07t3b3PtHiP3YtB8KEDCIZNnbYCIqKSkhhAwfPtzBwUEoFAoEAoFAAJKMqqoqaMFCqXdhYeHIkSP37dtXVVXFlrJB7uMSJKD3eYZEVl5e3oH3AmHBcyIzbbIGjIqqlUrlvXv34I6ppKSkvr4etgsLC1UqVVhYWEtLCyiujNK3b1/YsES3fefOnXnz5jk6On755ZeVlZV3796lafrixYuEEFC5mg2StOOGzMjIIIRw1Dfai037oQC5GFy1sA3/gqgSfp9aW1tbW1uZy094ICO2tVDqnZyc3NjYuHTp0qKiIqbXqFevXtyDNArESeOkms7CcyIzbbIGjIqqwVINajBYT5e9HR4ertFoaJqePn260UvTmTNnMk11Wrctl8sbGhqWLVu2fPlyiUTi6OjY1tYGJkhIEGaDZM5xc3Njmi0oKDh48KBMJpNKpVzeQzuyaRPOvuzO8dRTTwmFwvr6etP/45ZIvUtLS4VC4bZt2wYPHgxuuNzcXJVK1dFQ9RaBhvHe/fr162g7CMBzIjNtsgaMiqoVCoVIJAoJCSGEyOVyFxcXKKXL5XJ3d3eZTObh4eHl5ZWVlZWSklJXV9fc3Hzp0qWUlJQpU6bADQixWLcNH/eTJ09WVFRotVq5XD516lToxIR0YDZIcv+KLDk5+fLlyzqd7u+//542bVpbW9uGDRs4vofWtGkTy9YzJ13sy37llVdaW1ujoqIyMzPr6+ubmpouXryYnJzM/AhZLvWGfoPPPvtMpVKp1eojR47Mnj2bLe/lyJkzZ957773a2tra2tr33nsvJycnICAAlJ9IZ2D/alm/2N+eydqsqDowMJAplwYFBUVERMC2VCqdPHkybH/44YeGD3dwcIB7QNpi3bZGo+nTpw/7UHR0dHBwsJOTE1T3uQTp7e0dExOjVw4zuzQkG2vatGmLey3p9n3ZZnXYQqEQ1hiFgtSqVavo+57uNWvWQCPQTayHl5cXHLVc6n327Fn4AWNYsGBB3759GQG62SCh2D9jxgx2OxRFsceIIKYhtlbsb89kbVpU3dTUVFRUBLdpGo2moKAAbtNUKlVxcTFT5VmzZs3+/ftHjx7du3dvFxeXoKCghISECxcuMF9XC3XbLi4ux44di4iIcHV1lUgkGzduTEtLKysrCwwMdHBw4BIkVPrDw8P/+OOPoKAgkUgUFBS0Y8eOnTt3cn8P7cKmzaZLfdm7d+/+6aefxo8f7+bm1rNnzyFDhixfvpz5XbRc6h0eHn748OGwsDCxWOzr67t69ervvvuuE3GGhob+8ssvAwYMcHJyCgkJOXjw4IwZMzr3khFC+L4ie8T57bffCCHMWErkUYAZ2c93IHYMsbUrskccKJBoV09dAAAKXklEQVRB4QxBkE6DiYxPFAqFu7s7MxbEEMocycnJ1gwYQWwTTGR8olAoDKszCIJ0FHT28wnMXDEBjSMkux3Qkc13FN0NvCKzKl1kcL53797hw4djY2P79+/v5OQUEBCwdu3axsZG5gStVrtr166IiIg+ffrACYmJibdv337okSAIP7Ar/9hr2dUYnQvNERMzmUHrqkdwcDAzhwbmG+ohlUpVKlWnXwuC8AXBXkt+6SKDs6Oj46xZs44ePVpZWanVak+fPi2TyZRK5ffffw8nuLi4zJs3Lz09vbKysrGxMT09vW/fvkVFRdhXgHQT2FkNr8hsGY4CHODPP/8khLz00kvtnQDrUUVFRT28ABHEShDbuSLLycmhKErvlqetre3pp592d3evrq6GPY2NjZs3b5ZKpU5OTn369Hn77bfZs0xoms7IyIiLixs0aFDPnj379eu3cOFC5rFAfX09RVGxsbFarTYxMdHPz08sFsfHx3d0PSRL5hjOnz+fGTBhWCNTKpUURa1bty43N3f06NE9e/YcOHBgSkoKc8KoUaMoioIJCUePHmWaWrZsmennZc9F1wNmNXE0BSGIjcNbr2VwcHCPHj1g0j/Dzp07lUrltm3bYA7jtWvXnnnmmeLiYjh6/fr15OTk//7778iRI7AnIyMD9KpAZWXlnj178vPz//33X2Yn6CX8/PyioqKY27offvjhtddeg/lDNkJ1dfWzzz6rVqsJIaWlpXPnzh0yZEgnljvRarU5OTkrVqygKGrOnDntnfbXX38RQkycgCD2BPvyzMq3lqGhoYwdn6bp+vr63r17h4aGgr2Ti6jatGYa2Lp1KyFEJpONGTPmn3/+UavVJSUl8+fPb2ho6FC0lk+Wptsp9oM3zdHR8d13371x40Ztbe2rr75KCHnnnXfYp5m9tTxw4ADz3zpo0KADBw60d2Z+fr5IJDJx44kgtgyxKdU1LANRWVkJf7711ltCoTAvLw/+5CKqNq2ZBmDR7EmTJhl1OVgZE4ls1KhRzJ6amhpiUMDqUCKTSCTtmbJLS0slEsmIESPY6R5B7AjDRMZnryUoxuDusqCgYMeOHcuWLWNWVDQrqjatmWaAW8udO3d2kcvhYcEWVfv4+PTo0YM9EIwLMTExNE03NjaeP38+MDBw5cqVhiKjq1evTpw4sVevXn/++Wd3XWwYeQSxlUSWkJDg4+PD1P7NiqrNaqaBW7duVVRUjBw5knHv2CygdWegKIru1PhvsVg8YsSIQ4cOubq6fv311+xDV65cmTBhgrOz8/Hjx9lLliGIvcPnFCWpVCoWi3Nzc3///ffjx48fOHCAMS8zompY4MuQzMzMhoaGtWvXLl++HPboaaYBuByLiIjo2ldiFToqiW5ra7tx4wbzZ0lJSWRkpFgszsrK0lPgIoi9w+cVmVAoDAkJycvLW7Vq1bhx42AtL8CsqNqsZhowurRH57BQ8Ww5JvzLCxYs2Lhxo0KhUKvVjY2NOTk50dHRWq2W6fQsKCiYMGGCWCw+ceKEr68vH+EjSFfCLphZf0DsypUrCSEUReXm5uodMi2qNquZBmbOnElRVIeW7G6PTvdamjU4w030u+++y36U0W6B9vzL7OWsGUQiEWjBaZp+/fXXjQbACJoRxI4gNlXsJ4QMHTqUEPLyyy8bLn1mWlRtWjPNNKJQKKRSabepB7XnX/7xxx8/+OCDkSNHent7Ozs7w6KNBQUF48eP5ytUBLEmD1SU09LSZs+eTVvLMULT9LPPPnv27Nni4mJcCAtBEI5QFJWamhobG8vs4e2KrKamJj4+PjMzc/Xq1ZjFEASxBB4SWWFhIUVRvr6+e/fuDQoK0ltszWqgRRpBug08JDJY6s3V1TU2NjYjI0MkElk/BgRBuhM8JDKYZHP79u3U1FQeBzQx/R3r168nhOTk5Oj1jCQkJFg5JPTHIkgnYX91u7GP7IUXXhAIBEZnFz7//PMURTU2Nlo/Kj3QH4sgXCC2NvzCauTn5z/55JNGZxcqFAp/f3+xWGz9qPRAfyyCdBJ2VuuuV2QNDQ2EkLi4OMNDlZWVhJBp06ZZP6qHC/pjkUcHYjtXZGCI/eijj8aPH+/u7r5v3z6FQiGTyTw9PdPS0pjTVCpVYmIiCGCfeOKJpUuX3rp1izkKutTPP//87NmzI0eOFIvFoaGhcrkcjt67d8/BwYGiKA8PD0LI/v37mR5Jxr8KkzEDAwONtsAd9MciCI/wNmkcMsgnn3xSV1dHCElKSqIoqqysjBCybt06GOpWUVExfvz48vJyeEhZWdm3335bXFyclZUFQ9uhkevXr0dGRup0OkKIQqGYNWtWZWWlUCisqqpqbW01+uxMxoHJmBqNxmgLXfkGdAz0xyKIKdiXZ9a8tYyPjyeEfPzxx7du3QoMDCSEfPrppxqNJjQ01N/fn6bptrY2mOw9a9asoqIinU6Xk5MjkUgIIYWFhdBIVFQUIaR///6//vqrWq2+dOlSQEAAIaS0tJT9XKtWrSKEXLx40TCMKVOmcGnBLOiPRRCrQWzHEDt06FBPT0+Y4B0REfH444+D4XrixIljxoyhafrYsWPwzWxra2MelZSURAg5cuQI/Onr60tR1Llz55gTFi9eTAipqalhP9fw4cMfe+wxo2F4e3tzacE6oD8WQbhgmMj4qZHpdLrCwsJJkybBBG+lUhkZGSkQCGiaViqVMpmMELJv3z5CyObNm9kTpNva2gghoC2rrq6+fv362LFj2W7VwsJCDw8P9vC027dvK5VKo+OzKisrb968abYFWwD9sQhiAn4SmVKpvHfvHtw5lpSU1NfXw3ZhYaFKpQJzbF5enrOzM1suRgg5ffq0UCiEnaCWZa+i1NLSkpeXx/7OE0JOnjzZ2tpqNJFBic1sC7YA+mMRxAT8JDLIILAaG3QRsrfDw8MJIY2NjXqymvT09NOnT0+dOhWuyKAROBnIz8/X6XTsPYSQs2fPEkImTJhgGAZU+s22YBdY7o+dMGFCz5490R+L2CP8JDKFQiESiUJCQgghcrncxcUF6v1yudzd3R1uLQcMGKDVardu3apWq9Vq9a5du2JiYoRC4caNG6GRvLw8oVA4YsQIptlz586RBxMTIQR6QsvLy/Wsqtxb4AL6YxGET9gFM6sV+wMDA8eOHQvbQUFBERERsC2VSidPngzbhmJViqJ27NjBNOLt7T1s2DB2s3FxcQKBQG9ijZ5dY/HixR1tgQvoj0UQq0Fsodjf1NRUVFQE95IajaagoACugFQqVXFxMXM1FBcXt3fv3qFDhzo7O3t5eUVFRWVnZy9ZsgSOVlRU3Lx5E5bjZZDL5VKp1N3dnb0zMTExNjaWsaoyyylxb8FeQH8s8sjCpyEWQRCkE9iQIRZBEORhgYmsXVAhiyD2AiYyBEHsHj5XGrdxsFaIIPYCXpEhCGL3YCJDEMTuwUSGIIjdg4kMQRC7BxMZgiB2j5FeS70JLgiCIDbOA1OUqqqqQHqDIAhiy4wePRrE90An/XwIgiC2A9bIEASxezCRIQhi92AiQxDE7vkfyq+dH4Zz7PIAAAAASUVORK5CYII=\n",
"text/plain": [
"DatabaseTable[table]\n",
" name: functional_alltypes\n",
" schema:\n",
" index : int64\n",
" Unnamed__0 : int64\n",
" id : int32\n",
" bool_col : boolean\n",
" tinyint_col : int16\n",
" smallint_col : int16\n",
" int_col : int32\n",
" bigint_col : int64\n",
" float_col : float32\n",
" double_col : float64\n",
" date_string_col : string\n",
" string_col : string\n",
" timestamp_col : timestamp\n",
" year_ : int32\n",
" month_ : int32"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"t = con['omniscidb'].table('functional_alltypes')\n",
"t"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Percent Rank and CumeDist definition"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"def win_count(se):\n",
" count = copy(se)\n",
" count[:] = se.count()\n",
" return count\n",
"\n",
"\n",
"def sql_percent_rank(se):\n",
" \"\"\"\n",
" PERCENT_RANK = (RANK – 1)/(COUNT -1)\n",
" \"\"\"\n",
" return (se.rank(method='min') - 1) / (se.apply(win_count) - 1)\n",
"\n",
"\n",
"def sql_cume_dist(se):\n",
" \"\"\"\n",
" CUME_DIST = RANK/COUNT\n",
" \"\"\"\n",
" return se.rank(method='min') / se.apply(win_count)\n",
"\n",
"\n",
"def pd_percent_rank(se): \n",
" return se.rank(method='min', pct=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Test CUME DIST using Ibis/OmniSciDB"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"alltypes = t.execute()\n",
"\n",
"analytic_alltypes = alltypes.sort_values('id').groupby('string_col')\n",
"\n",
"result = alltypes.assign(\n",
" pandas_pct_rank=pd_percent_rank(analytic_alltypes.id),\n",
" cume_dist=sql_cume_dist(analytic_alltypes.id),\n",
" pct_rank=sql_percent_rank(analytic_alltypes.id),\n",
").set_index('id').sort_index()\n",
"\n",
"# display(result[['pandas_pct_rank', 'cume_dist']].describe())\n",
"pd.testing.assert_series_equal(\n",
" result['pandas_pct_rank'], result['cume_dist'],\n",
" check_dtype=False,\n",
" check_names=False,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Test OmniSciDB Percent Rank"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>cume_dist_val</th>\n",
" <th>percent_rank_val</th>\n",
" </tr>\n",
" <tr>\n",
" <th>id</th>\n",
" <th></th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>0.00137</td>\n",
" <td>0.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>0.00137</td>\n",
" <td>0.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>0.00137</td>\n",
" <td>0.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>0.00137</td>\n",
" <td>0.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>0.00137</td>\n",
" <td>0.0</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" cume_dist_val percent_rank_val\n",
"id \n",
"0 0.00137 0.0\n",
"1 0.00137 0.0\n",
"2 0.00137 0.0\n",
"3 0.00137 0.0\n",
"4 0.00137 0.0"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sql = '''\n",
"SELECT \n",
" id, cume_dist() OVER (PARTITION BY \"string_col\" ORDER BY \"id\") AS cume_dist_val,\n",
" percent_rank() OVER (PARTITION BY \"string_col\" ORDER BY \"id\") AS percent_rank_val\n",
"FROM functional_alltypes\n",
"'''\n",
"\n",
"cur = con['omniscidb'].con.execute(sql)\n",
"df = cursor2df(cur).set_index('id').sort_index()\n",
"df.head()"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"pd.testing.assert_series_equal(\n",
" result['cume_dist'], \n",
" df['cume_dist_val'],\n",
" check_dtype=False,\n",
" check_names=False,\n",
")\n",
"\n",
"pd.testing.assert_series_equal(\n",
" result['cume_dist'], \n",
" df['cume_dist_val'],\n",
" check_dtype=False,\n",
" check_names=False,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Conclusions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This notebook implemented Percent Rank and CumeDist for pandas. The tests against OmniSciDB passed with success. "
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment