<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.3">Jekyll</generator><link href="https://scottlittleorg-website.web.app/feed.xml" rel="self" type="application/atom+xml" /><link href="https://scottlittleorg-website.web.app/" rel="alternate" type="text/html" /><updated>2025-01-27T04:32:39+00:00</updated><id>https://scottlittleorg-website.web.app/feed.xml</id><title type="html">Scott Little</title><subtitle>Data Scientist</subtitle><entry><title type="html">Cardinality estimation in parallel</title><link href="https://scottlittleorg-website.web.app/Cardinality-estimation-in-Parallel/" rel="alternate" type="text/html" title="Cardinality estimation in parallel" /><published>2020-03-27T00:00:00+00:00</published><updated>2020-03-27T00:00:00+00:00</updated><id>https://scottlittleorg-website.web.app/Cardinality%20estimation%20in%20Parallel</id><content type="html" xml:base="https://scottlittleorg-website.web.app/Cardinality-estimation-in-Parallel/">&lt;h3 id=&quot;introduction-&quot;&gt;Introduction 🧮&lt;/h3&gt;
&lt;p&gt;First, what is cardinality? It’s counting the unique elements of a field.  In SQL, something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SELECT DISTINCT field FROM table&lt;/code&gt;.  You can definitely count unique elements this way but the trouble begins when you want to do quick analyses on big data.  If you want to explore relationships between two variables, it may involve multiple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GROUP BY&lt;/code&gt;s and other operations for every pair of variables that you want to explore.  This is especially tedious and expensive if you were to explore every combination of fields.  I’m no expert in big O notation estimates, but this sounds like &lt;em&gt;O(n²)&lt;/em&gt; to me.  And &lt;em&gt;O(n²)&lt;/em&gt; is considering pairs, it could grow to &lt;em&gt;O(nᵐ)&lt;/em&gt; if you wanted to compare m columns.  With probalistic data structures like HyperLogLog and MinHash, we can compute this for every column, so then the cost is only around &lt;em&gt;O(n)&lt;/em&gt;.  See the references listed at the bottom for in-depth discussions on probalistic data structures, the class of data structures that HyperLogLog and MinHash belong to.&lt;/p&gt;

&lt;h3 id=&quot;what-im-doing-️&quot;&gt;What I’m doing ⚙️&lt;/h3&gt;
&lt;p&gt;I’m modifying a Python implementation of HyperLogLog to work with Dask.  So far, the modifications have included serialization and adding the ability to get cardinality for intersections (HyperLogLog proper calculates cardinality for unions only).  I wanted to document my adventure here.&lt;/p&gt;

&lt;h3 id=&quot;exploring-the-data-&quot;&gt;Exploring the data 📊&lt;/h3&gt;
&lt;p&gt;Usually, we would like to count the number of distinct users who did x and also did y.  I looked for a fairly large dataset (a few GB) that was open and interesting and found the Chicago Divvy Bike Share dataset.  Instead of distinct users, this dataset’s primary key is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trip_id&lt;/code&gt;.  The data is 9495235 rows long and has 9495188 unique &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trip_id&lt;/code&gt;s (there are a small number of dupes present).  I broke this into 128 MiB chunks, which makes 16 partitions.  Partitions are what makes dask parallel. And in this case, if you had 16 cores, you could get a 16X speedup.  Plus, dask is out-of-core, meaning that you can process datasets larger than your ram.  Dask recommends just using pandas if your dataset fits in memory.&lt;/p&gt;

&lt;div class=&quot;table-wrapper&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt; &lt;/th&gt;
        &lt;th&gt;trip_id&lt;/th&gt;
        &lt;th&gt;year&lt;/th&gt;
        &lt;th&gt;month&lt;/th&gt;
        &lt;th&gt;week&lt;/th&gt;
        &lt;th&gt;day&lt;/th&gt;
        &lt;th&gt;hour&lt;/th&gt;
        &lt;th&gt;usertype&lt;/th&gt;
        &lt;th&gt;gender&lt;/th&gt;
        &lt;th&gt;starttime&lt;/th&gt;
        &lt;th&gt;stoptime&lt;/th&gt;
        &lt;th&gt;tripduration&lt;/th&gt;
        &lt;th&gt;temperature&lt;/th&gt;
        &lt;th&gt;events&lt;/th&gt;
        &lt;th&gt;from_station_id&lt;/th&gt;
        &lt;th&gt;from_station_name&lt;/th&gt;
        &lt;th&gt;latitude_start&lt;/th&gt;
        &lt;th&gt;longitude_start&lt;/th&gt;
        &lt;th&gt;dpcapacity_start&lt;/th&gt;
        &lt;th&gt;to_station_id&lt;/th&gt;
        &lt;th&gt;to_station_name&lt;/th&gt;
        &lt;th&gt;latitude_end&lt;/th&gt;
        &lt;th&gt;longitude_end&lt;/th&gt;
        &lt;th&gt;dpcapacity_end&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;2355134&lt;/td&gt;
        &lt;td&gt;2014&lt;/td&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;27&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;23&lt;/td&gt;
        &lt;td&gt;Subscriber&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;2014-06-30 23:57:00&lt;/td&gt;
        &lt;td&gt;2014-07-01 00:07:00&lt;/td&gt;
        &lt;td&gt;10.0667&lt;/td&gt;
        &lt;td&gt;68&lt;/td&gt;
        &lt;td&gt;tstorms&lt;/td&gt;
        &lt;td&gt;131&lt;/td&gt;
        &lt;td&gt;Lincoln Ave &amp;amp; Belmont Ave&lt;/td&gt;
        &lt;td&gt;41.9394&lt;/td&gt;
        &lt;td&gt;-87.6684&lt;/td&gt;
        &lt;td&gt;15&lt;/td&gt;
        &lt;td&gt;303&lt;/td&gt;
        &lt;td&gt;Broadway &amp;amp; Cornelia Ave&lt;/td&gt;
        &lt;td&gt;41.9455&lt;/td&gt;
        &lt;td&gt;-87.646&lt;/td&gt;
        &lt;td&gt;15&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;2355133&lt;/td&gt;
        &lt;td&gt;2014&lt;/td&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;27&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;23&lt;/td&gt;
        &lt;td&gt;Subscriber&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;2014-06-30 23:56:00&lt;/td&gt;
        &lt;td&gt;2014-07-01 00:00:00&lt;/td&gt;
        &lt;td&gt;4.38333&lt;/td&gt;
        &lt;td&gt;68&lt;/td&gt;
        &lt;td&gt;tstorms&lt;/td&gt;
        &lt;td&gt;282&lt;/td&gt;
        &lt;td&gt;Halsted St &amp;amp; Maxwell St&lt;/td&gt;
        &lt;td&gt;41.8646&lt;/td&gt;
        &lt;td&gt;-87.6469&lt;/td&gt;
        &lt;td&gt;15&lt;/td&gt;
        &lt;td&gt;22&lt;/td&gt;
        &lt;td&gt;May St &amp;amp; Taylor St&lt;/td&gt;
        &lt;td&gt;41.8695&lt;/td&gt;
        &lt;td&gt;-87.6555&lt;/td&gt;
        &lt;td&gt;15&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;2355130&lt;/td&gt;
        &lt;td&gt;2014&lt;/td&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;27&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;23&lt;/td&gt;
        &lt;td&gt;Subscriber&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;2014-06-30 23:33:00&lt;/td&gt;
        &lt;td&gt;2014-06-30 23:35:00&lt;/td&gt;
        &lt;td&gt;2.1&lt;/td&gt;
        &lt;td&gt;68&lt;/td&gt;
        &lt;td&gt;tstorms&lt;/td&gt;
        &lt;td&gt;327&lt;/td&gt;
        &lt;td&gt;Sheffield Ave &amp;amp; Webster Ave&lt;/td&gt;
        &lt;td&gt;41.9217&lt;/td&gt;
        &lt;td&gt;-87.6537&lt;/td&gt;
        &lt;td&gt;19&lt;/td&gt;
        &lt;td&gt;225&lt;/td&gt;
        &lt;td&gt;Halsted St &amp;amp; Dickens Ave&lt;/td&gt;
        &lt;td&gt;41.9199&lt;/td&gt;
        &lt;td&gt;-87.6488&lt;/td&gt;
        &lt;td&gt;15&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;2355129&lt;/td&gt;
        &lt;td&gt;2014&lt;/td&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;27&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;23&lt;/td&gt;
        &lt;td&gt;Subscriber&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;2014-06-30 23:26:00&lt;/td&gt;
        &lt;td&gt;2014-07-01 00:24:00&lt;/td&gt;
        &lt;td&gt;58.0167&lt;/td&gt;
        &lt;td&gt;68&lt;/td&gt;
        &lt;td&gt;tstorms&lt;/td&gt;
        &lt;td&gt;134&lt;/td&gt;
        &lt;td&gt;Peoria St &amp;amp; Jackson Blvd&lt;/td&gt;
        &lt;td&gt;41.8777&lt;/td&gt;
        &lt;td&gt;-87.6496&lt;/td&gt;
        &lt;td&gt;19&lt;/td&gt;
        &lt;td&gt;194&lt;/td&gt;
        &lt;td&gt;State St &amp;amp; Wacker Dr&lt;/td&gt;
        &lt;td&gt;41.8872&lt;/td&gt;
        &lt;td&gt;-87.6278&lt;/td&gt;
        &lt;td&gt;11&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;4&lt;/td&gt;
        &lt;td&gt;2355128&lt;/td&gt;
        &lt;td&gt;2014&lt;/td&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;27&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;23&lt;/td&gt;
        &lt;td&gt;Subscriber&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;2014-06-30 23:16:00&lt;/td&gt;
        &lt;td&gt;2014-06-30 23:26:00&lt;/td&gt;
        &lt;td&gt;10.6333&lt;/td&gt;
        &lt;td&gt;68&lt;/td&gt;
        &lt;td&gt;tstorms&lt;/td&gt;
        &lt;td&gt;320&lt;/td&gt;
        &lt;td&gt;Loomis St &amp;amp; Lexington St&lt;/td&gt;
        &lt;td&gt;41.8722&lt;/td&gt;
        &lt;td&gt;-87.6615&lt;/td&gt;
        &lt;td&gt;15&lt;/td&gt;
        &lt;td&gt;134&lt;/td&gt;
        &lt;td&gt;Peoria St &amp;amp; Jackson Blvd&lt;/td&gt;
        &lt;td&gt;41.8777&lt;/td&gt;
        &lt;td&gt;-87.6496&lt;/td&gt;
        &lt;td&gt;19&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;Something that I wanted to explore here is something that I knew would overlap, so I picked &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gender&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;month&lt;/code&gt; as my pair of variables that I wanted to explore.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/venn_gender_month.svg?sanitize=true&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is the groupby count for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trip_id&lt;/code&gt; for every combination of the values in month and gender:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;month&lt;/th&gt;
      &lt;th&gt;gender&lt;/th&gt;
      &lt;th&gt;count&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;0&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;Female&lt;/td&gt;
      &lt;td&gt;50003&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;Male&lt;/td&gt;
      &lt;td&gt;221712&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;Female&lt;/td&gt;
      &lt;td&gt;60649&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;Male&lt;/td&gt;
      &lt;td&gt;245326&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;Female&lt;/td&gt;
      &lt;td&gt;92346&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;5&lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;Male&lt;/td&gt;
      &lt;td&gt;345583&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;6&lt;/td&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt;Female&lt;/td&gt;
      &lt;td&gt;144702&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;7&lt;/td&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt;Male&lt;/td&gt;
      &lt;td&gt;476986&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;8&lt;/td&gt;
      &lt;td&gt;5&lt;/td&gt;
      &lt;td&gt;Female&lt;/td&gt;
      &lt;td&gt;216973&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;9&lt;/td&gt;
      &lt;td&gt;5&lt;/td&gt;
      &lt;td&gt;Male&lt;/td&gt;
      &lt;td&gt;652611&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10&lt;/td&gt;
      &lt;td&gt;6&lt;/td&gt;
      &lt;td&gt;Female&lt;/td&gt;
      &lt;td&gt;319679&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;11&lt;/td&gt;
      &lt;td&gt;6&lt;/td&gt;
      &lt;td&gt;Male&lt;/td&gt;
      &lt;td&gt;858546&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;12&lt;/td&gt;
      &lt;td&gt;7&lt;/td&gt;
      &lt;td&gt;Female&lt;/td&gt;
      &lt;td&gt;355774&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;13&lt;/td&gt;
      &lt;td&gt;7&lt;/td&gt;
      &lt;td&gt;Male&lt;/td&gt;
      &lt;td&gt;923731&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;14&lt;/td&gt;
      &lt;td&gt;8&lt;/td&gt;
      &lt;td&gt;Female&lt;/td&gt;
      &lt;td&gt;356374&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;15&lt;/td&gt;
      &lt;td&gt;8&lt;/td&gt;
      &lt;td&gt;Male&lt;/td&gt;
      &lt;td&gt;948329&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;16&lt;/td&gt;
      &lt;td&gt;9&lt;/td&gt;
      &lt;td&gt;Female&lt;/td&gt;
      &lt;td&gt;314032&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;17&lt;/td&gt;
      &lt;td&gt;9&lt;/td&gt;
      &lt;td&gt;Male&lt;/td&gt;
      &lt;td&gt;875939&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;18&lt;/td&gt;
      &lt;td&gt;10&lt;/td&gt;
      &lt;td&gt;Female&lt;/td&gt;
      &lt;td&gt;244807&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;19&lt;/td&gt;
      &lt;td&gt;10&lt;/td&gt;
      &lt;td&gt;Male&lt;/td&gt;
      &lt;td&gt;752905&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;20&lt;/td&gt;
      &lt;td&gt;11&lt;/td&gt;
      &lt;td&gt;Female&lt;/td&gt;
      &lt;td&gt;145102&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;21&lt;/td&gt;
      &lt;td&gt;11&lt;/td&gt;
      &lt;td&gt;Male&lt;/td&gt;
      &lt;td&gt;498454&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;22&lt;/td&gt;
      &lt;td&gt;12&lt;/td&gt;
      &lt;td&gt;Female&lt;/td&gt;
      &lt;td&gt;78234&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;23&lt;/td&gt;
      &lt;td&gt;12&lt;/td&gt;
      &lt;td&gt;Male&lt;/td&gt;
      &lt;td&gt;316438&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;We can use this to verify is our probabilistic data structures are doing the right thing.&lt;/p&gt;

&lt;h3 id=&quot;great-but-theres-a-problem&quot;&gt;Great! But there’s a problem…👹&lt;/h3&gt;
&lt;p&gt;When I first started using and modifying the HyperLogLog Python package, I thought this was all that I would need to get cardinality estimates for combinations of variables.  At this point I didn’t really care how the package worked, as long as it did.  To skip to the chase, what I found out is that HyperLogLog only estimates unions, not intersections.  My first instinct was to play with the code enough to tease the information out of the functions that I already had.  It turns out that one can perform multiple cardinality estimates to calculate arbitrary combinations of unions and intersections, i.e. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(A ∪ B) ∩ C&lt;/code&gt;, using something called the inclusion-exclusion principle.  This is just a fancy way of saying that we can calculate intersections with only unions.  For example, if we wanted to get the intersection of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A ∩ B&lt;/code&gt;, we’d “include” or add the individual cardinalities for A and B, then “exclude” or subtract the cardinality of the union &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A ∪ B&lt;/code&gt;.  Needless to say this could get a little complicated to make a function for, and I did make one, but I found out that this method is wildly inaccurate when the original cardinalities of the variables being compared are very different.  That is, the error of the inclusion-exclusion principle might be more than the result itself! Bummer. 🙁&lt;/p&gt;

&lt;h3 id=&quot;not-to-fear-minhash-is-here-&quot;&gt;Not to fear, MinHash is here! 😁&lt;/h3&gt;
&lt;p&gt;So, the way to solve this was to tack on another probabilistic data structure to solve for intersections.  I can get into the details of HyperLogLog later (or not, and just refer you to some papers or blogs), but we have to hash every unique element of the field that we are considering the cardinality for (in the above example, it’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trip_id&lt;/code&gt;).  FYI- without saying too much about the details- HyperLogLog is a fancy way of counting zeros to estimate cardinality.  HyperLogLog uses “registers” to store the result of adding a new element.  The main engine is &lt;strong&gt;elements that were added previously, will not be added again&lt;/strong&gt; because they will be hashed in exactly the same way and added to the same registers.  There are more elements than number of registers, so there will be collisions (mistakes), that’s why HyperLogLog is an estimate and not an exact amount.  Unions of elements are as easy as taking the max of there registers.  We can serialize the result as a small string, saving space on the millions or billions of elements that we would otherwise have to keep track of.  At the record level, it doesn’t make any sense to have hll data structures, but the magic comes in when you combine them and for, say, all of the unique values for each of your variables.&lt;/p&gt;

&lt;p&gt;So where does MinHash come into play? It turns out that our hll registers aren’t good enough to easily create cardinality estimates for intersections, as mentioned above.  We need to add a the MinHash, or k number of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MinHash&lt;/code&gt;es to each element.  I did this by creating a seperate number of registers, calculating an element’s MinHash using a single hash function, then adding that value to the registers if it was low enough.  This is all just an elaborate way of saying that I “sampled” the variable of interest (in this case &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trip_id&lt;/code&gt;).  This sampling would look like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/venn_gender_month_dots.svg?sanitize=true&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The result of this sampling is that we can find the proportion of the intersection to the union, which is called the Jaccard Index.  Mathematically,
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;J = ( A ∩ B ) / ( A ∪ B )&lt;/code&gt;, where J is the Jaccard Index.  So, we can use this to get our intersection by
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A ∩ B = J ( A ∪ B )&lt;/code&gt;.  So we can get the intersection by calculating the Jaccard Index using MinHash and multiplying it by the union, calculted by the HyperLogLog, our old friend.&lt;/p&gt;

&lt;h3 id=&quot;on-to-how-i-did-it-or-wait-&quot;&gt;On to how I did it, or, wait? ⏳&lt;/h3&gt;
&lt;p&gt;So, in short I modified an existing HyperLogLog Python package to accomplish this.  But first let me explain a bit of motivation (maybe I should have done this first?).  I noticed that in data science and machine learning, it’s really helpful to calculate the correlation of multiple variables. In pandas you can do this with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;df.corr()&lt;/code&gt; and produce a beautiful grid matrix of all the different relationships of the variables.  But what if your data is mostly categorical, or what if you have big data, or both.  It’s not as obvious how to see if two variables are related to each other.  One intuitive way we might do this is by something to think of our data like DNA matching, but instead of DNA, we’re matching users.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/CBP_chemist_reads_a_DNA_profile.jpg?raw=true&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But this breaks down when you consider that users don’t necessarily have a particular order that they appear in for the variables to be correlated.  So we are limited to how many users are shared across two variables.  But variables aren’t the ultimate unit.  Each variable has several values.  We can call this more fundamental unit a Key-Value Pair, or KVP.  The overlap of KVPs of one variable with the overlap of the KVPs of another variable are really what we want to get at to determine if the variables are related in any way.  If two KVPs have exactly the same number of users, then we can be pretty sure that there must be some sort of relation.  For example, we might find that users of a magazine variable correspond to the same users of a book variable.  Then upon further inpection, the value of the magazine variable is for science fiction and the value of the book variable is astronomy.  So, it makes sense why these two KVPs would have the same users.&lt;/p&gt;

&lt;h3 id=&quot;dask-stuff&quot;&gt;Dask stuff&lt;/h3&gt;
&lt;p&gt;Let’s summarize and expand each step here. I’m just going to put the heart of the code here. &lt;a href=&quot;https://github.com/scottlittle/hyperloglog/blob/master/examples/hll%20intersection%20with%20dask.ipynb&quot;&gt;See this for the full Jupyter Notebook.&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;map-reduce-functions&quot;&gt;Map reduce functions&lt;/h4&gt;
&lt;ol&gt;
  &lt;li&gt;Make function to apply HLL to each element in a series.&lt;/li&gt;
  &lt;li&gt;Map function to each element in parallel.&lt;/li&gt;
  &lt;li&gt;Reduce to combine HLLs over desired range (unioning these elements).&lt;/li&gt;
  &lt;li&gt;(Optional) Get cardinality from combined function.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Map and reduce code used to generate the dask task graph:&lt;/p&gt;
&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;############ map steps #############
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hll_count_series&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;series&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;hll&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hyperloglog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HyperLogLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# accept 1% counting error
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;series&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hll&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ddf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;trip_id&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;map_partitions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hll_count_series&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;this_result&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;f8&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_delayed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;########## reduce steps ############
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hll_reduce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# make tree reduction tasks
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;L&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;new_L&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;lazy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delayed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hll_reduce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# add neighbors  
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;new_L&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lazy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;L&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_L&lt;/span&gt;                       &lt;span class=&quot;c1&quot;&gt;# swap old list for new
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Here’s what this looks like as a dask task graph:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/tree-reduce-graph.svg?sanitize=true&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;group-by-functionality&quot;&gt;Group by functionality&lt;/h4&gt;
&lt;p&gt;We want to further this process to use with multiple combinations of variables and values, but this time we terminate the functionality with serialization of the HLL object so that we can instantiate it on the fly, that is, when we want to do analysis or quick computation, like in a dashboard.&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Make function that encapsulates previous “simple function” and then applies it to every variable and value of interest.&lt;/li&gt;
  &lt;li&gt;Terminate with serialization of HLL object.&lt;/li&gt;
  &lt;li&gt;Re-instantiate and run analysis like intersection.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s the code-in-brief used for these steps:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hll_serialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;serialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_serialized_hll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;series&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&apos;&apos;&apos;returns serialized hll from dask series as dask delayed object&apos;&apos;&apos;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;series&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;map_partitions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hll_count_series&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;this_result&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;f8&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;L&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delayed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hll_serialize&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;
    
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;groupby_approx_counts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ddf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;countby&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;trip_id&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&apos;&apos;&apos;
    input: takes in dask dataframe and item to group by, as well as item to count by
    output: tuple containing: list of unique values in the item to group by, a list of dask delayed objects with the serialized hll object
    &apos;&apos;&apos;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;uniques&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ddf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;compute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tolist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uniques&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_serialized_hll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ddf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ddf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;countby&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
    &lt;span class=&quot;n&quot;&gt;serial_objs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;compute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uniques&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serial_objs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;parallelized-estimate-results&quot;&gt;Parallelized estimate results&lt;/h3&gt;
&lt;p&gt;The moment you’ve been waiting for…&lt;/p&gt;

&lt;div class=&quot;table-wrapper&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt; &lt;/th&gt;
        &lt;th&gt;month&lt;/th&gt;
        &lt;th&gt;gender&lt;/th&gt;
        &lt;th&gt;card estimate&lt;/th&gt;
        &lt;th&gt;card actual&lt;/th&gt;
        &lt;th&gt;percent diff&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;51188&lt;/td&gt;
        &lt;td&gt;50003&lt;/td&gt;
        &lt;td&gt;-2.37%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;222455&lt;/td&gt;
        &lt;td&gt;221712&lt;/td&gt;
        &lt;td&gt;-0.34%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;59210&lt;/td&gt;
        &lt;td&gt;60649&lt;/td&gt;
        &lt;td&gt;2.37%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;246677&lt;/td&gt;
        &lt;td&gt;245326&lt;/td&gt;
        &lt;td&gt;-0.55%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;4&lt;/td&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;93464&lt;/td&gt;
        &lt;td&gt;92346&lt;/td&gt;
        &lt;td&gt;-1.21%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;340408&lt;/td&gt;
        &lt;td&gt;345583&lt;/td&gt;
        &lt;td&gt;1.5%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;4&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;141403&lt;/td&gt;
        &lt;td&gt;144702&lt;/td&gt;
        &lt;td&gt;2.28%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;7&lt;/td&gt;
        &lt;td&gt;4&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;476394&lt;/td&gt;
        &lt;td&gt;476986&lt;/td&gt;
        &lt;td&gt;0.12%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;212724&lt;/td&gt;
        &lt;td&gt;216973&lt;/td&gt;
        &lt;td&gt;1.96%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;9&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;668272&lt;/td&gt;
        &lt;td&gt;652611&lt;/td&gt;
        &lt;td&gt;-2.4%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;10&lt;/td&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;321649&lt;/td&gt;
        &lt;td&gt;319679&lt;/td&gt;
        &lt;td&gt;-0.62%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;871597&lt;/td&gt;
        &lt;td&gt;858546&lt;/td&gt;
        &lt;td&gt;-1.52%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;7&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;360552&lt;/td&gt;
        &lt;td&gt;355774&lt;/td&gt;
        &lt;td&gt;-1.34%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;13&lt;/td&gt;
        &lt;td&gt;7&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;930521&lt;/td&gt;
        &lt;td&gt;923731&lt;/td&gt;
        &lt;td&gt;-0.74%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;14&lt;/td&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;356013&lt;/td&gt;
        &lt;td&gt;356374&lt;/td&gt;
        &lt;td&gt;0.1%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;15&lt;/td&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;952413&lt;/td&gt;
        &lt;td&gt;948329&lt;/td&gt;
        &lt;td&gt;-0.43%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;16&lt;/td&gt;
        &lt;td&gt;9&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;313715&lt;/td&gt;
        &lt;td&gt;314032&lt;/td&gt;
        &lt;td&gt;0.1%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;17&lt;/td&gt;
        &lt;td&gt;9&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;879395&lt;/td&gt;
        &lt;td&gt;875939&lt;/td&gt;
        &lt;td&gt;-0.39%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;18&lt;/td&gt;
        &lt;td&gt;10&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;246669&lt;/td&gt;
        &lt;td&gt;244807&lt;/td&gt;
        &lt;td&gt;-0.76%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;19&lt;/td&gt;
        &lt;td&gt;10&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;759524&lt;/td&gt;
        &lt;td&gt;752905&lt;/td&gt;
        &lt;td&gt;-0.88%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;20&lt;/td&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;141407&lt;/td&gt;
        &lt;td&gt;145102&lt;/td&gt;
        &lt;td&gt;2.55%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;21&lt;/td&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;509001&lt;/td&gt;
        &lt;td&gt;498454&lt;/td&gt;
        &lt;td&gt;-2.12%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;22&lt;/td&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;76642&lt;/td&gt;
        &lt;td&gt;78234&lt;/td&gt;
        &lt;td&gt;2.03%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;23&lt;/td&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;321145&lt;/td&gt;
        &lt;td&gt;316438&lt;/td&gt;
        &lt;td&gt;-1.49%&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;Not too bad, not too bad. Could be worse. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;¯\_(ツ)_/¯&lt;/code&gt; &lt;br /&gt;
At least it’s better than the intersections by the inclusion-exclusion rule:&lt;/p&gt;

&lt;div class=&quot;table-wrapper&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt; &lt;/th&gt;
        &lt;th&gt;month&lt;/th&gt;
        &lt;th&gt;gender&lt;/th&gt;
        &lt;th&gt;card_int&lt;/th&gt;
        &lt;th&gt;card actual&lt;/th&gt;
        &lt;th&gt;perc diff&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;44596&lt;/td&gt;
        &lt;td&gt;50003&lt;/td&gt;
        &lt;td&gt;10.81%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;216517&lt;/td&gt;
        &lt;td&gt;221712&lt;/td&gt;
        &lt;td&gt;2.34%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;77474&lt;/td&gt;
        &lt;td&gt;60649&lt;/td&gt;
        &lt;td&gt;-27.74%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;255826&lt;/td&gt;
        &lt;td&gt;245326&lt;/td&gt;
        &lt;td&gt;-4.28%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;4&lt;/td&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;107591&lt;/td&gt;
        &lt;td&gt;92346&lt;/td&gt;
        &lt;td&gt;-16.51%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;359199&lt;/td&gt;
        &lt;td&gt;345583&lt;/td&gt;
        &lt;td&gt;-3.94%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;4&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;159666&lt;/td&gt;
        &lt;td&gt;144702&lt;/td&gt;
        &lt;td&gt;-10.34%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;7&lt;/td&gt;
        &lt;td&gt;4&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;476835&lt;/td&gt;
        &lt;td&gt;476986&lt;/td&gt;
        &lt;td&gt;0.03%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;233786&lt;/td&gt;
        &lt;td&gt;216973&lt;/td&gt;
        &lt;td&gt;-7.75%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;9&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;664212&lt;/td&gt;
        &lt;td&gt;652611&lt;/td&gt;
        &lt;td&gt;-1.78%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;10&lt;/td&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;320897&lt;/td&gt;
        &lt;td&gt;319679&lt;/td&gt;
        &lt;td&gt;-0.38%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;866065&lt;/td&gt;
        &lt;td&gt;858546&lt;/td&gt;
        &lt;td&gt;-0.88%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;7&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;358388&lt;/td&gt;
        &lt;td&gt;355774&lt;/td&gt;
        &lt;td&gt;-0.73%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;13&lt;/td&gt;
        &lt;td&gt;7&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;954115&lt;/td&gt;
        &lt;td&gt;923731&lt;/td&gt;
        &lt;td&gt;-3.29%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;14&lt;/td&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;371748&lt;/td&gt;
        &lt;td&gt;356374&lt;/td&gt;
        &lt;td&gt;-4.31%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;15&lt;/td&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;942378&lt;/td&gt;
        &lt;td&gt;948329&lt;/td&gt;
        &lt;td&gt;0.63%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;16&lt;/td&gt;
        &lt;td&gt;9&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;322049&lt;/td&gt;
        &lt;td&gt;314032&lt;/td&gt;
        &lt;td&gt;-2.55%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;17&lt;/td&gt;
        &lt;td&gt;9&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;862643&lt;/td&gt;
        &lt;td&gt;875939&lt;/td&gt;
        &lt;td&gt;1.52%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;18&lt;/td&gt;
        &lt;td&gt;10&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;246845&lt;/td&gt;
        &lt;td&gt;244807&lt;/td&gt;
        &lt;td&gt;-0.83%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;19&lt;/td&gt;
        &lt;td&gt;10&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;762295&lt;/td&gt;
        &lt;td&gt;752905&lt;/td&gt;
        &lt;td&gt;-1.25%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;20&lt;/td&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;155851&lt;/td&gt;
        &lt;td&gt;145102&lt;/td&gt;
        &lt;td&gt;-7.41%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;21&lt;/td&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;513240&lt;/td&gt;
        &lt;td&gt;498454&lt;/td&gt;
        &lt;td&gt;-2.97%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;22&lt;/td&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;Female&lt;/td&gt;
        &lt;td&gt;86019&lt;/td&gt;
        &lt;td&gt;78234&lt;/td&gt;
        &lt;td&gt;-9.95%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;23&lt;/td&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;Male&lt;/td&gt;
        &lt;td&gt;314132&lt;/td&gt;
        &lt;td&gt;316438&lt;/td&gt;
        &lt;td&gt;0.73%&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;h3 id=&quot;happy-app-y&quot;&gt;Happy App-y&lt;/h3&gt;
&lt;p&gt;To demostrate the power of this data structure, I made a ipywidgets app that can explore the cardinalities of different intersections.  The setup should be intuitive here, but behind the scenes, the app is calculating the unions between values of the same fields and the intersections of all of these results.  What you get is a nearly realtime exploratory tool of the data.  To do this with exact counts, you would need to do groupby sums on different cuts of the data, which would take a lot longer.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://github.com/scottlittle/scottlittle.github.io/blob/master/images/Screen%20Shot%202020-04-09%20at%204.06.11%20PM.png?raw=true&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;summary-statistics&quot;&gt;Summary statistics&lt;/h3&gt;
&lt;p&gt;Another really powerful advantage of this method of computing cardinality is quickly getting summary statistics about our data.  This was actually my first motativation of implementing everything here.  I wanted to get the “correlation” between two categorical variables on a large dataset, but this took 4 days to compute the the groupby counts for every pair of fields in the dataset.  This method, in opposition, would take minutes.  &lt;a href=&quot;https://towardsdatascience.com/the-search-for-categorical-correlation-a1cf7f1888c9&quot;&gt;Here’s a case&lt;/a&gt; where this statistic was calculated for a very small dataset and was able to find highly correlated coefficients.&lt;/p&gt;

&lt;p&gt;Theil’s U (uncertainty coefficient):&lt;/p&gt;

\[U(X \mid Y) = \frac{H(X) - H(X \mid Y)}{H(X)}\]

&lt;p&gt;with&lt;/p&gt;

\[H(X) = -\sum_{X} P_X \log{ P_{X} }\]

\[H(X \mid Y) = \sum_{X,Y} P_{XY} \log{ \frac{P_Y}{P_{X}} }\]

&lt;p&gt;or with N:&lt;/p&gt;

\[H(X \mid Y) = \frac{1}{N} \sum_{X,Y} N_{XY} \log{ \frac{N_Y}{N_{XY}} }\]

&lt;p&gt;Using this on our data above, we get &lt;br /&gt;
&lt;strong&gt;U ( gender | month ) = 0.003743&lt;/strong&gt; for gender given a month, and &lt;br /&gt;
&lt;strong&gt;U ( month | gender ) = 0.000888&lt;/strong&gt; for month given a gender. &lt;br /&gt;
So, neither of these are highly correlated. But we do get more information about gender from a given month, than month given a gender (it’s 4.2 times more).&lt;/p&gt;

&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;In conclusion, I made a thing. Check out the github project, post a comment here, or an issue on the github page.&lt;/p&gt;

&lt;h3 id=&quot;some-light-reading-&quot;&gt;Some light reading 📚&lt;/h3&gt;
&lt;h4 id=&quot;my-stuff&quot;&gt;My stuff&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/scottlittle/drac&quot;&gt;Github: My take on HyperLogLog with intersections&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;other-peoples-stuff&quot;&gt;Other people’s stuff&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/scottlittle/hyperloglog&quot;&gt;Github: Original HyperLogLog Python package without intersections&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://tech.nextroll.com/blog/data/2013/07/10/hll-minhash.html&quot;&gt;Really great blog post on HyperLogLog and MinHash by NextRoll (AdRoll)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://tech.nextroll.com/media/hllminhash.pdf&quot;&gt;AdRoll’s complimentary paper to their blog post on combining HyperLogLog and MinHash&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://arxiv.org/pdf/1710.08436.pdf&quot;&gt;Recent paper on HyperMinHash and some state of the art techniques&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://papers.nips.cc/paper/4778-one-permutation-hashing.pdf&quot;&gt;Paper on one permutation hashing for Minhash&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name></name></author><summary type="html">Introduction 🧮 First, what is cardinality? It’s counting the unique elements of a field. In SQL, something like SELECT DISTINCT field FROM table. You can definitely count unique elements this way but the trouble begins when you want to do quick analyses on big data. If you want to explore relationships between two variables, it may involve multiple GROUP BYs and other operations for every pair of variables that you want to explore. This is especially tedious and expensive if you were to explore every combination of fields. I’m no expert in big O notation estimates, but this sounds like O(n²) to me. And O(n²) is considering pairs, it could grow to O(nᵐ) if you wanted to compare m columns. With probalistic data structures like HyperLogLog and MinHash, we can compute this for every column, so then the cost is only around O(n). See the references listed at the bottom for in-depth discussions on probalistic data structures, the class of data structures that HyperLogLog and MinHash belong to.</summary></entry><entry><title type="html">3D RDF Graph Demo</title><link href="https://scottlittleorg-website.web.app/3D-RDF-Graph-Demo/" rel="alternate" type="text/html" title="3D RDF Graph Demo" /><published>2019-06-17T00:00:00+00:00</published><updated>2019-06-17T00:00:00+00:00</updated><id>https://scottlittleorg-website.web.app/3D%20RDF%20Graph%20Demo</id><content type="html" xml:base="https://scottlittleorg-website.web.app/3D-RDF-Graph-Demo/">&lt;p&gt;While learning &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPARQL&lt;/code&gt; (&lt;a href=&quot;http://scottlittle.org/Learning-SPARQL/&quot;&gt;see previous post&lt;/a&gt;) I explored the tutorials of the &lt;a href=&quot;http://brickschema.org&quot;&gt;Brick Schema&lt;/a&gt; to get a handle on making queries on an RDF database (using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RDFlib&lt;/code&gt; python package).  One small complaint about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RDFlib&lt;/code&gt; is that there is not an obvious way of outputting query results as a nice table.  I learned, however, that the output can be outputted to a JSON string and from there easily turned into a dictionary or pandas dataframe.  Additionally, you can programmatically replace the prefixes:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;s&lt;/th&gt;
      &lt;th&gt;p&lt;/th&gt;
      &lt;th&gt;o&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;rice: Exhaust_Air_Tempdac4UIP22&lt;/td&gt;
      &lt;td&gt;rdf: type&lt;/td&gt;
      &lt;td&gt;brick: Exhaust_Air_Temperature_Sensor&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;rice: Room101&lt;/td&gt;
      &lt;td&gt;bf: hasPoint&lt;/td&gt;
      &lt;td&gt;rice: TEMP2_Space_Temperature_RMI101&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;rice: FP_VAV_Hot_Water_Return_Temp&lt;/td&gt;
      &lt;td&gt;rdf: type&lt;/td&gt;
      &lt;td&gt;brick: Hot_Water_Return_Temperature&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;rice: RMI536_Space_Temperature_Perimeter&lt;/td&gt;
      &lt;td&gt;rdf: type&lt;/td&gt;
      &lt;td&gt;brick: Room_Temperature_Sensor&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;brick: Freeze_Protect_Low_Limit&lt;/td&gt;
      &lt;td&gt;rdf: type&lt;/td&gt;
      &lt;td&gt;brick: Point&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;This sort of output is easier to read than row by row output that the tutorials use.  From this output, I can construct a graph (using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;igraph&lt;/code&gt;) and view the results.  I built a custom 3D graph framework using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pythreejs&lt;/code&gt; so that I could explore the graph in 3D:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/brick-movie-480-2.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://scottlittle.org/brick-app-export&quot;&gt;You can explore this for yourself here.&lt;/a&gt;  This version is static in the sense that it is pre-rendered to decrease loading time.  A dynamic version that you could query, using SPARQL for instance, or select nodes with the mouse is possible if the application required it.  Also, this version only works on desktop browsers, but a dynamic version would work with either desktop or mobile browsers.&lt;/p&gt;</content><author><name></name></author><summary type="html">While learning SPARQL (see previous post) I explored the tutorials of the Brick Schema to get a handle on making queries on an RDF database (using the RDFlib python package). One small complaint about RDFlib is that there is not an obvious way of outputting query results as a nice table. I learned, however, that the output can be outputted to a JSON string and from there easily turned into a dictionary or pandas dataframe. Additionally, you can programmatically replace the prefixes:</summary></entry><entry><title type="html">Learning SPARQL Query Language</title><link href="https://scottlittleorg-website.web.app/Learning-SPARQL/" rel="alternate" type="text/html" title="Learning SPARQL Query Language" /><published>2019-06-10T00:00:00+00:00</published><updated>2019-06-10T00:00:00+00:00</updated><id>https://scottlittleorg-website.web.app/Learning%20SPARQL</id><content type="html" xml:base="https://scottlittleorg-website.web.app/Learning-SPARQL/">&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: I’m a SPARQL and semantic web beginner.&lt;/p&gt;

&lt;p&gt;SPARQL graph query language takes some getting used to for querying compared to normal SQL.  The promise is that you should be able to make more powerful queries quicker, diving into insights not as obvious with “normal” querying methods.  One drawback that I see is that it takes quite a bit of effort to get set up.  The data is very structured going into the graph database. This is the price to pay up front for the big promises of speed and scalability.  I believe that the package neo4j offers the ability to query using regular SQL, but if you do that, you might miss out on the insights and “ease” of a similar query in the SPARQL query language.&lt;/p&gt;

&lt;p&gt;What good is all of this?  There is the WikiData efforts, coming out of the FreeBase project (in which Google bought).  WikiData allows one to make quick and powerful queries on the data of WikiData.  From a natural language processing (NLP) point of view, this is a lot different than how a data scientist might ingest the data of Wikipedia- that is, word2vec, doc2vec, tf-idf on the articles and words.  Instead of operating on English words (or any language for that matter), the graph database makes use of semantic concepts that can be queried and turned into English or another language as an afterthought (almost).&lt;/p&gt;

&lt;p&gt;If I update this, I’ll show some basic examples here.&lt;/p&gt;</content><author><name></name></author><summary type="html">Disclaimer: I’m a SPARQL and semantic web beginner.</summary></entry><entry><title type="html">Apps added to site</title><link href="https://scottlittleorg-website.web.app/Apps-added-to-site/" rel="alternate" type="text/html" title="Apps added to site" /><published>2019-06-07T00:00:00+00:00</published><updated>2019-06-07T00:00:00+00:00</updated><id>https://scottlittleorg-website.web.app/Apps%20added%20to%20site</id><content type="html" xml:base="https://scottlittleorg-website.web.app/Apps-added-to-site/">&lt;p&gt;I added an apps link to this website.  &lt;a href=&quot;http://scottlittle.org/apps/&quot;&gt;Take a look!&lt;/a&gt;&lt;/p&gt;</content><author><name></name></author><summary type="html">I added an apps link to this website. Take a look!</summary></entry><entry><title type="html">First post!</title><link href="https://scottlittleorg-website.web.app/First-post!/" rel="alternate" type="text/html" title="First post!" /><published>2019-05-30T00:00:00+00:00</published><updated>2019-05-30T00:00:00+00:00</updated><id>https://scottlittleorg-website.web.app/First%20post!</id><content type="html" xml:base="https://scottlittleorg-website.web.app/First-post!/">&lt;p&gt;First post!  I hope to be adding some things that I find interesting here.&lt;/p&gt;</content><author><name></name></author><summary type="html">First post! I hope to be adding some things that I find interesting here.</summary></entry></feed>