예제 #1
0
    def loc_to_iloc(self, key: GetItemKeyType) -> GetItemKeyType:
        '''
        This is the low-level loc_to_iloc, analagous to LocMap.loc_to_iloc as used by Index. As such, the key at this point should not be a Series or Index object.

        If key is an np.ndarray, a Boolean array will be passed through; otherwise, it will be treated as an iterable of values to be passed to leaf_loc_to_iloc.
        '''
        if isinstance(key, slice):
            # given a top-level definition of a slice (and if that slice results in a single value), we can get a value range
            return slice(*LocMap.map_slice_args(self.leaf_loc_to_iloc, key))

        # this should not match tuples that are leaf-locs
        if isinstance(key, KEY_ITERABLE_TYPES):
            if isinstance(key, np.ndarray) and key.dtype == bool:
                return key  # keep as Boolean
            return [self.leaf_loc_to_iloc(x) for x in key]

        if not isinstance(key, HLoc):
            # assume it is a leaf loc tuple
            return self.leaf_loc_to_iloc(key)

        # everything after this is an HLoc

        # collect all ilocs for all leaf indices matching HLoc patterns
        ilocs = []
        levels = deque(((self, 0, 0), ))  # order matters

        while levels:
            level, depth, offset = levels.popleft()
            depth_key = key[depth]
            next_offset = offset + level.offset

            # print(level, depth, offset, depth_key, next_offset)
            # import ipdb; ipdb.set_trace()

            if level.targets is None:
                try:
                    ilocs.append(
                        level.index.loc_to_iloc(depth_key, offset=next_offset))
                except KeyError:
                    pass
            else:  # target is iterable np.ndaarray
                try:
                    iloc = level.index.loc_to_iloc(depth_key)  # no offset
                except KeyError:
                    pass
                else:
                    level_targets = level.targets[
                        iloc]  # get one or more IndexLevel objects
                    next_depth = depth + 1
                    # if not an ndarray, iloc has extracted a single IndexLevel
                    if isinstance(level_targets, IndexLevel):
                        levels.append((level_targets, next_depth, next_offset))
                    else:
                        levels.extend([(lvl, next_depth, next_offset)
                                       for lvl in level_targets])

        iloc_count = len(ilocs)
        if iloc_count == 0:
            raise KeyError('no matching keys across all levels')

        if iloc_count == 1 and not key.has_key_multiple():
            # drop to a single iloc selection
            return ilocs[0]

        # NOTE: might be able to combine contiguous ilocs into a single slice
        iloc = []  # combine into one flat iloc
        length = self.__len__()
        for part in ilocs:
            if isinstance(part, slice):
                iloc.extend(range(*part.indices(length)))
            # just look for ints
            elif isinstance(part, INT_TYPES):
                iloc.append(part)
            else:  # assume it is an iterable
                iloc.extend(part)
        return iloc
예제 #2
0
    def loc_to_iloc(self, key: GetItemKeyTypeCompound) -> GetItemKeyType:
        '''
        This is the low-level loc_to_iloc, analagous to LocMap.loc_to_iloc as used by Index. As such, the key at this point should not be a Series or Index object.

        If key is an np.ndarray, a Boolean array will be passed through; otherwise, it will be treated as an iterable of values to be passed to leaf_loc_to_iloc.
        '''
        from static_frame.core.series import Series

        if isinstance(key, slice):
            # given a top-level definition of a slice (and if that slice results in a single value), we can get a value range
            return slice(*LocMap.map_slice_args(self.leaf_loc_to_iloc, key))

        if isinstance(key, KEY_ITERABLE_TYPES):  # iterables of leaf-locs
            if isinstance(key, np.ndarray) and key.dtype == bool:
                return key  # keep as Boolean
            return [self.leaf_loc_to_iloc(x) for x in key]

        if not isinstance(key, HLoc):  # assume a leaf loc tuple
            if not isinstance(key, tuple):
                raise KeyError(
                    f'{key} cannot be used for loc selection from IndexHierarchy; try HLoc'
                )
            return self.leaf_loc_to_iloc(key)

        # HLoc following: collect all ilocs for all leaf indices matching HLoc patterns
        ilocs = []
        levels = deque(((self, 0, 0), ))  # order matters

        while levels:
            level, depth, offset = levels.popleft()

            depth_key = key[depth]
            # NOTE: depth_key should not be Series or Index at this point; IndexHierarchy is responsible for unpacking / reindexing prior to this call
            next_offset = offset + level.offset

            if isinstance(depth_key,
                          np.ndarray) and depth_key.dtype == DTYPE_BOOL:
                # NOTE: use length of level, not length of index, as need to observe all leafs covered at this node.
                depth_key = depth_key[next_offset:next_offset + len(level)]
                if len(depth_key) > len(level.index):
                    # given leaf-Boolean, determine what upper nodes to select
                    depth_key = level.values_at_depth(0)[depth_key]
                    if len(depth_key) > 1:
                        # NOTE: must strip repeated labels, but cannot us np.unique as must retain order
                        depth_key = list(dict.fromkeys(depth_key).keys())

            # print(level, depth, offset, depth_key, next_offset)
            if level.targets is None:
                try:
                    # NOTE: as a selection list might be given within the HLoc, it will be tested accross many indices, and should support a partial matching
                    ilocs.append(
                        level.index.loc_to_iloc(
                            depth_key,
                            offset=next_offset,
                            partial_selection=True,
                        ))
                except KeyError:
                    pass
            else:  # when not at a leaf, we are selecting level_targets to descend withing
                try:  # NOTE: no offset necessary as not a leaf selection
                    iloc = level.index.loc_to_iloc(depth_key,
                                                   partial_selection=True)
                except KeyError:
                    pass
                else:
                    level_targets = level.targets[
                        iloc]  # get one or more IndexLevel objects
                    next_depth = depth + 1
                    # if not an ndarray, iloc has extracted a single IndexLevel
                    if isinstance(level_targets, IndexLevel):
                        levels.append((level_targets, next_depth, next_offset))
                    else:
                        levels.extend([(lvl, next_depth, next_offset)
                                       for lvl in level_targets])

        iloc_count = len(ilocs)
        if iloc_count == 0:
            raise KeyError('no matching keys across all levels')

        if iloc_count == 1 and not key.has_key_multiple():
            return ilocs[0]  # drop to a single iloc selection

        # NOTE: might be able to combine contiguous ilocs into a single slice
        iloc_flat: tp.List[GetItemKeyType] = []  # combine into one flat iloc
        length = self.__len__()
        for part in ilocs:
            if isinstance(part, slice):
                iloc_flat.extend(range(*part.indices(length)))
            elif isinstance(part, INT_TYPES):
                iloc_flat.append(part)
            else:  # assume it is an iterable
                iloc_flat.extend(part)  #type: ignore
        return iloc_flat