Algorithms Every Programmer Should Know: Reservoir Sampling
omniref.com
omniref.com
> "I have a database table with #{17.kajillion} records, and I want to take a random sample of #{X} of them. How can I do this efficently?"
And goes on to say that something like `YourBigTable.all.sample(X)` is bad in time and space. And the link that explains why doing `ORDER BY RANDOM()` is bad lists one reason being that generation of random numbers is relatively expensive.
But these problems aren't solved by reservoir sampling. Reservoir sampling is still O(N), and it requires generating a random number for every element. It still beats `ORDER BY RANDOM()` because it doesn't have to sort the numbers (or even keep them all in memory at the same time). But that doesn't make it a good choice.
More generally, reservoir sampling is appropriate when you don't know the size of the set beforehand. But in the premise, we do know the size of the set. And if you know the size of the set (and have some way to address arbitrary elements), then randomly generating a set of X indices is going to be much simpler and faster. Yeah, you have to deal with duplicate indices, but that's an easy problem to solve.
Yes, reservoir sampling is O(N), but my point there was that it doesn't require instantiating every record at once. Also, the cost of the random number generation isn't the big deal in an ORDER BY RANDOM() implementation -- it's that even the smart implementations end up doing multiple table scans. So even at O(n) this probably beats most implementations of that approach, in practice.
Reservoir sampling is appropriate with more than just a set of unknown size -- you very frequently know the size of a set, but it's still too big to sample directly. But yes, if your sets are small, you have a lot of options.
isn't the movement of such data -- when you have billions of rows simply impossible in reasonable time?
(find_each instantiates a few records at a time, then throws them away)
There's also another, less-known variant of this algorithm that I'll go into in a future post that alleviates the concern.
I'm not sure that I buy this...Do you have an example? Still, it is a neat algorithm. And it is a true stream query algorithm, that is, if you have an open-ended stream, it maintains a random sample at all times.
You can make a better approach using a much longer list, but then the math is far less elegant. EX: Add the first X * N elements to the list. Then remove 1/X of them. Then 1/X of the times add an element to the list until it’s full. Cull 1/X of them and then add new elements (1/X)^2 of the time. Repeat until you run out of elements at which point you cull the list back to X elements.
Downside is your generating ~N log N random numbers so scanning the list and then sorting a random set may be faster.
If you have an approximate number elements AND want a small percentage of them then randomly generate X * N element list which is then culled to an N element list.
Finally, if N happens to be close to the full number of elements you’re better off creating an exclusion list.
PS: Reservoir Sampling is only good if your memory constrained, need a constant time algorithm, and generating random numbers is expensive.
Offhand I'm not sure how to do that in SQL, since data isn't usually fetched by index (unless the row has an explicit index key). Worst-case you could iterate over the rows just as you do with reservoir sampling, and it would still be faster (since you aren't generating tons of random numbers and doing a lot of array manipulation). Or you could do one query fetching just the primary key, record the keys of the indices you're interested in, and then do a second query to fetch that set. Or maybe SQL does have some way to fetch by index after all.
If you have an integral primary key, you could select its max and generate random numbers up to that. Continuity isn't guaranteed so you would need to check for nonexistent primary keys in addition to checking for duplicates.
If there's no integral primary key, then there's no standard SQL solution faster than O(n).
let count = query("SELECT COUNT() FROM table")
let indices = computeIndices(count, k)
for index in indices {
results.append(query("SELECT * FROM table LIMIT 1 OFFSET ?", index))
}
Yeah, you're doing a bunch of queries, but you're removing all of copying all the data from all the rows.Also, do you have any citation for OFFSET j being O(j)? I can believe that's the case, but it also seems fairly obvious to optimize OFFSET for the case of a non-filtered unordered SELECT (or one ordered in a way that matches an index).
Can anyone comment on why this reservoir method would be a better choice than the old fashioned systematic sample? (which, BTW, only requires the generation of one random number to determine the starting record).
Let's keep this going.
Resist commenting about being downvoted. It never does any good, and it makes boring reading.
Please don't bait other users by inviting them to downvote you.
- is it a common problem to have, not knowing the set size?
- is it O(N), or rather O(n)? My intuition tells me it depends on whether or not I have to retrieve every record in the set or not, because I/O should still be much more expensive than random number generation.
One motivating case is when the items arrive over time, like you have a website and you want to sample uniformly from your visitors over the course of the next hour. You don't need to know how many people will arrive.
Yes. It could be as simple as receiving values from a Python generator (or equivalent mechanism in your favourite language), where you don't know the number of items beforehand (and querying for the length is either impossible or expensive).
In such a cases, using reservoir sampling is an elegant solution, because you're probably in a situation where you'll be iterating through all values anyway.
1) collect data for some length of time, and
2) thereafter have available for querying the (maybe approximate) 25th, 50th, 95th, and 99th percentile TCP connection bandwidth thresholds, and
3) signal when a connection grows beyond the 95th percentile.
You only have a fairly limited amount of memory.
I had the same thought reading this. This solution is only really suitable if the number of elements to select is within some constant of the total number of elements.
> More generally, reservoir sampling is appropriate when you don't know the size of the set beforehand. But in the premise, we do know the size of the set. And if you know the size of the set (and have some way to address arbitrary elements), then randomly generating a set of X indices is going to be much simpler and faster. Yeah, you have to deal with duplicate indices, but that's an easy problem to solve.
Last time I had to solve this, I came up with the following solution[1] which doesn't generate any duplicates and runs in O(k) where k is the size of result set.
There are lots of cases where this assumption doesn't hold: if you're sampling from a join, or from a table with non-contiguous ID values, for example.
It seems less, not more, likely that this kind of situation would exist in a huge table - what use would such a table be if you could only process it sequentially?
A sufficiently smart database might understand "order by rand() limit n" and do reservoir sampling on row id's behind the scenes. I wouldn't count on it, though.
If you set it up in advance, you could also use reservoir sampling to avoid even storing all the data in the first place, while still saving a random sample.
In fact, i think this is the underlying meme of 'big data' - that it's now plausible and useful to do analysis over all of a large dataset, so you might as well build everything around iterating over the entire table regardless.
Unfortunately I don't understand how each element has the same probability of being chosen. I.e. the first element seems to have a probability of 1/n, while the second has 1/n + 1/(n-1) etc. Am i wrong about this?
/* This is standard modern C++ random number generation.
It means, using the random generator "engine", return
a number according to distribution "dist". In this
"dist" is a uniform distribution from "i" to "max-min".
"auto" is the new way to type variables in C++: it
infers the type of the variable from the type of the
expression */
22: auto index = dist(engine);
/* The following lines are a bit more complex, as they
contain a nested expression. Let us focus on the inner
expression:
(index < size ) ? array[index]
: map.insert({index, min + index}).first->second
This is in the form:
test-expr ? when-true-expr
: when-false-expr
This expression is a ternary condition. What it means
is that the expression before the question mark is
computed (index < size), and if it evaluates to true,
the overall expression will resolve to the result of
the expression between the question mark and the colon,
otherwise it resolves to what is after the colon.
Here, it means that if "index" is smaller than "size",
use "array[index]", otherwise insert a new entry in the
map, with key "index" and value "min+index", then reuse
the result of the operation by accessing member "second"
of the member "first". I am not familiar with STL maps;
here is an explanation of the map::insert method from
cplusplus.com:
The single element versions (1) return a pair, with
its member pair::first set to an iterator pointing
to either the newly inserted element or to the
element with an equivalent key in the map. The
pair::second element in the pair is set to true if
a new element was inserted or false if an equivalent
key already existed.
*/
23: std::swap(array[i], index < size
24: ? array[index]
25: : map.insert({index, min + index}).first->second);
/* Then, the std::swap operation simply swaps the two indicated
values in the array. */I've annotated the program here: http://ideone.com/gmaBri
> Unfortunately I don't understand how each element has the same probability of being chosen. I.e. the first element seems to have a probability of 1/n, while the second has 1/n + 1/(n-1) etc. Am i wrong about this?
This is just a partial Fisher-Yates shuffle[1] using a sparse sequence instead of an array. Keep in mind that if an element is not chosen it is swapped with the chosen element and will be considered again. I think the best way to think about it is that you are choosing a random element from the set and then "removing" it from the set with the swap and then repeating.
[1] http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#Th...
Say again? My laptop can read from /dev/urandom at over 1 Gbps. If you're running in userland on modern hardware you can easily hit 10 Gbps.
Seeding is hard, but once you've done that, generating random numbers is insanely cheap.
Depends on your use case. Generating random numbers is indeed relatively expensive:
$ time dd if=/dev/zero of=/dev/null bs=1024 count=1M
1073741824 bytes (1.1 GB) copied, 0.348118 s, 3.1 GB/s
real 0m0.350s
user 0m0.092s
sys 0m0.260s
$ time dd if=/dev/urandom of=/dev/null bs=1024 count=1M
1073741824 bytes (1.1 GB) copied, 102.332 s, 10.5 MB/s
real 1m42.334s
user 0m0.152s
sys 1m41.994s
(I7, 3.2.0-4-amd64)(fyi, Dr. Colin Percival is an eminent security developer for FreeBSD, author and CEO of the trusted Tarsnap, and developer of the AWS AMI's for FreeBSD; when he takes a whack at Linux's PRNG, he's got some standing to do it from :).)
But, yep, this is normal for Linux.. although, frankly, I'm just glad that we, like FreeBSD, are not taking rdrand at face value (thx Ted T'so!) Perhaps post-seed performance will improve down the road.
The inner loop of a PRNG microbenchmark should be identical to the inner loop of an AES-CTR microbenchmark (or your favourite block cipher or MAC if you prefer a different one) and those are fast these days.
I agree with your comment in general. Only: you can do better than generate a random number for each element. (Generate a random number to tell you how many elements in the steam to skip.)
That should work if you pull the skip count from the appropriate distribution (negative binomial, I think).
Reservoir sampling: I'm not sure that applying this algorithm to database sampling is the right thing to do. By its nature, the algorithm has to touch every single row in a database, and it does that because it's designed for data streams where you don't know in advance the size of the stream -- which isn't the case with database tables.
Assuming a uniform distribution in your database, you could instead do something like:
SELECT *
FROM (
SELECT
@row := @row +1 AS rownum, [column, column, columns...]
FROM (
SELECT @row :=0) r, [table name]
) ranked
WHERE rownum % [n] = 1
to get every nth record of your table, calculating n ahead of time for n ~= (table size)/(sample size). That should be a little bit faster and in most cases still provide acceptable results.It happened a few times that we were asked for a random sample of records. It was quite common to want to recalculate the rehabilitation rates and other stats.
This was a problem, as patient records filled several tapes and, being the youngster, I had to sit loading tapes for hours.
Because our data set was bigger than memory, we had trouble deciding what to sample. We'd read everything in, filtering the records, and recording just their offsets. Then we'd pick some number, then re-read all the tapes to fetch the actual records. Naive and tedious.
Luckily the IT manager knew all about reservoir sampling, and explained it to us.
We made a little Turbo Pascal program that ran over the data files each night before they were archived to tape and kept a large (but small enough to fit on one machine) reservoir!
Every time we were asked after that for a random sample after that we just handed out the reservoir.
The care managers, who thought they were still causing us to sit for hours shuffling tapes, were terribly grateful at the quick turnarounds. We never did let them in on our secret.
These days of course hospital records fit in RAM. Not the same kind of problems.
Something should really only be filed under "Every Programmer Should Know" if it will be encountered in the real world more than once or twice. So in this case, the title feels like click-bait.
That is almost never true. In fact, I have seen clever performance optimizations in database applications that exploit I/O scheduler and layout induced bias in index-less record order. In practice, sampling 25% of very large database tables will often not be representative of sampling 100% of that database table even if the value is not explicitly indexed. Database engines automatically do a lot of low-level optimizations that introduce bias in a nominally random dump of records as a stream.
It is actually quite difficult to randomly sample a large database table.
QA raised a ticket complaining the subset we were using (just taking the first n items from the collection that matched the filter, collection size was enough that we didn't want to double enumerate or store the whole thing in memory) was unbalanced, eg we were giving the impression that areas of the map that had plenty of items were actually empty.
Switched to reservoir sampling to make sure the subset was a fair representation of the collection, with only one pass and not blowing our memory budget. And everyone you explain it to gets to admire the algorithm.
I once had to extend it for a distributed setting (think mapreduce) where you can cheaply iterate all records but not sequentially. Instead you have multiple reservoirs, and you can't resample those uniformly since each saw a different number of records.
The unbiased way to aggregate 2 reservoirs is to draw a random number from a hypergeometric distribution. You aggregate a larger number by combining 2 at a time.
Fun story, an interviewer at Google once asked me to derive reservoir sampling a long time ago, which I completely wiffed.
I'd probably wiff it in an interview too. For some reason my brain doesn't work as well in an interview setting. Thankfully I haven't had to worry about that too many times.
Looping n times, calling rand(n) and putting the random element in the output. This mostly works, and is actually far better, in space and time, especially in the case where "putting the element in the output" is expensive (as is implied by the "kajillions of records" premise).
The problem with this solution is that you might pick the same random number twice or more in a row. This means you have to keep track of your random numbers if you absolutely can't tolerate a slightly smaller sample.
The reservoir avoids reusing random numbers in a clever way, by only applying the random number as the target for replacement, and iterating over every N. So the same elt in the sample might be replaced twice, but no elt in N will be added to the sample twice.
One interesting thing is that when N is slightly larger than n, those extra elements are very likely to replace something in the base sample. In fact, the probability is n/N.
Reservoir sampling is normally used on large streams where you do not want to or cannot keep the data you are sampling from in memory.
Consider the simplest non-trivial example: a sample of 1 element, from a two-element reservoir. At iteration one, we accept the first element. At iteration two, if the sample is to be random, we have to decide to keep the new element with a probability of 1/2:
First element: (1/1) (1/2) = 1/2*
Why are we multiplying the acceptance of the first element with the probability of keeping it? And then...
But what if we're sampling from a pool of three elements instead? We know that we therefore have to keep the new element with a probability of 1/3…which means that we need to keep the element we already have with probability 2/3:
First element: (1/2) (2/3) = 1/3*
Where did the 1/2 come from? Why is it not 1/1 here?
I realized that I needed to add the ability to sample an exact number of records, so I went ahead and implemented reservoir sampling. It's a neat algorithm and the performance is pretty stellar. Now I just need to expand it to allow stratified sampling on a column! :)
http://stackoverflow.com/questions/25942333/how-do-reservoir...
I use this algorithm in one of my tweet bots that tweets out a random tweet from a txt file containing 10k different quotes, works fantastically and has been for several months with no duplicates or malfunctions.
http://algs4.cs.princeton.edu/lectures/21ElementarySorts.pdf
In that he assigns a random number to each element. Sorts the elements based on the random number. Then takes the first N elements from the beginning.
http://41j.com/blog/2015/02/select-random-line-file-single-p...
Which is a question I've had come up in interviews. It's a neat trick.
https://github.com/taltman/scripts/blob/master/EDA/samp
It's shorter than this ruby implementation, and includes copious documentation, file handling, and corner-case support.
https://github.com/weblicht/conll-utils/blob/master/src/main...
(Also returns items that were replaced.)
"Algorithms Every Data Scientist Should Know: Reservoir Sampling" http://blog.cloudera.com/blog/2013/04/hadoop-stratified-rand...
Another interesting problem is how to pick 3 million rows out of 4 million at random, without duplicates. With O(N) time and picks streaming from the algorithm.
2. compute p = n/N
3. select * from table where random()<p*(some lambda >1) order by random() limit n
4. Repeat 3 until you got n rows
EDIT: I misread it as rand(n), rand(idx) is correct.
j = rand(idx)
out[j] = i if j < n
..it is keeping the sampling probability at n/idx where n is the sample/reservoir size and idx is the number of items seen so far. Then if this item is selected for sampling, each of the n items in the reservoir is equally likely to be replaced. I think the implementation is correct, assuming that idx is the number of items seen so far.Though, I think there is an off-by one in this implementation (assuming that Ruby indexing starts at 0):
j = rand(idx)
out[j] = i if j < n
Say that the index is n, it will call rand(n), which gives a random number [0..n). However, the index should be picked from [0..n].That sounded right to me. I could be convinced otherwise.
Suppose that the sample size is 1 and you are getting the second item (index 1). You will call rand(1), which has 0 as the only possible outcome. So, you will always replace the first item (index 0). Whereas if you would call rand(2) (possible outcomes: 0 and 1), you replace the item in the sample with probability .5 (assuming that the random number generator is uniform).
I'll fix the code and publish a new gem a bit later today. Thanks!