Skip to content

Commit afa7177

Browse files
committed
PERF: slightly faster getitem by using (axis, indices) pairs instead of creating IGroup objects internally
arr = ndtest((3, 4)) %timeit arr['a1', 'b2'] # 77.3 µs -> 67.1 µs %timeit arr.points['a1', 'b2'] # 80.7 µs -> 70.2 µs %timeit arr.ipoints[1, 2] # 25.9 µs -> 25.1 µs changed _key_to_axis_and_indices to return a dict directly
1 parent 57e5a3d commit afa7177

File tree

3 files changed

+34
-39
lines changed

3 files changed

+34
-39
lines changed

larray/core/array.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6314,13 +6314,10 @@ def insert(self, value, before=None, after=None, pos=None, axis=None, label=None
63146314
before = IGroup(pos, axis=axis)
63156315

63166316
if before is not None:
6317-
before = self.axes._translate_axis_key(before)
6318-
axis = before.axis
6319-
before_pos = axis.index(before)
6317+
axis, before_pos = self.axes._translate_axis_key(before)
63206318
else:
6321-
after = self.axes._translate_axis_key(after)
6322-
axis = after.axis
6323-
before_pos = axis.index(after) + 1
6319+
axis, after_pos = self.axes._translate_axis_key(after)
6320+
before_pos = after_pos + 1
63246321

63256322
def length(v):
63266323
if isinstance(v, Array) and axis in v.axes:
@@ -6424,9 +6421,7 @@ def drop(self, labels=None):
64246421
label0 0 1 2
64256422
label2 6 7 8
64266423
"""
6427-
group = self.axes._translate_axis_key(labels)
6428-
axis = group.axis
6429-
indices = axis.index(group)
6424+
axis, indices = self.axes._translate_axis_key(labels)
64306425
axis_idx = self.axes.index(axis)
64316426
new_axis = Axis(np.delete(axis.labels, indices), axis.name)
64326427
new_axes = self.axes.replace(axis, new_axis)
@@ -9257,8 +9252,8 @@ def stack(elements=None, axes=None, title=None, meta=None, dtype=None, res_axes=
92579252
items = list(zip(axes[0], values))
92589253
else:
92599254
def translate_and_sort_key(key, axes):
9260-
dict_of_igroups = {k.axis: k for k in axes._key_to_igroups(key)}
9261-
return tuple(dict_of_igroups[axis] for axis in axes)
9255+
dict_of_indices = dict(axes._key_to_axis_and_indices(key))
9256+
return tuple(IGroup(dict_of_indices[axis], axis=axis) for axis in axes)
92629257

92639258
# passing only via _key_to_igroup should be enough if we allow for partial axes
92649259
dict_elements = {translate_and_sort_key(key, axes): value for key, value in elements}

larray/core/axis.py

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2645,29 +2645,29 @@ def _translate_axis_key_chunk(self, axis_key):
26452645
26462646
Returns
26472647
-------
2648-
IGroup
2648+
(axis, indices)
26492649
Indices group with a valid axis (from self)
26502650
"""
26512651
axis_key = remove_nested_groups(axis_key)
26522652

2653-
if isinstance(axis_key, IGroup) and axis_key.axis is not None:
2653+
if isinstance(axis_key, IGroup):
2654+
if axis_key.axis is None:
2655+
raise ValueError("positional groups without axis are not supported")
2656+
26542657
# retarget to real axis, if needed
26552658
# only retarget IGroup and not LGroup to give the opportunity for axis.translate to try the "ticks"
26562659
# version of the group ONLY if key.axis is not real_axis (for performance reasons)
26572660
if axis_key.axis in self:
26582661
axis_key = axis_key.retarget_to(self[axis_key.axis])
2662+
# already positional
2663+
if isinstance(axis_key, IGroup):
2664+
return axis_key.axis, axis_key.key
26592665
else:
26602666
# axis associated with axis_key may not belong to self.
26612667
# In that case, we translate IGroup to labels and search for a compatible axis
26622668
# (see end of this method)
26632669
axis_key = axis_key.to_label()
26642670

2665-
# already positional
2666-
if isinstance(axis_key, IGroup):
2667-
if axis_key.axis is None:
2668-
raise ValueError("positional groups without axis are not supported")
2669-
return axis_key
2670-
26712671
# labels but known axis
26722672
if isinstance(axis_key, LGroup) and axis_key.axis is not None:
26732673
try:
@@ -2676,7 +2676,7 @@ def _translate_axis_key_chunk(self, axis_key):
26762676
axis_pos_key = real_axis.index(axis_key)
26772677
except KeyError:
26782678
raise ValueError(f"{axis_key!r} is not a valid label for any axis")
2679-
return real_axis.i[axis_pos_key]
2679+
return real_axis, axis_pos_key
26802680
except KeyError:
26812681
# axis associated with axis_key may not belong to self.
26822682
# In that case, we translate LGroup to labels and search for a compatible axis
@@ -2702,7 +2702,8 @@ def _translate_axis_key_chunk(self, axis_key):
27022702
valid_axes = ', '.join(a.name if a.name is not None else f'{{{self.index(a)}}}'
27032703
for a in valid_axes)
27042704
raise ValueError(f'{axis_key} is ambiguous (valid in {valid_axes})')
2705-
return valid_axes[0].i[axis_pos_key]
2705+
real_axis = valid_axes[0]
2706+
return real_axis, axis_pos_key
27062707

27072708
def _translate_axis_key(self, axis_key):
27082709
"""
@@ -2715,7 +2716,7 @@ def _translate_axis_key(self, axis_key):
27152716
27162717
Returns
27172718
-------
2718-
IGroup
2719+
(axis, indices)
27192720
Indices group with a valid axis (from self)
27202721
"""
27212722
# called from _key_to_igroups
@@ -2751,8 +2752,9 @@ def _translate_axis_key(self, axis_key):
27512752
# TODO: do not recheck already checked elements
27522753
key_chunk = axis_key.i[:size] if isinstance(axis_key, Array) else axis_key[:size]
27532754
try:
2754-
tkey = self._translate_axis_key_chunk(key_chunk)
2755-
axis = tkey.axis
2755+
axis, ikey = self._translate_axis_key_chunk(key_chunk)
2756+
# if key is unambiguous (did not raise an exception), we know the axis
2757+
# TODO: if len(axis_key) < size, we can return axis, ikey directly
27562758
break
27572759
# TODO: we should only continue when ValueError is caused by an ambiguous key, otherwise we only delay
27582760
# an inevitable failure
@@ -2763,17 +2765,17 @@ def _translate_axis_key(self, axis_key):
27632765
# make sure we have an Axis object
27642766
# TODO: we should make sure the tkey returned from _translate_axis_key_chunk always contains a
27652767
# real Axis (and thus kill this line)
2766-
axis = self[axis]
2768+
# axis = self[axis]
27672769
# wrap key in LGroup
27682770
axis_key = axis[axis_key]
27692771
# XXX: reuse tkey chunks and only translate the rest?
27702772
return self._translate_axis_key_chunk(axis_key)
27712773
else:
27722774
return self._translate_axis_key_chunk(axis_key)
27732775

2774-
def _key_to_igroups(self, key):
2776+
def _key_to_axis_and_indices(self, key):
27752777
"""
2776-
Translates any key to an IGroups tuple.
2778+
Translates any key to a tuple of (axis, indices) tuples.
27772779
27782780
Parameters
27792781
----------
@@ -2783,8 +2785,8 @@ def _key_to_igroups(self, key):
27832785
Returns
27842786
-------
27852787
tuple
2786-
tuple of IGroup, each IGroup having a real axis from this array.
2787-
The order of the IGroups is *not* guaranteed to be the same as the order of axes.
2788+
tuple of (axis, indices) pairs, with axis from this array.
2789+
The order of the pairs is *not* guaranteed to be the same as the order of axes.
27882790
27892791
See Also
27902792
--------
@@ -2827,7 +2829,7 @@ def _key_to_igroups(self, key):
28272829
key = [axis_key for axis_key in key
28282830
if not _isnoneslice(axis_key) and axis_key is not Ellipsis]
28292831

2830-
# translate all keys to IGroup
2832+
# translate all keys to (axis, indices) pairs
28312833
return tuple(self._translate_axis_key(axis_key) for axis_key in key)
28322834

28332835
def _key_to_raw_and_axes(self, key, collapse_slices=False, translate_key=True, points=False, wildcard=False):
@@ -2853,21 +2855,18 @@ def _key_to_raw_and_axes(self, key, collapse_slices=False, translate_key=True, p
28532855
# the key we need to know which axis each key belongs to and to do that, we need to
28542856
# translate the key to indices)
28552857

2856-
# any key -> (IGroup, IGroup, ...)
2857-
igroup_key = self._key_to_igroups(key)
2858-
2859-
# extract axis from Group keys
2860-
key_items = [(k1.axis, k1) for k1 in igroup_key]
2858+
# any key -> ((axis, indices), (axis, indices), ...)
2859+
key_items = self._key_to_axis_and_indices(key)
28612860

28622861
# even keys given as dict can contain duplicates (if the same axis was
28632862
# given under different forms, e.g. name and AxisReference).
2864-
dupe_axes = list(duplicates(axis1 for axis1, key1 in key_items))
2863+
dupe_axes = list(duplicates(axis for axis, key in key_items))
28652864
if dupe_axes:
2866-
dupe_axes = ', '.join(str(axis1) for axis1 in dupe_axes)
2865+
dupe_axes = ', '.join(str(axis) for axis in dupe_axes)
28672866
raise ValueError(f"key has several values for axis: {dupe_axes}\n{key_items}")
28682867

2869-
# IGroup -> raw positional
2870-
dict_key = {axis1: axis1.index(key1) for axis1, key1 in key_items}
2868+
# ((axis, indices), (axis, indices), ...) -> dict
2869+
dict_key = dict(key_items)
28712870

28722871
# dict -> tuple (complete and order key)
28732872
assert all(isinstance(k1, Axis) for k1 in dict_key)

larray/core/group.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,7 @@ def retarget_to(self, target_axis):
842842
-------
843843
Group with axis, raise ValueError if retargeting is not possible
844844
"""
845+
assert isinstance(target_axis, ABCAxis)
845846
if self.axis is target_axis:
846847
return self
847848
elif isinstance(self.axis, str) or isinstance(self.axis, ABCAxisReference):

0 commit comments

Comments
 (0)