95079


# 環境

  * Python 3.11.2
  * pandas 2.0.2

#

列がMultiIndexで、列名がソートされていないDataFrameがあります。

[code]

    In [474]: df = pandas.DataFrame({("x","a"):[1,2,3],("y",""):["a","b","b"],("x","b"):[5,6,7]})
    
    In [481]: df
    Out[481]: 
       x  y  x
       a     b
    0  1  a  5
    1  2  b  6
    2  3  b  7
    
[/code]

y列をgroupbyで処理するとPerformanceWarningが発生しました。

[code]

    In [486]: df.groupby("y").sum()
    <ipython-input-486-d2970d0aff60>:1: PerformanceWarning: dropping on a non-lexsorted multi-index without a level parameter may impact performance.
      tmp.sum()
    Out[486]: 
       x    
       a   b
    y       
    a  1   5
    b  5  13
    
[/code]

しかし、以下のコードではPerformanceWarningは発生しませんでした。

[code]

    In [476]: df.groupby(("y","")).sum()
    Out[476]: 
           x    
           a   b
    (y, )       
    a      1   5
    b      5  13
    
[/code]

# 質問

groupbyに`"y"`を指定すると警告が発生するのに、`("y","")`を指定すると警告が発生しないのはなぜでしょうか？  
ソートされていないMultiIndexが問題であれば、`("y","")`を指定したときにも警告が発生すると思っていました。

# 補足

以下のブログを書いてるときに、気になった質問です。

<https://qiita.com/yuji38kwmt/items/01180d451c9f2610f624>

[pandasのissue](https://github.com/pandas-
dev/pandas/issues/new?assignees=&labels=Usage+Question%2CNeeds+Triage&projects=&template=submit_question.yml&title=QST%3A+)には、

> Since StackOverflow is better suited towards answering usage questions, we
> ask that all usage questions are first asked on StackOverflow.

と書いてあったので、まずは日本版StackOverflowで質問しました。

またバグではないような気がしたので、まだissueには報告していません。


CC BY-SA 4.0
2023-06-02T10:56:02.600
95076
2023-06-02T12:57:56.920
2023-06-02T12:57:56.920
47127
19524
question
python pandas
ソートされてないMultiIndexの列を持つDataFrameに対して、PerformanceWarningが発生するケースと発生しないケースの差は何でしょうか？


当該のエラーメッセージは以下の `drop()` メソッドで表示されています。(引用コードの最後の部分)

[pandas/multi.py at 2.0.x · pandas-dev/pandas](https://github.com/pandas-
dev/pandas/blob/2.0.x/pandas/core/indexes/multi.py#L2205)

[code]

    def drop(  # type: ignore[override]
        self,
        codes,
        level: Index | np.ndarray | Iterable[Hashable] | None = None,
        errors: IgnoreRaise = "raise",
    ) -> MultiIndex:
        """
        Make new MultiIndex with passed list of codes deleted.
    
        Parameters
        ----------
        codes : array-like
            Must be a list of tuples when level is not specified.
        level : int or level name, default None
        errors : str, default 'raise'
    
        Returns
        -------
        MultiIndex
        """
        if level is not None:
            return self._drop_from_level(codes, level, errors)
    
        if not isinstance(codes, (np.ndarray, Index)):
            try:
                codes = com.index_labels_to_array(codes, dtype=np.dtype("object"))
            except ValueError:
                pass
    
        inds = []
        for level_codes in codes:
            try:
                loc = self.get_loc(level_codes)
                # get_loc returns either an integer, a slice, or a boolean
                # mask
                if isinstance(loc, int):
                    inds.append(loc)
                elif isinstance(loc, slice):
                    step = loc.step if loc.step is not None else 1
                    inds.extend(range(loc.start, loc.stop, step))
                elif com.is_bool_indexer(loc):
                    if self._lexsort_depth == 0:
                        warnings.warn(
                            "dropping on a non-lexsorted multi-index "
                            "without a level parameter may impact performance.",
                            PerformanceWarning,
                            stacklevel=find_stack_level(),
                        )
    
[/code]

この `drop()` メソッドをそれぞれの場合で実行してみると `"y"` を指定した場合に当該のエラーが発生することが確認できます。

[code]

    >>> df.columns.drop("y")
    <stdin>:1: PerformanceWarning: dropping on a non-lexsorted multi-index without a level parameter may impact performance.
    MultiIndex([('x', 'a'),
                ('x', 'b')],
               )
    
    >>> df.columns.drop(("y",""))
    MultiIndex([('x', 'a'),
                ('x', 'b')],
               )
    
    #
    # "without a level parameter" との事なので、level を指定すればエラーメッセージは表示されない
    # (以下のコードが実行されるので)
    #
    # if level is not None:
    #     return self._drop_from_level(codes, level, errors)
    #
    >>> df.columns.drop("y", level=0)
    MultiIndex([('x', 'a'),
                ('x', 'b')],
               )
    
[/code]

理由は、それぞれの場合で `drop()` メソッドの内容を辿ってみると判るかと思います。

##### `df.groupby("y").sum()` の場合

[code]

    >>> codes = com.index_labels_to_array("y", dtype=np.dtype("object"))
    >>> codes
    array(['y'], dtype=object)
    >>> loc = df.columns.get_loc(codes[0])
    >>> loc
    array([False,  True, False])  # boolean array
    >>> com.is_bool_indexer(loc)
    True
    >>> df.columns._lexsort_depth
    0
    
[/code]

##### `df.groupby(("y","")).sum()` の場合

[code]

    >> codes = com.index_labels_to_array(("y",""), dtype=np.dtype("object"))
    >>> codes
    array([('y', '')], dtype=object)
    >>> loc = df.columns.get_loc(codes[0])
    >>> loc  # int
    1
    >>> isinstance(loc, int)
    True
    
[/code]


CC BY-SA 4.0
2023-06-02T12:54:35.487
95079
2023-06-02T12:54:35.487
47127
95076
answer