Skip to content

Instantly share code, notes, and snippets.

@versae
Last active August 11, 2017 22:57
Show Gist options
  • Save versae/97132435ca226a94c47d0f7d138ff4b6 to your computer and use it in GitHub Desktop.
Save versae/97132435ca226a94c47d0f7d138ff4b6 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Outperforming `numpy.random.choice()`\n",
"\n",
"With the purpose of sampling pixels from the composite histogram of a collection of images, I'm now using `numpy.random.choice()` to sample an arbitrary fraction of pixels using the counts of colors as weights. Therefore, the problem can be seen as a special case of weighted random selection with replacement.\n",
"\n",
"The current code of `numpy.random.choice()` is based on the cumulative distribution function of the weights, but there seem to be better approaches to it, like reservoir sampling, the alias method, or roulette. [Efraimidis and Spirakis](http://utopia.duth.gr/~pefraimi/research/data/2007EncOfAlg.pdf)' or [Chao](https://academic.oup.com/biomet/article-abstract/69/3/653/221439/A-general-purpose-unequal-probability-sampling)'s reservoir algorithms do not quite work as they are speficied in our case, since we aim to generate samples bigger than the number of distinct elements based on their counts. In this notebook I profile some versions of these algorithms using naive Python, Cython, and some adaptations extracted from StackOverflow and the Internet, like [Jason Orendorff](https://stackoverflow.com/a/2149533/2154372)'s implementation, [J.F. Sebastian](https://stackoverflow.com/questions/13047806/weighted-random-sample-in-python)'s or [Eli Bendersky](http://eli.thegreenplace.net/2010/01/22/weighted-random-generation-in-python)'s adaptation using bisect, [sholte](https://stackoverflow.com/questions/6432499/how-to-do-weighted-random-sample-of-categories-in-python)'s roulette wheel selection, and Walker's Alias method as implemented by [Denis Bzowy](http://code.activestate.com/recipes/576564-walkers-alias-method-for-random-objects-with-diffe/). My own implementation of [Chao](https://academic.oup.com/biomet/article-abstract/69/3/653/221439/A-general-purpose-unequal-probability-sampling)'s reservoir sampling is included but not added."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"import cython\n",
"import numpy as np\n",
"import json\n",
"import random\n",
"from itertools import chain\n",
"import pyximport\n",
"pyximport.install(setup_args={\"include_dirs\": np.get_include()},\n",
" reload_support=True)\n",
"from histonets.utils import parse_histogram"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Data is coming from a real composite histogram of a collection of images."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"pixels: 277876704\n",
"colors: 896522\n"
]
}
],
"source": [
"h = parse_histogram(json.load(open(\"/Users/versae/Downloads/hist.txt\", \"r\")))\n",
"colors = np.fromiter(h.keys(), dtype='i8,i8,i8', count=-1)\n",
"counts = np.fromiter(h.values(), dtype=np.float32, count=-1)\n",
"size = counts.sum()\n",
"weights = counts / size\n",
"print(\"pixels:\", int(size))\n",
"print(\"colors:\", len(colors))"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"%reload_ext cython"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def weighted_sample_naive(items, weights, n):\n",
" \"\"\"Naive version of weighted sampling. The code is modified so it\n",
" can more samples than in the population\"\"\"\n",
" total = np.sum(weights)\n",
" i, w = 0, weights[0]\n",
" size = len(items)\n",
" while n:\n",
" x = total * (1 - random.random() ** (1.0 / n))\n",
" total -= x\n",
" while x > w:\n",
" x -= w\n",
" i += 1\n",
" w = weights[i % size]\n",
" w -= x\n",
" yield items[i % size]\n",
" n -= 1"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"%%cython\n",
"import random\n",
"\n",
"import numpy as np\n",
"cimport numpy as np\n",
"cimport cython\n",
"\n",
"\n",
"@cython.boundscheck(False)\n",
"@cython.wraparound(False)\n",
"def weighted_sample(np.ndarray items, np.ndarray weights, int n):\n",
" cdef float total = np.sum(weights), w = weights[0], x = 0.0\n",
" cdef Py_ssize_t i = 0, l = len(items), m = i % l\n",
" for s in range(n, 0, -1):\n",
" x = total * (1 - random.random() ** (1.0 / s))\n",
" total -= x\n",
" while x > w:\n",
" x -= w\n",
" i += 1\n",
" m = i % l\n",
" w = weights[m]\n",
" w -= x\n",
" yield items[m]\n",
"\n",
"\n",
"@cython.boundscheck(False)\n",
"@cython.wraparound(False)\n",
"def weighted_sample_copy(np.ndarray items, np.ndarray weights, int n):\n",
" cdef np.ndarray out = np.empty(n, dtype='i8,i8,i8')\n",
" cdef float total = np.sum(weights), w = weights[0], x = 0.0\n",
" cdef Py_ssize_t i = 0, l = len(items), m = i % l, o = 0\n",
" for s in range(n, 0, -1):\n",
" x = total * (1 - random.random() ** (1.0 / s))\n",
" total -= x\n",
" while x > w:\n",
" x -= w\n",
" i += 1\n",
" m = i % l\n",
" w = weights[m]\n",
" w -= x\n",
" out[o] = items[m]\n",
" o += 1\n",
" return out"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's check it does what it's supposed to do"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[array([1, 1, 1]),\n",
" array([1, 1, 1]),\n",
" array([1, 1, 1]),\n",
" array([1, 1, 1]),\n",
" array([1, 1, 1]),\n",
" array([1, 1, 1]),\n",
" array([1, 1, 1]),\n",
" array([2, 2, 2]),\n",
" array([3, 3, 3]),\n",
" array([3, 3, 3])]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(weighted_sample_naive(np.array([(1,1,1), (2,2,2), (3,3,3)]), np.array([0.6, 0.3, 0.1]), 10))"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([(1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1),\n",
" (1, 1, 1), (1, 1, 1), (2, 2, 2), (3, 3, 3)],\n",
" dtype=[('f0', '<i8'), ('f1', '<i8'), ('f2', '<i8')])"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"weighted_sample_copy(np.array([(1,1,1), (2,2,2), (3,3,3)]), np.array([0.6, 0.3, 0.1]), 10)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[array([1, 1, 1]),\n",
" array([1, 1, 1]),\n",
" array([1, 1, 1]),\n",
" array([1, 1, 1]),\n",
" array([1, 1, 1]),\n",
" array([1, 1, 1]),\n",
" array([2, 2, 2]),\n",
" array([2, 2, 2]),\n",
" array([2, 2, 2]),\n",
" array([2, 2, 2])]"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(weighted_sample(np.array([(1,1,1), (2,2,2), (3,3,3)]), np.array([0.6, 0.3, 0.1]), 10))"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"%%cython\n",
"# J.F. Sebastian answer: https://stackoverflow.com/questions/13047806/weighted-random-sample-in-python\n",
"import bisect\n",
"import random\n",
"import numpy as np\n",
"cimport numpy as np\n",
"from collections import Counter, Sequence\n",
"\n",
"\n",
"class WeightedPopulation(Sequence):\n",
" \n",
" def __init__(self, np.ndarray population, np.ndarray weights):\n",
"# assert len(population) == len(weights) > 0\n",
" self.population = population\n",
" self.cumweights = weights.cumsum()\n",
" \n",
" def __len__(self):\n",
" return self.cumweights[-1]\n",
" \n",
" def __getitem__(self, int i):\n",
" if not 0 <= i < self.cumweights[-1]:\n",
" raise IndexError(i)\n",
" return self.population[bisect.bisect(self.cumweights, i)]\n",
"\n",
"def weighted_sample_bisect(population, weights, k):\n",
" return random.sample(WeightedPopulation(population, weights), k)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[array([1, 1, 1]),\n",
" array([1, 1, 1]),\n",
" array([2, 2, 2]),\n",
" array([2, 2, 2]),\n",
" array([2, 2, 2]),\n",
" array([3, 3, 3]),\n",
" array([1, 1, 1]),\n",
" array([1, 1, 1]),\n",
" array([1, 1, 1]),\n",
" array([1, 1, 1])]"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"weighted_sample_bisect(np.array([(1,1,1), (2,2,2), (3,3,3)]), np.array([60, 30, 10]), 10)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"%%cython\n",
"# Eli Bendersky's post http://eli.thegreenplace.net/2010/01/22/weighted-random-generation-in-python\n",
"import random\n",
"import bisect\n",
"import numpy as np\n",
"cimport numpy as np\n",
"\n",
"class WeightedRandomGenerator(object):\n",
" def __init__(self, weights):\n",
" self.totals = weights.cumsum()\n",
"\n",
" def next(self):\n",
" rnd = random.random() * self.totals[-1]\n",
" return bisect.bisect_right(self.totals, rnd)\n",
"\n",
" def __call__(self):\n",
" return self.next()\n",
"\n",
"def weighted_random_generator(items, weights, n):\n",
" wrg = WeightedRandomGenerator(weights)\n",
" return items[[wrg.next() for _ in range(n)]]"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[1, 1, 1],\n",
" [1, 1, 1],\n",
" [2, 2, 2],\n",
" [1, 1, 1],\n",
" [1, 1, 1],\n",
" [1, 1, 1],\n",
" [1, 1, 1],\n",
" [1, 1, 1],\n",
" [3, 3, 3],\n",
" [1, 1, 1]])"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"weighted_random_generator(np.array([(1,1,1), (2,2,2), (3,3,3)]), np.array([0.6, 0.3, 0.1]), 10)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"# sholte's SO answer: https://stackoverflow.com/questions/6432499/how-to-do-weighted-random-sample-of-categories-in-python\n",
"# based on the roulette wheel selection\n",
"\n",
"def roulette_sampling(items, counts, n):\n",
" return items[np.array(counts).cumsum().searchsorted(np.random.sample(n))]"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[1, 1, 1],\n",
" [2, 2, 2],\n",
" [1, 1, 1],\n",
" [1, 1, 1],\n",
" [1, 1, 1],\n",
" [1, 1, 1],\n",
" [1, 1, 1],\n",
" [2, 2, 2],\n",
" [1, 1, 1],\n",
" [1, 1, 1]])"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"roulette_sampling(np.array([(1,1,1), (2,2,2), (3,3,3)]), np.array([0.6, 0.3, 0.1]), 10)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# # http://nbviewer.jupyter.org/gist/mehitabel/7144197\n",
"# import numpy as np\n",
"# import heapq as hq\n",
"\n",
" \n",
"# class BoundedHeap(object):\n",
"# \"\"\" Represents a bounded min heap \"\"\"\n",
"# def __init__(self, max_size=100):\n",
"# self.heap = []\n",
"# self.max_size = max_size\n",
" \n",
"# def __len__(self):\n",
"# return len(self.heap)\n",
" \n",
"# def __iter__(self):\n",
"# for x in self.heap:\n",
"# yield x\n",
" \n",
"# def min(self):\n",
"# return self.heap[0][0]\n",
" \n",
"# def add(self, x):\n",
"# if len(self) < self.max_size:\n",
"# hq.heappush(self.heap, x)\n",
"# elif x > self.heap[0]: \n",
"# hq.heapreplace(self.heap, x)\n",
" \n",
"# def consume(self, iterable):\n",
"# for x in iterable:\n",
"# self.add(x)\n",
"\n",
" \n",
"# def weighted_reservoir_sample(iterable, sample_size):\n",
"# \"\"\" \n",
"# eats an iterable of terms that look like (item, weight)\n",
"# returns a weighted sample without replacement.\n",
"# \"\"\"\n",
"# def clean(iterable):\n",
"# \"\"\" flipped the signs because our heap keeps track of max instead of min\n",
"# \"\"\"\n",
"# for item, weight in iterable:\n",
"# yield -np.random.exponential(scale=1.0 / weight), item\n",
"# reservoir = BoundedHeap(sample_size)\n",
"# reservoir.consume(clean(iterable))\n",
"# for rnd, val in reservoir:\n",
"# yield val"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"# [a for a in weighted_reservoir_sample(zip(*[np.array([(1,1,1), (2,2,2), (3,3,3)]), np.array([60, 30, 10])]), 100)]"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
"# Walker's Alias method as implemented by Denis Bzowy in ActiveState\n",
"# http://code.activestate.com/recipes/576564-walkers-alias-method-for-random-objects-with-diffe/\n",
"class WalkerRandom:\n",
" \"\"\" Walker's alias method for random objects with different probablities\n",
" \"\"\"\n",
" def __init__( self, weights, keys=None ):\n",
" \"\"\" builds the Walker tables prob and inx for calls to random().\n",
" The weights (a list or tuple or iterable) can be in any order;\n",
" they need not sum to 1.\n",
" \"\"\"\n",
" n = self.n = len(weights)\n",
" self.keys = keys\n",
" sumw = sum(weights)\n",
" prob = [w * n / sumw for w in weights] # av 1\n",
" inx = [-1] * n\n",
" short = [j for j, p in enumerate( prob ) if p < 1]\n",
" long = [j for j, p in enumerate( prob ) if p > 1]\n",
" while short and long:\n",
" j = short.pop()\n",
" k = long[-1]\n",
" # assert prob[j] <= 1 <= prob[k]\n",
" inx[j] = k\n",
" prob[k] -= (1 - prob[j]) # -= residual weight\n",
" if prob[k] < 1:\n",
" short.append( k )\n",
" long.pop()\n",
" self.prob = prob\n",
" self.inx = inx\n",
"\n",
" def random( self ):\n",
" \"\"\" each call -> a random int or key with the given probability\n",
" fast: 1 randint(), 1 random.uniform(), table lookup\n",
" \"\"\"\n",
" u = random.uniform( 0, 1 )\n",
" j = random.randint( 0, self.n - 1 ) # or low bits of u\n",
" randint = j if u <= self.prob[j] \\\n",
" else self.inx[j]\n",
" return self.keys[randint] if self.keys \\\n",
" else randint\n",
"\n",
"def walker_alias(items, weights, n):\n",
" walker = WalkerRandom(weights)\n",
" return items[[walker.random() for _ in range(n)]]"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[1, 1, 1],\n",
" [3, 3, 3],\n",
" [1, 1, 1],\n",
" [1, 1, 1],\n",
" [3, 3, 3],\n",
" [1, 1, 1],\n",
" [2, 2, 2],\n",
" [2, 2, 2],\n",
" [1, 1, 1],\n",
" [3, 3, 3]])"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"walker_alias(np.array([(1,1,1), (2,2,2), (3,3,3)]), np.array([0.6, 0.3, 0.1]), 10)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# %%cython\n",
"# # A-Chao: https://academic.oup.com/biomet/article-abstract/69/3/653/221439/A-general-purpose-unequal-probability-sampling\n",
"# import numpy as np\n",
"# cimport numpy as np\n",
"# import random\n",
"\n",
"# def weighted_reservoir_chao(np.ndarray items, np.ndarray weights, int k):\n",
"# reservoir = np.zeros(k, dtype='i8,i8,i8')\n",
"# n = len(items)\n",
"# for i in range(k):\n",
"# reservoir[i] = items[i]\n",
"# for i in range(k + 1, n):\n",
"# if random.random() <= weights[i]:\n",
"# reservoir[random.randint(1, k - 1)] = items[i]\n",
"# return reservoir"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"# weighted_reservoir_chao(np.array([(1,1,1), (2,2,2), (3,3,3)]), np.array([60, 30, 10]), 2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now let's time some basic execution"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"380 ms ± 14 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"54.1 ms ± 1.55 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"58.1 ms ± 1.4 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"58.3 ms ± 4.11 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"65.3 ms ± 212 µs per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"7.5 ms ± 479 µs per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"3.51 s ± 28.6 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"14.2 ms ± 496 µs per loop (mean ± std. dev. of 3 runs, 1 loop each)\n"
]
}
],
"source": [
"%timeit -r 3 -n 1 list(weighted_sample_naive(colors, counts, 10000))\n",
"%timeit -r 3 -n 1 np.fromiter(weighted_sample(colors, counts, 10000), dtype='i8,i8,i8')\n",
"%timeit -r 3 -n 1 weighted_sample_copy(colors, counts, 10000)\n",
"%timeit -r 3 -n 1 weighted_sample_bisect(colors, counts.astype(int), 10000)\n",
"%timeit -r 3 -n 1 weighted_random_generator(colors, weights, 10000)\n",
"%timeit -r 3 -n 1 roulette_sampling(colors, counts, 10000)\n",
"%timeit -r 3 -n 1 walker_alias(colors, weights, 10000)\n",
"%timeit -r 3 -n 1 np.random.choice(colors, size=10000, p=weights)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we are ready to execute with full setup and take some measurements"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"bisect\n",
"10\n",
"5.58 ms ± 835 µs per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"100\n",
"5.77 ms ± 58.2 µs per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"1000\n",
"13.7 ms ± 1.92 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"10000\n",
"82 ms ± 1.78 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"100000\n",
"796 ms ± 17 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"1000000\n",
"8.22 s ± 72.8 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"10000000\n",
"1min 22s ± 125 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"naive\n",
"10\n",
"354 ms ± 21 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"100\n",
"367 ms ± 6.86 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"1000\n",
"373 ms ± 463 µs per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"10000\n",
"410 ms ± 7.63 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"100000\n",
"784 ms ± 6.66 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"1000000\n",
"4.22 s ± 20.7 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"10000000\n",
"37.7 s ± 721 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"cythonize\n",
"10\n",
"46.2 ms ± 3.73 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"100\n",
"55.2 ms ± 245 µs per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"1000\n",
"53.2 ms ± 884 µs per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"10000\n",
"80.5 ms ± 3.8 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"100000\n",
"329 ms ± 3.81 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"1000000\n",
"2.81 s ± 31.1 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"10000000\n",
"27.8 s ± 141 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"cython_copy\n",
"10\n",
"45.5 ms ± 5.16 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"100\n",
"55 ms ± 1.39 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"1000\n",
"57.5 ms ± 2.32 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"10000\n",
"79.1 ms ± 690 µs per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"100000\n",
"320 ms ± 5.75 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"1000000\n",
"2.82 s ± 8.11 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"10000000\n",
"27.8 s ± 188 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"numpy_choice\n",
"10\n",
"12.5 ms ± 1.79 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"100\n",
"11.7 ms ± 840 µs per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"1000\n",
"13.5 ms ± 318 µs per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"10000\n",
"38.8 ms ± 1.13 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"100000\n",
"291 ms ± 10.9 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"1000000\n",
"2.86 s ± 31.3 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"10000000\n",
"28.5 s ± 121 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"wrg\n",
"10\n",
"2.54 ms ± 110 µs per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"100\n",
"3.43 ms ± 169 µs per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"1000\n",
"11.8 ms ± 1.58 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"10000\n",
"87.3 ms ± 2.34 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"100000\n",
"864 ms ± 5.61 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"1000000\n",
"8.64 s ± 57.6 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"10000000\n",
"1min 27s ± 416 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"roulette\n",
"10\n",
"4.67 ms ± 683 µs per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"100\n",
"4.38 ms ± 236 µs per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"1000\n",
"6.97 ms ± 388 µs per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"10000\n",
"33.2 ms ± 1.22 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"100000\n",
"270 ms ± 5.1 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"1000000\n",
"2.62 s ± 57.9 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"10000000\n",
"26.4 s ± 208 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"walker\n",
"10\n",
"3.47 s ± 12.2 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"100\n",
"3.56 s ± 27.9 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"1000\n",
"3.52 s ± 1.92 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"10000\n",
"3.6 s ± 8.16 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"100000\n",
"4.09 s ± 23.5 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"1000000\n",
"8.66 s ± 21.5 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"10000000\n",
"56.6 s ± 791 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n"
]
}
],
"source": [
"max_exp = 8\n",
"\n",
"bisect = {'n': [], 'time': []}\n",
"naive = {'n': [], 'time': []}\n",
"cythonized = {'n': [], 'time': []}\n",
"cython_copy = {'n': [], 'time': []}\n",
"wrg = {'n': [], 'time': []}\n",
"roulette = {'n': [], 'time': []}\n",
"walker = {'n': [], 'time': []}\n",
"numpy_choice = {'n': [], 'time': []}\n",
"\n",
"print(\"bisect\")\n",
"for exp in range(1, max_exp):\n",
" size = 10 ** exp\n",
" print(size)\n",
" bisect['n'].append(size)\n",
" run = %timeit -r 3 -n 1 -o np.fromiter(chain.from_iterable(weighted_sample_bisect(colors, counts.astype(int), size)), np.uint8, count=-1)\n",
" bisect['time'].append(run.best)\n",
" \n",
"print(\"naive\")\n",
"for exp in range(1, max_exp):\n",
" size = 10 ** exp\n",
" print(size)\n",
" naive['n'].append(size)\n",
" run = %timeit -r 3 -n 1 -o np.fromiter(chain.from_iterable(weighted_sample_naive(colors, counts, size)), np.uint8, count=-1)\n",
" naive['time'].append(run.best)\n",
" \n",
"print(\"cythonize\")\n",
"for exp in range(1, max_exp):\n",
" size = 10 ** exp\n",
" print(size)\n",
" cythonized['n'].append(size)\n",
" run = %timeit -r 3 -n 1 -o np.fromiter(chain.from_iterable(weighted_sample(colors, counts, size)), np.uint8, count=-1)\n",
" cythonized['time'].append(run.best)\n",
" \n",
"print(\"cython_copy\")\n",
"for exp in range(1, max_exp):\n",
" size = 10 ** exp\n",
" print(size)\n",
" cython_copy['n'].append(size)\n",
" run = %timeit -r 3 -n 1 -o np.fromiter(chain.from_iterable(weighted_sample_copy(colors, counts, size)), np.uint8, count=-1)\n",
" cython_copy['time'].append(run.best)\n",
" \n",
"print(\"numpy_choice\")\n",
"for exp in range(1, max_exp):\n",
" size = 10 ** exp\n",
" print(size)\n",
" numpy_choice['n'].append(size)\n",
" run = %timeit -r 3 -n 1 -o np.fromiter(chain.from_iterable(np.random.choice(colors, p=weights, size=size)), np.uint8, count=-1)\n",
" numpy_choice['time'].append(run.best)\n",
"\n",
"print(\"wrg\")\n",
"for exp in range(1, max_exp):\n",
" size = 10 ** exp\n",
" print(size)\n",
" wrg['n'].append(size)\n",
" run = %timeit -r 3 -n 1 -o np.fromiter(chain.from_iterable(weighted_random_generator(colors, weights, size)), np.uint8, count=-1)\n",
" wrg['time'].append(run.best)\n",
"\n",
"print(\"roulette\")\n",
"for exp in range(1, max_exp):\n",
" size = 10 ** exp\n",
" print(size)\n",
" roulette['n'].append(size)\n",
" run = %timeit -r 3 -n 1 -o np.fromiter(chain.from_iterable(roulette_sampling(colors, counts, size)), np.uint8, count=-1)\n",
" roulette['time'].append(run.best) \n",
"\n",
"print(\"walker\")\n",
"for exp in range(1, max_exp):\n",
" size = 10 ** exp\n",
" print(size)\n",
" walker['n'].append(size)\n",
" run = %timeit -r 3 -n 1 -o np.fromiter(chain.from_iterable(walker_alias(colors, weights, size)), np.uint8, count=-1)\n",
" walker['time'].append(run.best) "
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [],
"source": [
"%matplotlib inline\n",
"import seaborn as sns\n",
"import pandas as pd\n",
"\n",
"bisect_df = pd.DataFrame.from_dict(bisect).set_index('n').rename(columns={'time': 'bisect'})\n",
"naive_df = pd.DataFrame.from_dict(naive).set_index('n').rename(columns={'time': 'naive'})\n",
"cythonized_df = pd.DataFrame.from_dict(cythonized).set_index('n').rename(columns={'time': 'cythonized'})\n",
"cython_copy_df = pd.DataFrame.from_dict(cython_copy).set_index('n').rename(columns={'time': 'cythonized_copy'})\n",
"wrg_df = pd.DataFrame.from_dict(cython_copy).set_index('n').rename(columns={'time': 'wrg'})\n",
"roulette_df = pd.DataFrame.from_dict(cython_copy).set_index('n').rename(columns={'time': 'roulette'})\n",
"walker_df = pd.DataFrame.from_dict(cython_copy).set_index('n').rename(columns={'time': 'walker'})\n",
"numpy_choice_df = pd.DataFrame.from_dict(numpy_choice).set_index('n').rename(columns={'time': 'numpy.choice()'})"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<matplotlib.text.Text at 0x125f3b748>"
]
},
"execution_count": 34,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA7YAAAH0CAYAAAAJ9bHWAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3XlcVnXC///XR0AwwRV3UWhTYlUIRSNR02qsXMqsUDPH\nabFVu+dXM07pbTU1d95NOVP5q0xTqUxLHaecmkYtTVyglFxIW8B91wBFZfl8/7guuFFREbg4gO/n\n48FDruuc8znvc13+8+ZzFmOtRURERERERKS2qud0ABEREREREZHKULEVERERERGRWk3FVkRERERE\nRGo1FVsRERERERGp1VRsRUREREREpFZTsRUREREREZFaTcVWRERqDGNMojFmZyW2n2aMeaYqM11g\nfx2MMbnGGK/q2qeIiIicTcVWRETKZIzJNMbkuYvbPmPMTGOMv9O5ihljRhljVpZ+z1r7oLX2uerK\nYK3dbq31t9YWVtc+PcEY42uMmW6MyTLG5Bhj1htjbi61PMn9/6D457gxxhpjYtzLl5yx/JQx5vtS\n20cbY1YYY341xuws/ceHC43tXqerMebrUv8XH3e/3+GMbXPd2z5Zatt73Md1zBiz0BjTrIzjv8oY\nc8IYM6fqP10REakOKrYiInI+t1pr/YGuQCzwJ4fziGd4AzuAXkBjXN/zR8aYYABrbbK7wPu7/z+M\nBX4GvnUvv/mM5auAeaXGfx/4Gmjm3sdYY8xt5RnbGBMI/Av4/4HmwJXAF+5tt5+xbQRQBHzs3jbM\nvd0IoBVwHHijjON/HVhXic9PREQcpmIrIiIXZK3dBSwBwgGMMW2NMf8wxhw2xvxojPld8brGmEnG\nmPnGmLnu2b9vjTFRpZZbY8yVpV7PNMY8X9Z+jTFPG2N+co+z2Rgz2P1+KDANiHfP0h0tayxjzO/c\n+Q6787Y9I8eDxphtxpijxpjXjTHmHDnijDGpxphs94zhK+73g93jeBtjirMU/5wwxmS616tX6lgO\nGWM+Kmvm8Bz7TnTPcj5pjNlvjNljjLmvPNuWl7X2mLV2krU201pbZK39J/ALEHOOTe4FZllrbRl5\ng4EEYFapt4OBZGttobX2J2AlEFbOsccDn7sL8ElrbY61dss5th0JfG2tzXS/TgIWW2u/ttbmAs8A\nQ4wxAaXy3gUcBf5zjjFFRKQWULEVEZELMsYEAb8BvnO/9SGwE2gL3AH82RjTp9QmA3HN2DXDNVu3\n0BjjU4Fd/4SrJDUG/huYY4xp4y42DwIp7tm6JmVk7gO8CNwJtAGy3LlLuwW4Foh0r3fjOXK8Brxm\nrW0EXAF8dOYK1tqUUjOHTYE1wAfuxY8Cg3DNVrYFjuCaJSzOmm6Muec8n0Nr92fQDvgt8LoxpmlZ\nKxpj3nAX9bJ+0s+zj9JjtAKuBjaVsawjcD2nF9fSRgIrSpVLgFeBkcYYH2NMJyAe+LKcY3cHDhtj\nVrmL/WJjTIcytjXufb9X6u0wYEPxC3epPuU+NowxjYDJuMqziIjUYiq2IiJyPgvds6Erga9wFdgg\noCfwlLX2hLV2PfAOrlJRLM1aO99amw+8AvjhKigXxVo7z1q72z2LOBfYBsSVc/Mk4F1r7bfW2pPA\nH3DN8AaXWucla+1Ra+12YBkQfY6x8oErjTGB1tpca+3qC+x7KpADTHC/fhCYYK3d6c4yCbjDGOPt\nPs5Ia+375xkvH5hsrc231n4G5AKdylrRWjvWWtvkHD+RF8iN+w8QycB71tqMMlYpLq6/nGOIkcDM\nM977J64/gOQBGcB0a21Zp/6WNXZ7XLO4jwMdcM0kf1DGttfhOt14fqn3/IFfz1jvV6B4xvY5d5YK\n37BMRERqBhVbERE5n0HuQtTRXZjycM04HrbW5pRaLwvXbGKxHcW/WGuL+L/Z3YtijBlpXDcyOuou\n2OFAYDk3b+vOVZwjFzh0Rs69pX4/jqsIleW3uGb5Mowx64wxt5wn8wNAInCP+9gBOgILSh3HFqAQ\nVxErj0PW2oJyZq0wY0w9YDauWc1HzrHambOipbe/Dtfs8vxS7zXDdY3sZFx/4AgCbjTGjC3n2HnA\nAmvtOmvtCVwz9z2MMY3PWO9e4GP391wsF2h0xnqNgBxjTDRwA/DXcxyniIjUIt5OBxARkVpnN9DM\nGBNQqtx2AHaVWieo+Bd3WWrv3g5cpeyyUuu2xlV8T+M+LfVtoC+uU44LjTHrgeLrYM+6vrOMnB1L\njdcQ182Hdp1zi3Ow1m4D7nYfyxBgvjGmeRmZE3DNAl5nrc0utWgHMNpa+83F7vtiGWOmAcPPsTjL\nWlvmta3uU3mn4yrbv3HPtp+5Tk9cfzCYf+Yyt3uBT84ol5cDhdba4tOLdxpjPsR1anvJjZzOM3Y6\np3/XZV3X2wAYCgw+Y9EmoPT13ZcDvsBWXH+sCAa2uy+t9ge8jDHXWGu7nuP4RESkhtKMrYiIXBRr\n7Q5cd7190RjjZ4yJxFUSSj8qJcYYM8R9qu0TwEmg+PTd9cA9xhgvY8xNuK47LUtDXCXmAID7hknh\npZbvA9obY+qfY/sPgPuM61EzvsCfgTVnXPtZLsaY4caYFu4Z2KPut4vOWCcI17W3I621W88YYhrw\ngrusY4xpYYwZeLE5ysP9yCP/c/yc64ZNAG8CobjuhJ13jnWKZ0VzzlzgLpd3cvZpyFtdi8097pto\ntQaG4Sqs5Rl7BjDY/T364LoB1EprbelTjAfjum552RnbJgO3GmMS3H/YmIyreOcAb+G6Xjra/TMN\n+JRzX2ctIiI1mIqtiIhUxN24Zrt2AwuAidba0jcDWoSrvBzB9aiVIaVmAB8HbsVVEJOAhWXtwFq7\nGfhfIAVXiY0ASs94LsU1I7fXGHOwjO2/xFWCPgb24Coxd138oQJwE7DJGJOL60ZSd5VR/vrivsaz\n1J2Ri2++9BrwD+ALY0wOrpLfrXhDY8wmY0xSBbNVmrtwP4Cr4O0tlT+p1Dp+uIprmach47o51lHO\nKJfumeshwDhc/x/WAxuB0nevPufY1tqlwB9xlc79uB73c+aNtu4FZp95l2Zr7SZc1zcnu7cNwPU4\nIay1x621e4t/cJ22fMJae+AcxyciIjWYKeNO/SIiIhVmjJkEXGmtPdfpsCIiIiJVSjO2IiIiIiIi\nUqup2IqIiIiIiEitplORRUREREREpFbTjK2IiIiIiIjUaiq2IiIiIiIiUqt5Ox2gPAIDA21wcLDT\nMURERERERMQD0tLSDlprW1R0+1pRbIODg0lNTXU6hoiIiIiIiHiAMSarMtvrVGQRERERERGp1VRs\nRUREREREpFZTsRUREREREZFarVZcY1uW/Px8du7cyYkTJ5yOIlXIz8+P9u3b4+Pj43QUERERERGp\nJWptsd25cycBAQEEBwdjjHE6jlQBay2HDh1i586dhISEOB1HRERERERqiVp7KvKJEydo3ry5Sm0d\nYoyhefPmmoUXEREREZGLUmuLLaBSWwfpOxURERERkYtVq4ut0zIzMwkPDz/r/TFjxrB58+Yq2cfR\no0d54403qmQsERERERGRukjF1gPeeecdrrnmmioZS8VWRERERETk/FRsK6mgoICkpCRCQ0O54447\nOH78OImJiaSmplJYWMioUaMIDw8nIiKCv/71rwD89NNP3HTTTcTExJCQkEBGRgYA+/btY/DgwURF\nRREVFcWqVat4+umn+emnn4iOjub3v/+9k4cqIiIiIiJSI9XauyLXFD/88APTp0+nZ8+ejB49+rTZ\n1fXr17Nr1y42btwIuGZfAe6//36mTZvGVVddxZo1axg7dixLly7lscceo1evXixYsIDCwkJyc3N5\n6aWX2LhxI+vXr3fk+ERERERERGq6OlFs/3vxJjbvzq7SMa9p24iJt4ZdcL2goCB69uwJwPDhw5k6\ndWrJsssvv5yff/6ZRx99lAEDBtC/f39yc3NZtWoVQ4cOLVnv5MmTACxdupRZs2YB4OXlRePGjTly\n5EhVHpaIiIiIiEidUyeKrZPOvItv6ddNmzZlw4YNfP7550ybNo2PPvqIV199lSZNmmgGVkRERERE\npIrUiWJbnplVT9m+fTspKSnEx8fz/vvvc91117F48WIADh48SP369bn99tvp1KkTw4cPp1GjRoSE\nhDBv3jyGDh2KtZb09HSioqLo27cvb775Jk888UTJqcgBAQHk5OQ4dnwiIiIiIiI1nW4eVUmdOnXi\n9ddfJzQ0lCNHjvDQQw+VLNu1axeJiYlER0czfPhwXnzxRQCSk5OZPn06UVFRhIWFsWjRIgBee+01\nli1bRkREBDExMWzevJnmzZvTs2dPwsPDdfMoERERERGRMhhrrdMZLig2Ntampqae9t6WLVsIDQ11\nKJF4kr5bEREREZFLizEmzVobW9HtNWMrIiIiIiIijikoLKr0GCq2IiIiIiIi4ohfj+fT46WllR5H\nxVZEREREREQcMS9tB/tzTlZ6HBVbERERERERqXZFRZbZq7OI7di00mOp2IqIiIiIiEi1+3rbAbIO\nHWdEfMdKj6ViKyIiIiIiItVudkoWgf6+3BzeptJjqdhWo2nTpjFr1iynY4iIiIiIiDhqx+HjLP1h\nP3fHBVHfu/K11LsKMkk5Pfjgg05HEBERERERcVzymu3UM4Z7unWokvE0Y1sJmZmZhIaG8rvf/Y6w\nsDD69+9PXl4eb7/9Ntdeey1RUVHcfvvtHD9+HIBJkyYxZcoUMjIyiIuLO22ciIgIANLS0ujVqxcx\nMTHceOON7Nmzx5FjExERERER8YQT+YXMXbedG0Jb0qZxgyoZU8W2krZt28bDDz/Mpk2baNKkCR9/\n/DFDhgxh3bp1bNiwgdDQUKZPn37aNp07d+bUqVP88ssvAMydO5dhw4aRn5/Po48+yvz580lLS2P0\n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\nRYsWMXv2bKejOCIzM5NbbrmFjRs3lrn8kUceoUuXLvz2t7895xg19bsVERERERGXD9Zu5w+ffM+8\nB+O5NrhZpcczxqRZa2MvvGbZdJFjFXr00UdZsmQJn332mdNRaqSYmBgaNmzI//7v/zodRURERERE\nKshay+yULDq3DiC2Y1On4wAqtlXqb3/7m9MRKuWFF14463rboUOHMmHChHKPERwcfM7Z2rS0tErl\nExERERER5327/Qib92Tz58ERZ9041ikqtlJiwoQJF1ViRURERETk0jMrJYsAX28GRrd1OkoJ3TxK\nREREREREyuVAzkk++34Pt8e0p6FvzZknVbEVERERERGRcpm7bjv5hZYR8R2djnIaFVsRERERERG5\noILCIpLXbOe6KwO5ooW/03FOo2IrIiIiIiIiF/Tllv3s+fVEjZutBRXbalFYWOh0BBERERERkUqZ\nszqLto396Nu5pdNRzqJiW0Evv/wyU6dOBWDcuHH06dMHgKVLl5KUlIS/vz9PPvkkUVFRpKSk8Nln\nn9G5c2diYmJ47LHHuOWWW5yMLyIiIiIiUm4/Hchl5Y8HuadbB7y9al6NrHmJaomEhARWrFgBQGpq\nKrm5ueTn57NixQquv/56jh07Rrdu3diwYQOxsbE88MADLFmyhLS0NA4cOOBwehERERERkfKbnZKF\nj5dh2LUdnI5Spppzf+ZKWPHRVg7uyK3SMQOD/Em48+pzLo+JiSEtLY3s7Gx8fX3p2rUrqamprFix\ngqlTp+Ll5cXtt98OQEZGBpdffjkhISEA3H333bz11ltVmldERERERMQTjp0s4OO0nfwmog0tAnyd\njlOmOlFsneDj40NISAgzZ86kR48eREZGsmzZMn788UdCQ0Px8/PDy8vL6ZgiIiIiIiKVsnD9LnJO\nFjCyBt40qlidKLbnm1n16H4TEpgyZQrvvvsuERERjB8/npiYGIwxp63XqVMnfv75ZzIzMwkODmbu\n3LmO5BUREREREbkY1lpmp2RxTZtGdO3Q1Ok456RrbCshISGBPXv2EB8fT6tWrfDz8yMhIeGs9Ro0\naMAbb7zBTTfdRExMDAEBATRu3NiBxCIiIiIiIuW3LvMIGXtzGBnf8awJvJqkTszYOqVv377k5+eX\nvN66dWvJ77m5p1/z27t3bzIyMrDW8vDDDxMbG1ttOUVERERERCpi9uosAvy8GRjdzuko56UZ22ry\n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\nN9LT05k0aRKjRo06K/PDDz9Mv379SE9PJy0tjdDQUDZu3MiCBQtYtWoV69evp6CggA8//BCAVatW\n0bVr15Lto6KiWL169cV9sCIiIiIiUilLNu7hYO4pRtTRR/yUVidORV6yZAl79+6t0jFbt27NzTff\nfMH1QkJCiI6OBiAmJobMzMzzrt+7d28CAgIICAigcePG3HrrrQBERESQnp5est7dd98NwPXXX092\ndjZHjx5l9OjRDBw4kCeeeIJ3332X++6776zxv/zyS5544gm8vFznzzdr1qxk2ZAhQ0pyfvbZZwCs\nXLmSTz/9FID+/fszatQojh07dtqYy5cvLymt3t7eNGrUiC+//JJ169YRGxsLQF5eHkFBQQBkZ2dz\n2WX/9xchb29vjDHk5eXRoEGD834+IiIiIiJSNWanZBHc/DISrgx0OorH1Yli6yRfX9+S3728vMjL\ny8Pb27vktNwTJ06cc/169eqVvK5Xrx4FBQUly4wxp21njCEoKIhWrVqxdOlS1q5de9Gn9xbvy8vL\n67R9lceZeay1jB49mueee+6sdevVO/tEgFOnTp127CIiIiIi4jmbd2eTmnWEPw0IpV49c+ENark6\nUWzLM7NanYKDg0lLSyMuLo758+dXaIy5c+fSu3dvVq5cSePGjWnc2PUg5TFjxjB8+HBGjBhRMitb\nWr9+/Zg2bRrXX389Xl5eHD58+LRZ2zMlJCSQnJzMH/7wB7788kvatWtHw4YNT1und+/eTJs2jUce\neYTCwkKOHTvGDTfcwB133MHjjz9OYGAghw4d4tixY3To0IErr7ySzMzMkmuL9+3bR7t27cosvCIi\nIiIiUvVmr87Ez6ceQ2OCnI5SLdQ0POC//uu/ePPNN+nSpQsHDx6s0Bh+fn506dKFBx98kOnTp5e8\nf9ttt5Gbm3vaacgLFixg8uTJADzwwAO0bt265EZPH3300Xn3M3nyZFJSUoiMjOTZZ59lxowZZ63z\n97//nc8//5yIiAhiY2PJyMggIiKCiRMncsMNNxAZGUn//v3Zt28fAAMGDGD58uUl2y9btowBAwZU\n6HMQEREREZGL82tePgu/283AqHY0vszH6TjVwlhrnc5wQbGxsbb4LsPFtmzZQmhoqEOJPCsxMZEp\nU6aUXL9aWmpqKuPGjWPFihUOJCufnTt3MmbMGP71r38BMHDgQF555RWuuOKKcm1fl79bERERERFP\n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\nkkEAAAAASUVORK5CYII=\n",
"text/plain": [
"<matplotlib.figure.Figure at 0x11ae20160>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"results = pd.concat([\n",
" bisect_df.T, naive_df.T, cythonized_df.T, cython_copy_df.T, wrg_df.T, roulette_df.T,\n",
" walker_df.T, numpy_choice_df.T\n",
"]).T\n",
"ax = results.plot(figsize=(16,8), logx=True)\n",
"ax.set_ylabel(\"Time (s)\")\n",
"ax.set_xlabel(\"Sample size (k << n)\")\n",
"ax.set_title(\"Population size: n = {}\".format(int(counts.sum())))"
]
},
{
"cell_type": "code",
"execution_count": 48,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(26, 28.5)"
]
},
"execution_count": 48,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA8QAAAHwCAYAAABg2KZlAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3XmQZOtZ3/nfk3lO7lld3VW9Vld3SyM2swnUgweEjBAM\nwgoDRmGEkJCEDcgabCNhYmZsbAIsvBCOQViOgEGyZcuAWCUEYpVko7GsYQld3bkGpCsjltu319tr\ndeWeeU6+88c5mXVyq8y+t7Kqu873E9FRuZ08p7K77+1fPe/7POacEwAAAAAAaZM56AsAAAAAAOAg\nEIgBAAAAAKlEIAYAAAAApBKBGAAAAACQSgRiAAAAAEAqEYgBAAAAAKlEIAYAPHLM7KVmduU5HP9T\nZvaDe3lNc853zszqZpbdr3MCAID5CMQAgOfEzJ4ys1Yc+J4xs3ebWeWgr2vAzL7DzD6WfMw59ybn\n3I/s1zU45552zlWcc+F+nXMZzCxvZu8ys0tmVjOzJ8zsryeef23852Dwq2lmzsxeFD//22PPd83s\njxPHv9DM/puZ3TezK8kfWsx77/g1X2pmH038WXxz/Pi5sWPr8bHfnzj2NfH31TCzXzWzY1O+/88y\ns7aZ/ezef7oAgINAIAYA7IVvcM5VJH2ppIuS/ukBXw+Ww5N0WdJXSTqi6Pf5l8zsgiQ5594TB/9K\n/OfheyT9haTH4+f/+tjzvyfplxPv/3OSPirpWHyO7zGzb1zkvc1sXdLvSHqHpDVJL5D0ofjYp8eO\n/UJJfUnvi4/9/Pi410k6Kakp6SenfP8/Ienjz+HzAwA8ZAjEAIA945y7Kum3JX2BJJnZGTP7gJnd\nNbM/M7PvHrzWzH7YzN5rZr8YVxsfN7MvTjzvzOwFifvvNrN/Pu28ZvaPzOzP4/f5lJl9c/z450n6\nKUlfHlcFt6a9l5l9d3x9d+PrPTN2HW8ys8+Y2ZaZ/YSZ2Yzr+DIze8zMtuMK5dvixy/E7+OZ2eBa\nBr/aZvZU/LpM4nu5Y2a/NK1SOePcL42rqt9vZjfN7LqZ/e1Fjl2Uc67hnPth59xTzrm+c+43JP2l\npBfNOOQNkn7aOeemXO8FSS+R9NOJhy9Ieo9zLnTO/bmkj0n6/AXf+x9K+mAcnDvOuZpz7skZx75e\n0kedc0/F918r6dedcx91ztUl/aCkV5pZNXG9r5a0Jem/zHhPAMAjiEAMANgzZrYp6RWS/r/4oV+Q\n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\nV8TPvVpjy6XN7IyZ/ZYkOecCSX9f0gcVNeP6JefcJ5d4rQAAAACAlFna2CXn3Mck2YznvmPKY9ck\nvSJx/7ck/dayrg8AAAAAkG770mUaAAAAAICHDYEYAAAAAJBKBGIAAAAAQCoRiAEAAAAAqUQgBgAA\nAACkEoEYAAAAAJBKBGIAAAAAQCoRiAEAAAAAqUQgBgAAAACkEoEYAAAAAJBKBGIAAAAAQCoRiAEA\nAAAAqUQgBgAAAACkEoEYAAAAAJBKBGIAAAAAQCoRiAEAAAAAqUQgBgAAAACkEoEYAAAAAJBKBGIA\nAAAAQCoRiAEAAAAAqUQgBgAAAACkEoEYAAAAAJBKBGIAAAAAQCoRiAEAAAAAqUQgBgAAAACkEoEY\nAAAAAJBKBGIAAAAAQCoRiAEAAAAAqUQgBgAAAACkEoEYAAAAAJBKBGIAAAAAQCoRiAEAAAAAqUQg\nBgAAAACkEoEYAAAAAJBKBGIAAAAAQCoRiAEAAAAAqUQgBgAAAACkEoEYAAAAAJBKBGIAAAAAQCoR\niAEAAAAAqUQgBgAAAACkEoEYAAAAAJBKBGIAAAAAQCoRiAEAAAAAqUQgBgAAAACkEoEYAAAAAJBK\nBGIAAAAAQCoRiAEAAAAAqUQgBgAAAACkEoEYAAAAAJBKBGIAAAAAQCoRiAEAAAAAqUQgBgAAAACk\nEoEYAAAAAJBKBGIAAAAAQCoRiAEAAAAAqUQgBgAAAACkEoEYAAAAAJBKBGIAAAAAQCoRiAEAAAAA\n/397dx4fVZXmf/zzGJAom2KQRoMSW0UEspCwd7EaZEQBUVoRRKBpWURa1BntYQZoGluUyK8bFRhR\nCGhUBGVxlBZpdkEwYWsgEZCJytIsQTuggCGc3x91SSeQBIRUKqS+79erXty659xzn1N1SPLUufdU\nSFJCLCIiIiIiIiFJCbGIiIiIiIiEJCXEIiIiIiIiEpKUEIuIiIiIiEhIUkIsIiIiIiIiIUkJsYiI\niIiIiIQkJcQiIiIiIiISkpQQi4iIiIiISEhSQiwiIiIiIiIhSQmxiIiIiIiIhKQKwQ4g0HJycti9\nezfHjx8PdihSgsLDw4mMjKRixYrBDkVERERERC5R5T4h3r17N1WrVqVu3bqYWbDDkRLgnCMrK4vd\nu3cTFRUV7HBEREREROQSVe4vmT5+/DjXXHONkuFyxMy45pprNOsvIiIiIiIXpdwnxICS4XJI76mI\n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\nL02bNqVZs2YMGDCAuLg4AOrVq8err75K/fr1+e677xg8eHBeW7169aJOnTrnPKeIiIiIiMiFMudc\nsGMoMQkJCS41NbXAvvT09DKZVM2ZM4f58+fz5ptvBjuUoMjMzOTuu+9my5YthZYPHTqUuLg4fvOb\n3xTZRll9b0VEREREpOSZWZpzLqEk29TNmUHw+OOPs3DhQj7++ONgh1ImxcfHU7lyZV566aVghyIi\nIiIiIuWYEuIgePnll4MdwkV57rnnzrqfuEePHowYMeK826hbt26Rs8NpaWkXFZ+IiIiIiMj5UEIs\nP9uIESN+VvIrIiIiIiJSFmlRLREREREREQlJSohFREREREQkJCkhFhERERERkZCkhFjyREZG8v33\n3593/blz5zJ+/PgLOtepU6do164dR48e5fjx47Ru3Zrc3NwLaktERERERORCaFEtuWD33nvvBR/7\n4YcfkpCQQJUqVQBo06YNc+bM4YEHHiip8ERERERERIqlGeIAy8zMpH79+vz2t7+lQYMGdOzYkWPH\njtG2bVtSU1MBOHToEHXr1gUgOTmZbt26kZiYSN26dXnllVeYMGECcXFxNG/enMOHDwPQtm1bfve7\n3xEbG0vDhg1Zt24dp06d4pZbbuHgwYOAfxb25ptvznt+2smTJxk+fDgNGzYkOjqaSZMm5ZX9+c9/\nJi4ujujoaLZv354XX5cuXYiOjqZly5Z5X5f0+uuv88QTTwDwj3/8g65duxIdHU1MTAxr164FYMaM\nGTRt2pTY2FiGDBnCqVOnAEhJSaFr16555+3WrRspKSkl+tqLiIiIiIgUJ6RmiF9Y9wIZhzNKtM3b\natzGM02fKbbOjh07eOedd5g6dSq//vWvef/994utv2XLFjZs2MDx48e5+eabeeGFF9iwYQPDhw9n\n5syZeUnojz/+yMaNG1mxYgX9+/dny5Yt9O7dm5SUFJ544gkWL15MTEwMNWvWLND+5MmT2bt3L5s2\nbSIsLCwvyQaoVasWGzZsYOLEiUyYMIEpU6bw3//93zRr1owFCxawaNEi+vbtm5fMn/bYY4+RmJjI\n0KFDOXnyJD/++CNbtmxh7ty5rF69mgoVKvDoo4/y7rvv8tBDD7F69WqSk5Pzjo+JieHzzz8/n5dc\nRERERESkRGiGuBRERUURGxsLQHx8PJmZmcXWb9euHVWrVqVmzZpUr16de+65B4BGjRoVOLZnz54A\ntG7dmuzsbL7//nv69+/PzJkzAZg2bRr9+vU7q/3FixczaNAgwsLCAKhRo0ZeWffu3c+Kc9WqVTz8\n8MMAdOzYkb179/LDDz8UaHPZsmUMHDgQgAoVKlCtWjUWL17MF198QUJCArGxsSxfvpyvvvoKgOzs\nbK688sq84ytUqICZcezYsWJfGxERERERkZISUjPE55rJDZRKlSrlbYeFhXHs2DEqVKiQd/nw8ePH\ni6x/2WWX5T2/7LLLOHnyZF6ZmRU4zsyoU6cOtWrVYsmSJaxbt+5nX4Z8+lxhYWEFznU+zozHOUf/\n/v354x//eFbdyy47+7OYn376qUDfRUREREREAkkzxEFSt25d0tLSAJgzZ84FtTFr1izAP4NbvXp1\nqlevDsCAAQPo3bs3PXr0yJsFzi8xMZEpU6bkreqc/5Lpwvh8vrzEevHixVx//fVUrly5QJ127dox\nZcoUAHJzc8nOzuaOO+7gvffe49ChQwBkZWXxzTffAHDzzTcXmO3ev38/119/faGJsoiIiIiISCAo\n+wiSp59+msmTJxMXF5eXMP5c4eHhxMXFMWjQIN544428/V26dOHo0aMFLpeeO3cuY8aMAWDgwIH8\n4he/yFsA67333iv2PGPGjGHNmjVER0czcuRIpk+ffladV155hU8++YRGjRqRkJBARkYGjRo1YtSo\nUdxxxx1ER0fTsWNH9u/fD0Dnzp1ZtmxZ3vFLly6lc+fOF/Q6iIiIiIiIXAhzzgU7hhKTkJDgzlzs\nKT09nfr16wcposBp27YtSUlJJCQknFWWmprK8OHDWblyZRAiOz+7d+9mwIAB/PWvfwWga9euTJgw\ngV/+8pfn3UZ5fW9FRERERORsZpbmnDs7AboIIXUPcSgYN24ckydPLvNfYRQZGUnfvn05evQoFStW\n5P777/9ZybCIiIiIiMjF0gyxXLL03oqIiIiIhI5AzBDrHmIREREREREJSUqIRUREREREJCQpIRYR\nEREREZGQpIRYREREREREQpISYhEREREREQlJSojLkNzc3GCHICIiIiIiEjKUEAfY+PHjmThxIgDD\nhw+nffv2ACxZsoRevXpRpUoVnnrqKWJiYlizZg0ff/wxt912G/Hx8QwbNoy77747mOGLiIiIiIiU\nWxWCHUBp+sef/sSJ9IwSbbNS/dv4xX/+Z5HlPp+Pl156iWHDhpGamsqJEyfIyclh5cqVtG7dmrff\nfptmzZrx0ksvcfz4cW655RZWrFhBVFQUPXv2LNFYRURERERE5F80Qxxg8fHxpKWlkZ2dTaVKlWjR\nogWpqamsXLkSn89HWFgY9913HwAZGRncdNNNREVFASghFhERERERCaCQmiEubiY3UCpWrEhUVBTJ\nycm0bNmS6Oholi5dys6dO6lfvz7h4eGEhYWVelwiIiIiIiKhTjPEpcDn85GUlETr1q3x+XxMmTKF\nuLg4zKxAvXr16rFr1y4yMzMBmDVrVhCiFRERERERCQ1KiEuBz+dj3759tGjRglq1ahEeHo7P5zur\n3hVXXMGkSZPo1KkT8fHxVK1alerVqwchYhERERERkfIvpC6ZDpYOHTqQk5OT93z79u1520ePHi1Q\nt127dmRkZOCc47HHHiMhIaHU4hQREREREQklmiEuY6ZOnUpsbCwNGjTgn//8JwMHDgx2SCIiIiIi\nIuWSZojLmOHDhzN8+PBghyEiIiIiIlLuaYZYREREREREQpISYhEREREREQlJSohFREREREQkJCkh\nFhERERERkZCkhLiMGz16NElJScXWmTdvHtu2bct7npyczN69ewMdmoiIiIiIyCVNCXEpcs5x6tSp\nEm9XCbGIiIiIiMjPp4Q4wDIzM6lXrx59+vShYcOGvPnmmzRq1IiGDRvyzDPP5NWrUqVK3vacOXPo\n27fvWW199dVXdOrUifj4eHw+HxkZGaxevZoFCxbw7//+78TGxvLCCy+QmppKr169iI2N5dixY6Sl\npdGmTRvi4+O588472bdvX2l0XUREREREpEwLqe8hXvnedg59e7RE24yoUwXfr28tts6OHTuYMWMG\nN9xwA82bNyctLY2rr76ajh07Mm/ePLp163Ze53r00UeZMmUKt9xyC2vXrmXIkCEsWbKELl26cPfd\nd3P//fcDsHDhQpKSkkhISCAnJ4fHH3+c+fPnU7NmTWbNmsWIESOYNm3aRfddRERERETkUhZSCXGw\n3HjjjTRv3pz58+fTtm1batasCUCvXr1YsWLFeSXER48eZfXq1fTo0SNv34kTJ8553JdffsmWLVtI\nTEwEIDc3l9q1a19gT0RERERERMqPkEqIzzWTGyiVK1c+Zx0zy9s+fvz4WeWnTp3iqquuYuPGjT/r\n3M45GjRowJo1a37WcSIiIiIiIuWd7iEuRU2bNmX58uUcOnSI3Nxc3nnnHdq0aQNArVq1SE9P59Sp\n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\n",
"text/plain": [
"<matplotlib.figure.Figure at 0x11eac0ba8>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"ax = results[[\"cythonized\", \"cythonized_copy\", \"numpy.choice()\", \"wrg\", \"roulette\", \"walker\"]].plot(figsize=(16,8))\n",
"ax.set_ylabel(\"Time (s)\")\n",
"ax.set_xlabel(\"Sample size (k << n)\")\n",
"ax.set_title(\"Population size: n = {}\".format(int(counts.sum())))\n",
"ax.set_xlim(0.99 * 10**7, 10**7)\n",
"ax.set_ylim(26, 28.5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"After all, it seems like only our Cythonized version of Jason Orendorff's implementation is the fastest, even faster than numpy. The Cython version with array copy, the roulette method, Walker's Alias method, and weighted random generator all perform better than numpy, but only after sampling at least $10^7$ elements. Nevertheless, the performance difference is negligible."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Applying for example roulette for more than 1 billion elements is still making the process hangs with `numpy.random.choice()`, therefore it would be better to only sample below $10^8$ pixels and use the most performant and simples method instead: `roulette_sampling()`."
]
},
{
"cell_type": "code",
"execution_count": 49,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"16.8 s ± 315 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"3min 4s ± 5.33 s per loop (mean ± std. dev. of 3 runs, 1 loop each)\n"
]
}
],
"source": [
"%timeit -r 3 -n 1 roulette_sampling(colors, counts, 10**8)\n",
"%timeit -r 3 -n 1 roulette_sampling(colors, counts, 10**9)"
]
},
{
"cell_type": "code",
"execution_count": 50,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"37.7 s ± 480 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n",
"7min 13s ± 600 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)\n"
]
}
],
"source": [
"%timeit -r 3 -n 1 np.random.choice(colors, p=weights, size=10**8)\n",
"%timeit -r 3 -n 1 np.random.choice(colors, p=weights, size=10**9)"
]
}
],
"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.5.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment