This is inspired by this post.
Consider a function f that returns a 1d np.ndarray idx of indices which the user will typically use to index other arrays. Assume further that a frequent outcome is for f to return the full range of legal indices. In the linked post it is suggested this be special-cased by f returning slice(None) instead of np.arange(maxind).
Since advanced indexing comes at a cost
>>> a = np.arange(1_000_000)
>>> direct = lambda: np.sum(a[:])
>>> indirect = lambda: np.sum(a[a])
>>> timeit(direct, number=100)
0.07656216900795698
>>> timeit(indirect, number=100)
0.2885982050211169
this looks like a reasonable optimisation at first sight.
Unfortunately, it is not "correct". Imagine, for example, the user wants to create a one-hot representation of idx. One straightforward way of going about this is
result = np.zeros((k, maxind), dtype=int)
result[np.arange(k), idx] = 1
This breaks if np.arange(maxind) is substituted by slice(None) (It will fill the whole of result with ones).
So my question is: Can one have one's cake and eat it here, i.e.:
Is there anything f could return that faithfully mimics the semantics of np.arange(maxind) while avoiding advanced indexing where possible?
Since I'm almost resigned to the answer being no:
What's the next best thing?
Maybe return an "enhanced np.s_", i.e. an object with an engineered __getitem__?
class smart_idx:
def __init__(self, n):
self.n = n
def __getitem__(self, idx):
idx = idx if isinstance(idx, tuple) else (idx,)
if idx:
count = idx.count('X')
need_adv = count > 1
if count == 1:
for i in idx:
if not isinstance(i, slice) and i != Ellipsis:
need_adv = True
break
repl = np.arange(self.n) if need_adv else slice(None)
return tuple(repl if i == 'X' else i for i in idx)
return slice(None)
The user would have to use it like
data[idx[3, 4:9, 'X', [1,3,2,6]]]
data[idx['X', ..., :4:-1]]
data[idx[]]
and __getitem__, detecting the advanced index would decide to replace 'X' with np.arange(4) in the first and with slice(None) in the other two examples.
But that's rather clunky, not to mention that the overhead added might eat up whatever speed we gained.
Are there simpler strategies?
idxis a list/array you are going to get advanced indexing, regardless of whether you use a slice or arange for the to the other index. In the[arange...,idx]indexing it is picking one item from each row. Indexing on a flattened array is faster, but counterbalanced by the cost of computing the flat index. - hpauljidxwithslice(None)in special cases where that makes sense (basicallyidx==np.arange(maxind)+ no other advanced indices around). - Paul Panzeridxis, when you do indexing for arbitrary positions in >1D (2D in your example), e.g.[something..., idx]it is advanced indexing regardless of whatidxcould possibly be, unless bothsomethingandidxaresliceobjects, but that case would never mimic your desired behaviour. - Imanol Luengo