How to separate time ranges/intervals into bins if intervals occur over multiple bins
<p>I have a dataset which consists of pairs of start-end times (say seconds) of something happening across a recorded period of time. For example:</p>

<pre><code>#each tuple includes (start, stop) of the event happening
data = [(0, 1), (5,8), (14,21), (29,30)]
</code></pre>

<p>I want to quantify what percentage of the time this thing is happening within bins of any size that I desire. For example if I wanted bins of 5 seconds each I would like a function that would:</p>

<ul>
<li>split up any tuples that cross over into multiple bins</li>
<li>add up the total time event occurs and divide by bin size to get time event occurred during each bin</li>
</ul>

<p>I'm mostly having trouble with the first point.</p>

<p>Ideally it would look something like this where bin_times is the function that I need help writing, and the output is what the function would return:</p>

<pre><code>data = [(0, 1), (5,8), (15,21), (29,30)]
bin_times(data, bin_size=5, total_length=40)

&gt;&gt; [20, 60, 0, 100, 20, 20, 0, 0]
</code></pre>

<p>If you don't mind using <code>numpy</code>, here is a strategy:</p>

<pre><code>import numpy as np

def bin_times(data, bin_size, total_length):
    times = np.zeros(total_length, dtype=np.bool)
    for start, stop in data:
        times[start:stop] = True
    binned = 100 * np.average(times.reshape(-1, bin_size), axis=1)
    return binned.tolist()

data = [(0, 1), (5,8), (15,21), (29,30)]
bin_times(data, 5, 40)
// =&gt; [20.0, 60.0, 0.0, 100.0, 20.0, 20.0, 0.0, 0.0]
</code></pre>

<p>To explain the logic of <code>bin_times()</code>, let me use a smaller example:</p>

<pre><code>data = [(0, 1), (3, 8)]
bin_times(data, 3, 9)
// =&gt; [33.3, 100.0, 66.6]
</code></pre>

<ol>
<li><p>The <code>times</code> array encodes whether your event is happening in each unit time interval. You start by setting every entry to <code>False</code>:</p>

<pre><code>[False, False, False, False, False, False, False, False, False]
</code></pre></li>
<li><p>Read the incoming <code>data</code> and turn the appropriate entries to <code>True</code>:</p>

<pre><code>[True, False, False, True, True, True, True, True, False]
</code></pre></li>
<li><p>Reshape it into a two-dimensional matrix in which the length of the rows is <code>bin_size</code>:</p>

<pre><code>[[True, False, False],
 [True,  True,  True],
 [True,  True, False]]
</code></pre></li>
<li><p>Take the average in each row:</p>

<pre><code>[0.333, 1.000, 0.666]
</code></pre></li>
<li><p>Multiply by 100 to turn those numbers into percentages:</p>

<pre><code>[33.3, 100.0, 66.6]
</code></pre></li>
<li><p>To hide the use of <code>numpy</code> from the consumer of the function, use the <code>.tolist()</code> method to turn the resulting <code>numpy</code> array into a plain Python list.</p></li>
</ol>

<p>One caveat: <code>bin_size</code> needs to evenly divide <code>total_length</code> &mdash; the reshaping will throw a <code>ValueError</code> otherwise.</p>

python