title: Numpy: Get indices of boundaries in an array where starts of boundaries always start with a particular number; non-boundaries by a particular number
body: <p><strong>Problem</strong>:</p>
  <p>The most computationally efficient solution to getting the indices of boundaries in an array where starts of boundaries always start with a particular number and non-boundaries are indicated by a different particular number.</p>
  <p><strong>Differences between this question and other boundary-based numpy questions on SO:</strong></p>
  <p>here are some other boundary based numpy questions</p>
  <p><a href="https://stackoverflow.com/questions/61760669/numpy-1d-array-find-indices-of-boundaries-of-subsequences-of-the-same-number">Numpy 1D array - find indices of boundaries of subsequences of the same number</a></p>
  <p><a href="https://stackoverflow.com/questions/55697045/getting-the-boundary-of-numpy-array-shape-with-a-hole">Getting the boundary of numpy array shape with a hole</a></p>
  <p><a href="https://stackoverflow.com/questions/51696326/extracting-boundary-of-a-numpy-array">Extracting boundary of a numpy array</a></p>
  <p>The difference between the question I am asking and other stackoverflow posts in my attempt to search for a solution is that the other boundaries are indicated by a jump in value, or a 'hole' of values.</p>
  <p>What seems to be unique to my case is the starts of boundaries always start with a particular number.</p>
  <p><strong>Motivation:</strong></p>
  <p>This problem is inspired by IOB tagging in natural language processing. In IOB tagging, the start of a word is tagged with B [beginning] is the tag of the first letter in an entity, I [inside] is the tag for all other characters besides the first character in a word, and [O] is used to tag all non-entity characters</p>
  <p><strong>Example:</strong></p>
  <pre><code>import numpy as np
  
  a = np.array(
      [
       0, 0, 0, 1, 2, 2, 2, 0, 0, 0, 1, 0, 0, 1, 2, 1, 1, 0, 0, 1, 1, 1
      ]
  )
  </code></pre>
  <p>1 is the start of each boundary. If a boundary has a length greater than one, then 2 makes up the rest of the boundary. 0 are non-boundary numbers.</p>
  <p>The entities of these boundaries are <code>1, 2, 2, 2</code>, <code>1</code>, <code>1,2</code>, <code>1</code>, <code>1</code>, <code>1</code>, <code>1</code>, <code>1</code></p>
  <p>So the desired solution; the indices of the indices boundary values for <code>a</code> are</p>
  <pre><code>desired = [[3, 6], [10, 10], [13, 14], [15, 15], [16,16], [19,19], [20,20], [21,21]]
  </code></pre>
  <p><strong>Current Solution:</strong></p>
  <p>If flattened, the numbers in the desired solution are in ascending order. So the raw indices numbers can be calculated, sorted, and reshaped later.</p>
  <p>I can get the start indices using</p>
  <pre><code>starts = np.where(a==1)[0]
  starts
  </code></pre>
  <pre><code>array([ 3, 10, 13, 15, 16, 19, 20, 21])
  </code></pre>
  <p>So what's left is <code>6, 10, 14, 15,16,19,20,21</code></p>
  <p>I can get all except 1 using 3 different conditionals where I can compare a shifted array to the original by decreases in values and the values of the non-shifted array.</p>
  <pre><code>first = np.where(a[:-1] - 2 == a[1:])[0]
  first 
  </code></pre>
  <pre><code>array([6])
  </code></pre>
  <pre><code>second = np.where((a[:-1] - 1 == a[1:]) &amp; 
      ((a[1:]==1) | (a[1:]==0)))[0]
  second
  </code></pre>
  <pre><code>array([10, 14, 16])
  </code></pre>
  <pre><code>third = np.where(
      (a[:-1] == a[1:]) &amp;
      (a[1:]==1)
      )[0]
  third
  </code></pre>
  <pre><code>array([15, 19, 20])
  </code></pre>
  <p>The last number I need is <code>21</code>, but since I needed to shorten the length of the array by 1 to do the shifted comparisons, I'm not sure how to get that particular value using logic, so I just used a simple if statement for that.</p>
  <p>Using the rest of the retrieved values for the indices, I can concatenate all the values and reshape them.</p>
  <pre><code>if (a[-1] == 1) | (a[-1] == 2):
      pen = np.concatenate((
          starts, first, second, third, np.array([a.shape[0]-1])
      ))
  else:
      pen = np.concatenate((
          starts, first, second, third, 
      ))
  np.sort(pen).reshape(-1,2)
  </code></pre>
  <pre><code>array([[ 3,  6],
         [10, 10],
         [13, 14],
         [15, 15],
         [16, 16],
         [19, 19],
         [20, 20],
         [21, 21]])
  </code></pre>
  <p>Is this the most computationally efficient solution for my answer? I realize the four where statements can be combined with <code>or</code> operators but wanted to have each separate for the reader to see each result in this post. But I am wondering if there is a more computationally efficient solution since I have not mastered all of numpy's functions and am unsure of the computational efficiency of each.</p>