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

Code before:

        if self.as_index:
            # Always holds for SeriesGroupBy unless GH#36507 is implemented
            d = {
                self.obj._get_axis_name(self.axis): index,
                "copy": False,
                "fill_value": fill_value,
            }
            if using_string_dtype() and method == "sum":
                if isinstance(output, Series) and isinstance(output.dtype, StringDtype):
                    d["fill_value"] = ""
                    return output.reindex(**d)  # type: ignore[arg-type]
                elif isinstance(output, DataFrame) and any(
                    isinstance(dtype, StringDtype) for dtype in output.dtypes
                ):
                    orig_dtypes = output.dtypes
                    indices = np.nonzero(output.dtypes == "string")[0]
                    for idx in indices:
                        output.isetitem(idx, output.iloc[:, idx].astype(object))
                    output = output.reindex(**d)
                    for idx in indices:
                        col = output.iloc[:, idx]
                        output.isetitem(
                            idx, col.mask(col == 0, "").astype(orig_dtypes.iloc[idx])
                        )
                    return output
            return output.reindex(**d)  # type: ignore[arg-type]

        # GH 13204
        # Here, the categorical in-axis groupers, which need to be fully
        # expanded, are columns in `output`. An idea is to do:
        # output = output.set_index(self._grouper.names)
        #                .reindex(index).reset_index()
        # but special care has to be taken because of possible not-in-axis
        # groupers.
        # So, we manually select and drop the in-axis grouper columns,
        # reindex `output`, and then reset the in-axis grouper columns.

        # Select in-axis groupers
        in_axis_grps = [
            (i, ping.name) for (i, ping) in enumerate(groupings) if ping.in_axis
        ]
        if len(in_axis_grps) > 0:
            g_nums, g_names = zip(*in_axis_grps)
            output = output.drop(labels=list(g_names), axis=1)

        # Set a temp index and reindex (possibly expanding)
        output = output.set_index(self._grouper.result_index).reindex(
            index, copy=False, fill_value=fill_value
        )


Review comment (refactor):
The original fix from the PR does not actually work for the 2.3.x branch, because here we still have the `_reindex_output` helper method to deal with the observed=False case. 

And then we have the problem that `output.reindex(..)` doesn't work if you want to fill with multiple fill values (0 for numerical columns, "" for string columns). The above is a bit an ugly solution, but I also don't directly know a cleaner way how to do this with the current pandas APIs .. (and given this is only for 2.3.x and not to keep forever, and only behind the option flag, it's maybe OK?)

Code after:

        if self.as_index:
            # Always holds for SeriesGroupBy unless GH#36507 is implemented
            d = {
                self.obj._get_axis_name(self.axis): index,
                "copy": False,
                "fill_value": fill_value,
            }
            if using_string_dtype() and method == "sum":
                if isinstance(output, Series) and isinstance(output.dtype, StringDtype):
                    d["fill_value"] = ""
                    return output.reindex(**d)  # type: ignore[return-value, arg-type]
                elif isinstance(output, DataFrame) and any(
                    isinstance(dtype, StringDtype) for dtype in output.dtypes
                ):
                    orig_dtypes = output.dtypes
                    indices = np.nonzero(output.dtypes == "string")[0]
                    for idx in indices:
                        output.isetitem(idx, output.iloc[:, idx].astype(object))
                    output = output.reindex(**d)  # type: ignore[assignment, arg-type]
                    for idx in indices:
                        col = output.iloc[:, idx]
                        output.isetitem(
                            idx, col.mask(col == 0, "").astype(orig_dtypes.iloc[idx])
                        )
                    return output  # type: ignore[return-value]
            return output.reindex(**d)  # type: ignore[arg-type]

        # GH 13204
        # Here, the categorical in-axis groupers, which need to be fully
        # expanded, are columns in `output`. An idea is to do:
        # output = output.set_index(self._grouper.names)
        #                .reindex(index).reset_index()
        # but special care has to be taken because of possible not-in-axis
        # groupers.
        # So, we manually select and drop the in-axis grouper columns,
        # reindex `output`, and then reset the in-axis grouper columns.

        # Select in-axis groupers
        in_axis_grps = [
            (i, ping.name) for (i, ping) in enumerate(groupings) if ping.in_axis
        ]
        if len(in_axis_grps) > 0:
            g_nums, g_names = zip(*in_axis_grps)
            output = output.drop(labels=list(g_names), axis=1)

        # Set a temp index and reindex (possibly expanding)
        output = output.set_index(self._grouper.result_index).reindex(
            index, copy=False, fill_value=fill_value
        )