Repository: pandas-dev/pandas
File: pandas/core/groupby/ops.py (Python)

Code before:
        return klass(
            data,
            self.ngroups,
            sorted_ids=self._sorted_ids,
            sort_idx=self.result_ilocs,
        )

    @cache_readonly
    def indices(self) -> dict[Hashable, npt.NDArray[np.intp]]:
        """dict {group name -> group indices}"""
        if len(self.groupings) == 1 and isinstance(self.result_index, CategoricalIndex):
            # This shows unused categories in indices GH#38642
            result = self.groupings[0].indices
        else:
            codes_list = [ping.codes for ping in self.groupings]
            result = get_indexer_dict(codes_list, self.levels)
        if not self.dropna:
            has_mi = isinstance(self.result_index, MultiIndex)
            if not has_mi and self.result_index.hasnans:
                result = {
                    np.nan if isna(key) else key: value for key, value in result.items()
                }
            elif has_mi:
                # MultiIndex has no efficient way to tell if there are NAs
                result = {
                    tuple(np.nan if isna(comp) else comp for comp in key): value
                    for key, value in result.items()
                }

        return result

    @final
    @cache_readonly
    def result_ilocs(self) -> npt.NDArray[np.intp]:
        """
        Get the original integer locations of result_index in the input.
        """
        # Original indices are where group_index would go via sorting.
        # But when dropna is true, we need to remove null values while accounting for
        # any gaps that then occur because of them.
        ids = self.ids

        if self.has_dropped_na:
            mask = np.where(ids >= 0)
            # Count how many gaps are caused by previous null values for each position
            null_gaps = np.cumsum(ids == -1)[mask]
            ids = ids[mask]

        result = get_group_index_sorter(ids, self.ngroups)

        if self.has_dropped_na:

Review comment (suggestion):
```suggestion
                    # error: "Hashable" has no attribute "__iter__" (not iterable)
                    tuple(np.nan if isna(comp) else comp for comp in key): value  # type: ignore[attr-defined]
```

Code after:
        return klass(
            data,
            self.ngroups,
            sorted_ids=self._sorted_ids,
            sort_idx=self.result_ilocs,
        )

    @cache_readonly
    def indices(self) -> dict[Hashable, npt.NDArray[np.intp]]:
        """dict {group name -> group indices}"""
        if len(self.groupings) == 1 and isinstance(self.result_index, CategoricalIndex):
            # This shows unused categories in indices GH#38642
            result = self.groupings[0].indices
        else:
            codes_list = [ping.codes for ping in self.groupings]
            result = get_indexer_dict(codes_list, self.levels)
        if not self.dropna:
            has_mi = isinstance(self.result_index, MultiIndex)
            if not has_mi and self.result_index.hasnans:
                result = {
                    np.nan if isna(key) else key: value for key, value in result.items()
                }
            elif has_mi:
                # MultiIndex has no efficient way to tell if there are NAs
                result = {
                    # error: "Hashable" has no attribute "__iter__" (not iterable)
                    tuple(np.nan if isna(comp) else comp for comp in key): value  # type: ignore[attr-defined]
                    for key, value in result.items()
                }

        return result

    @final
    @cache_readonly
    def result_ilocs(self) -> npt.NDArray[np.intp]:
        """
        Get the original integer locations of result_index in the input.
        """
        # Original indices are where group_index would go via sorting.
        # But when dropna is true, we need to remove null values while accounting for
        # any gaps that then occur because of them.
        ids = self.ids

        if self.has_dropped_na:
            mask = np.where(ids >= 0)
            # Count how many gaps are caused by previous null values for each position
            null_gaps = np.cumsum(ids == -1)[mask]
            ids = ids[mask]

        result = get_group_index_sorter(ids, self.ngroups)