30

I have an array of arbitrary length, and I want to select N elements of it, evenly spaced out (approximately, as N may be even, array length may be prime, etc) that includes the very first arr[0] element and the very last arr[len-1] element.

Example:

>>> arr = np.arange(17)
>>> arr
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16])

Then I want to make a function like the following to grab numElems evenly spaced out within the array, which must include the first and last element:

GetSpacedElements(numElems = 4)
>>> returns 0, 5, 11, 16

Does this make sense?

I've tried arr[0:len:numElems] (i.e. using the array start:stop:skip notation) and some slight variations, but I'm not getting what I'm looking for here:

>>> arr[0:len:numElems]
array([ 0,  4,  8, 12, 16])

or

>>> arr[0:len:numElems+1]
array([ 0,  5, 10, 15])

I don't care exactly what the middle elements are, as long as they're spaced evenly apart, off by an index of 1 let's say. But getting the right number of elements, including the index zero and last index, are critical.

halfer
  • 19,824
  • 17
  • 99
  • 186
JDS
  • 16,388
  • 47
  • 161
  • 224
  • 1
    Unless someone has any better ideas, I can only think of `np.round(np.linspace(0, 16, 4))`. – cs95 Jun 04 '18 at 16:59
  • FYI - the array elements are arbitrary, just describing the positions of the elements I want to access. I'll try out the linspace code. – JDS Jun 04 '18 at 17:04
  • @JDS oh, in that case Divakar's extension should give you what you're looking for. – cs95 Jun 04 '18 at 17:05

4 Answers4

38

To get a list of evenly spaced indices, use np.linspace:

idx = np.round(np.linspace(0, len(arr) - 1, numElems)).astype(int)

Next, index back into arr to get the corresponding values:

arr[idx]

Always use rounding before casting to integers. Internally, linspace calls astype when the dtype argument is provided. Therefore, this method is NOT equivalent to:

# this simply truncates the non-integer part
idx = np.linspace(0, len(array) - 1, numElems).astype(int)
idx = np.linspace(0, len(arr) - 1, numElems, dtype='int')
ddelange
  • 1,037
  • 10
  • 24
cs95
  • 379,657
  • 97
  • 704
  • 746
  • Hey, looking to try this but I'm getting: `TypeError: 'int' object is not callable`. Is there a quick change I can do? On Python 3.5 I believe. – JDS Jun 04 '18 at 17:29
  • nvm, just gotta be careful on calling `len` for a numpy array, I just used `arr.shape[0]` and it's fine. I can see `idx` provides the indicies just how I want them. However `arr[idx]` doesn't work, so I'm looking for a small modification: `IndexError: arrays used as indices must be of integer (or boolean) type` – JDS Jun 04 '18 at 17:37
  • 1
    Here's my fix: `idx = np.round(np.linspace(0, len - 1, numElems, dtype='int'))`, where `len = arr.shape[0]`. Now `arr[idx]` now works as expected :) – JDS Jun 04 '18 at 17:41
  • 2
    There are no guarantee that there won't be any duplicate in the idx list if numElements is smaller or equal to len(arr). In my opinion that is a flaw – Tobbey Oct 19 '18 at 07:51
  • If `numElems` is larger than the length of the array `len(array)` this code will produce duplicate selections. – David Bernat Mar 24 '20 at 22:19
3

Your GetSpacedElements() function should also take in the array to avoid unfortunate side effects elsewhere in code. That said, the function would need to look like this:

import numpy as np

def GetSpacedElements(array, numElems = 4):
    out = array[np.round(np.linspace(0, len(array)-1, numElems)).astype(int)]
    return out

arr = np.arange(17)
print(array)
spacedArray = GetSpacedElements(arr, 4)
print (spacedArray)
Justin
  • 191
  • 3
2

If you want to know more about finding indices that match values you seek, also have a look at numpy.argmin and numpy.where. Implementing the former:

import numpy as np

test = np.arange(17)

def nearest_index(array, value):
    return (np.abs(np.asarray(array) - value)).argmin()

def evenly_spaced_indices(array, steps):
    return [nearest_index(array, value) for value in np.linspace(np.min(array), np.max(array), steps)]

print(evenly_spaced_indices(test,4))

You should keep in mind that this is an unnecessary amount of function calls for the initial question you asked as switftly demonstrated by coldspeed. np.round intuitively rounds to the closest matching integer serving as index, implementing a similar process but optimised in C++. If you are interested in the indices too, you could have your function simply return both:

import numpy as np

def GetSpacedElements(array, numElems=4, returnIndices=False):
    indices = np.round(np.linspace(0, len(arr) - 1, numElems)).astype(int)
    values = array[indices]
    return (values, indices) if returnIndices else (values)

arr = np.arange(17) + 42
print(arr)
print(GetSpacedElements(arr, 4))                            # values only
print(GetSpacedElements(arr, 4, returnIndices=True)[0])     # values only
print(GetSpacedElements(arr, 4, returnIndices=True)[1])     # indices only
ddelange
  • 1,037
  • 10
  • 24
1

To get N evenly spaced elements from list 'x':

x[::int(np.ceil( len(x) / N ))]