データのインデックス付けと選択#

pandasオブジェクト内の軸のラベル情報は、多くの目的に役立ちます。

  • 既知のインジケーターを使用してデータを識別します(つまり、メタデータを提供します)。これは、分析、視覚化、およびインタラクティブなコンソール表示にとって重要です。

  • 自動的および明示的なデータアライメントを可能にします。

  • データセットのサブセットを直感的に取得および設定できます。

このセクションでは、最後の点に焦点を当てます。つまり、pandasオブジェクトのサブセットをスライス、ダイスし、一般的に取得および設定する方法です。この領域でより多くの開発がなされてきたSeriesとDataFrameが主な焦点となります。

PythonとNumPyのインデックス演算子 [] および属性演算子 . は、幅広いユースケースにおいてpandasデータ構造への迅速かつ容易なアクセスを提供します。これにより、Python辞書とNumPy配列の扱い方を知っていれば、新たに学ぶことがほとんどないため、インタラクティブな作業が直感的になります。ただし、アクセスするデータの型が事前にわからないため、標準演算子を直接使用することには最適化の限界があります。プロダクションコードでは、この章で説明する最適化されたpandasデータアクセスメソッドを利用することをお勧めします。

警告

設定操作に対してコピーまたは参照のどちらが返されるかは、コンテキストによって異なる場合があります。これは時として 連鎖割り当て と呼ばれ、避けるべきです。ビューを返すかコピーを返すか を参照してください。

MultiIndex およびより高度なインデックス付けのドキュメントについては、MultiIndex / 高度なインデックス付け を参照してください。

高度な戦略については、クックブック を参照してください。

インデックス付けのさまざまな選択肢#

オブジェクト選択には、より明示的な位置ベースのインデックス付けをサポートするために、ユーザーから多くの追加要望がありました。pandasは現在、3種類の多軸インデックス付けをサポートしています。

  • .loc は主にラベルベースですが、ブール配列と一緒に使用することもできます。.loc は、項目が見つからない場合に KeyError を発生させます。許容される入力は次のとおりです。

    • 単一のラベル。例: 5 または 'a' (ここで 5 はインデックスの ラベル として解釈されます。この使用はインデックスに沿った整数位置 ではありません)。

    • ラベルのリストまたは配列 ['a', 'b', 'c']

    • ラベル 'a':'f' を含むスライスオブジェクト (通常のPythonスライスとは異なり、インデックスに存在する場合は開始と停止の 両方 が含まれます! ラベルによるスライス および エンドポイントは包含的 を参照)。

    • ブール配列 (任意の NA 値は False として扱われます)。

    • 1つの引数 (呼び出し元のSeriesまたはDataFrame) を取り、インデックス付けに対して有効な出力 (上記のうちの1つ) を返す callable 関数。

    • 要素が上記の入力のいずれかである行 (および列) インデックスのタプル。

    詳細については、ラベルによる選択 を参照してください。

  • .iloc は主に整数位置ベース (軸の 0 から length-1 まで) ですが、ブール配列と一緒に使用することもできます。.iloc は、要求されたインデクサが範囲外の場合に IndexError を発生させます。ただし、スライスインデクサは範囲外のインデックス付けを許可します (これはPython/NumPyのスライスセマンティクスに準拠しています)。許容される入力は次のとおりです。

    • 整数、例: 5

    • 整数のリストまたは配列 [4, 3, 0]

    • 整数 1:7 を含むスライスオブジェクト。

    • ブール配列 (任意の NA 値は False として扱われます)。

    • 1つの引数 (呼び出し元のSeriesまたはDataFrame) を取り、インデックス付けに対して有効な出力 (上記のうちの1つ) を返す callable 関数。

    • 要素が上記の入力のいずれかである行 (および列) インデックスのタプル。

    詳細については、位置による選択高度なインデックス付け、および 高度な階層 を参照してください。

  • .loc.iloc、および [] インデックス付けもインデクサとして callable を受け入れることができます。詳細については、Callableによる選択 を参照してください。

    行(および列)インデックスへのタプルキーの分解は、呼び出し可能オブジェクトが適用される 前に 発生するため、行と列の両方をインデックス付けするために呼び出し可能オブジェクトからタプルを返すことはできません。

多軸選択でオブジェクトから値を取得するには、次の表記法を使用します(例として .loc を使用しますが、.iloc にも適用されます)。任意の軸アクセサはヌルスライス : であっても構いません。指定から省略された軸は : と見なされます。例えば、p.loc['a']p.loc['a', :] と同等です。

In [1]: ser = pd.Series(range(5), index=list("abcde"))

In [2]: ser.loc[["a", "c", "e"]]
Out[2]: 
a    0
c    2
e    4
dtype: int64

In [3]: df = pd.DataFrame(np.arange(25).reshape(5, 5), index=list("abcde"), columns=list("abcde"))

In [4]: df.loc[["a", "c", "e"], ["b", "d"]]
Out[4]: 
    b   d
a   1   3
c  11  13
e  21  23

基本#

前のセクション でデータ構造を紹介した際に述べたように、[] (Pythonでクラスの動作を実装することに慣れている人にとっては __getitem__ とも呼ばれる) を用いたインデックス付けの主な機能は、低次元のスライスを選択することです。次の表は、[] を用いてpandasオブジェクトをインデックス付けしたときの戻り値の型を示しています。

オブジェクトの型

選択

戻り値の型

Series

シリーズ[ラベル]

スカラ値

DataFrame

フレーム[列名]

列名に対応する Series

ここでは、インデックス付け機能を説明するために、簡単な時系列データセットを構築します。

In [5]: dates = pd.date_range('1/1/2000', periods=8)

In [6]: df = pd.DataFrame(np.random.randn(8, 4),
   ...:                   index=dates, columns=['A', 'B', 'C', 'D'])
   ...: 

In [7]: df
Out[7]: 
                   A         B         C         D
2000-01-01  0.469112 -0.282863 -1.509059 -1.135632
2000-01-02  1.212112 -0.173215  0.119209 -1.044236
2000-01-03 -0.861849 -2.104569 -0.494929  1.071804
2000-01-04  0.721555 -0.706771 -1.039575  0.271860
2000-01-05 -0.424972  0.567020  0.276232 -1.087401
2000-01-06 -0.673690  0.113648 -1.478427  0.524988
2000-01-07  0.404705  0.577046 -1.715002 -1.039268
2000-01-08 -0.370647 -1.157892 -1.344312  0.844885

特に明記しない限り、インデックス付け機能は時系列に固有のものではありません。

したがって、上記のように、[] を使用した最も基本的なインデックス付けを行います。

In [8]: s = df['A']

In [9]: s[dates[5]]
Out[9]: -0.6736897080883706

[] に列のリストを渡すことで、その順序で列を選択できます。DataFrameに列が含まれていない場合、例外が発生します。複数の列もこの方法で設定できます。

In [10]: df
Out[10]: 
                   A         B         C         D
2000-01-01  0.469112 -0.282863 -1.509059 -1.135632
2000-01-02  1.212112 -0.173215  0.119209 -1.044236
2000-01-03 -0.861849 -2.104569 -0.494929  1.071804
2000-01-04  0.721555 -0.706771 -1.039575  0.271860
2000-01-05 -0.424972  0.567020  0.276232 -1.087401
2000-01-06 -0.673690  0.113648 -1.478427  0.524988
2000-01-07  0.404705  0.577046 -1.715002 -1.039268
2000-01-08 -0.370647 -1.157892 -1.344312  0.844885

In [11]: df[['B', 'A']] = df[['A', 'B']]

In [12]: df
Out[12]: 
                   A         B         C         D
2000-01-01 -0.282863  0.469112 -1.509059 -1.135632
2000-01-02 -0.173215  1.212112  0.119209 -1.044236
2000-01-03 -2.104569 -0.861849 -0.494929  1.071804
2000-01-04 -0.706771  0.721555 -1.039575  0.271860
2000-01-05  0.567020 -0.424972  0.276232 -1.087401
2000-01-06  0.113648 -0.673690 -1.478427  0.524988
2000-01-07  0.577046  0.404705 -1.715002 -1.039268
2000-01-08 -1.157892 -0.370647 -1.344312  0.844885

これは、列のサブセットに変換を(インプレースで)適用するのに役立つ場合があります。

警告

pandasは、.loc から Series および DataFrame を設定するときに、すべての軸を整列します。

これは、列の配置が値の割り当ての前に行われるため、df変更しません

In [13]: df[['A', 'B']]
Out[13]: 
                   A         B
2000-01-01 -0.282863  0.469112
2000-01-02 -0.173215  1.212112
2000-01-03 -2.104569 -0.861849
2000-01-04 -0.706771  0.721555
2000-01-05  0.567020 -0.424972
2000-01-06  0.113648 -0.673690
2000-01-07  0.577046  0.404705
2000-01-08 -1.157892 -0.370647

In [14]: df.loc[:, ['B', 'A']] = df[['A', 'B']]

In [15]: df[['A', 'B']]
Out[15]: 
                   A         B
2000-01-01 -0.282863  0.469112
2000-01-02 -0.173215  1.212112
2000-01-03 -2.104569 -0.861849
2000-01-04 -0.706771  0.721555
2000-01-05  0.567020 -0.424972
2000-01-06  0.113648 -0.673690
2000-01-07  0.577046  0.404705
2000-01-08 -1.157892 -0.370647

列の値を交換する正しい方法は、生の値を使用することです。

In [16]: df.loc[:, ['B', 'A']] = df[['A', 'B']].to_numpy()

In [17]: df[['A', 'B']]
Out[17]: 
                   A         B
2000-01-01  0.469112 -0.282863
2000-01-02  1.212112 -0.173215
2000-01-03 -0.861849 -2.104569
2000-01-04  0.721555 -0.706771
2000-01-05 -0.424972  0.567020
2000-01-06 -0.673690  0.113648
2000-01-07  0.404705  0.577046
2000-01-08 -0.370647 -1.157892

ただし、.iloc は位置によって操作されるため、.iloc から SeriesDataFrame を設定する場合、pandasは軸を整列しません。

これは、値の代入前に列の配置が行われないため、df を変更します。

In [18]: df[['A', 'B']]
Out[18]: 
                   A         B
2000-01-01  0.469112 -0.282863
2000-01-02  1.212112 -0.173215
2000-01-03 -0.861849 -2.104569
2000-01-04  0.721555 -0.706771
2000-01-05 -0.424972  0.567020
2000-01-06 -0.673690  0.113648
2000-01-07  0.404705  0.577046
2000-01-08 -0.370647 -1.157892

In [19]: df.iloc[:, [1, 0]] = df[['A', 'B']]

In [20]: df[['A','B']]
Out[20]: 
                   A         B
2000-01-01 -0.282863  0.469112
2000-01-02 -0.173215  1.212112
2000-01-03 -2.104569 -0.861849
2000-01-04 -0.706771  0.721555
2000-01-05  0.567020 -0.424972
2000-01-06  0.113648 -0.673690
2000-01-07  0.577046  0.404705
2000-01-08 -1.157892 -0.370647

属性アクセス#

Series のインデックスまたは DataFrame の列に属性として直接アクセスできます。

In [21]: sa = pd.Series([1, 2, 3], index=list('abc'))

In [22]: dfa = df.copy()
In [23]: sa.b
Out[23]: 2

In [24]: dfa.A
Out[24]: 
2000-01-01   -0.282863
2000-01-02   -0.173215
2000-01-03   -2.104569
2000-01-04   -0.706771
2000-01-05    0.567020
2000-01-06    0.113648
2000-01-07    0.577046
2000-01-08   -1.157892
Freq: D, Name: A, dtype: float64
In [25]: sa.a = 5

In [26]: sa
Out[26]: 
a    5
b    2
c    3
dtype: int64

In [27]: dfa.A = list(range(len(dfa.index)))  # ok if A already exists

In [28]: dfa
Out[28]: 
            A         B         C         D
2000-01-01  0  0.469112 -1.509059 -1.135632
2000-01-02  1  1.212112  0.119209 -1.044236
2000-01-03  2 -0.861849 -0.494929  1.071804
2000-01-04  3  0.721555 -1.039575  0.271860
2000-01-05  4 -0.424972  0.276232 -1.087401
2000-01-06  5 -0.673690 -1.478427  0.524988
2000-01-07  6  0.404705 -1.715002 -1.039268
2000-01-08  7 -0.370647 -1.344312  0.844885

In [29]: dfa['A'] = list(range(len(dfa.index)))  # use this form to create a new column

In [30]: dfa
Out[30]: 
            A         B         C         D
2000-01-01  0  0.469112 -1.509059 -1.135632
2000-01-02  1  1.212112  0.119209 -1.044236
2000-01-03  2 -0.861849 -0.494929  1.071804
2000-01-04  3  0.721555 -1.039575  0.271860
2000-01-05  4 -0.424972  0.276232 -1.087401
2000-01-06  5 -0.673690 -1.478427  0.524988
2000-01-07  6  0.404705 -1.715002 -1.039268
2000-01-08  7 -0.370647 -1.344312  0.844885

警告

  • このアクセスは、インデックス要素が有効なPython識別子である場合にのみ使用できます。たとえば、s.1 は許可されません。有効な識別子の説明については、こちら を参照してください。

  • 既存のメソッド名と衝突する場合、属性は利用できません。例えば、s.min は許可されませんが、s['min'] は可能です。

  • 同様に、以下のリストのいずれかと競合する場合、属性は利用できません:indexmajor_axisminor_axisitems

  • これらのいずれの場合でも、標準のインデックス付けは機能します。例えば、s['1']s['min']s['index'] は対応する要素または列にアクセスします。

IPython環境を使用している場合、タブ補完を使用してこれらのアクセス可能な属性を表示することもできます。

DataFrameの行に dict を割り当てることもできます。

In [31]: x = pd.DataFrame({'x': [1, 2, 3], 'y': [3, 4, 5]})

In [32]: x.iloc[1] = {'x': 9, 'y': 99}

In [33]: x
Out[33]: 
   x   y
0  1   3
1  9  99
2  3   5

属性アクセスを使用してSeriesの既存の要素やDataFrameの列を変更できますが、注意が必要です。属性アクセスを使用して新しい列を作成しようとすると、新しい列ではなく新しい属性が作成され、UserWarning が発生します。

In [34]: df_new = pd.DataFrame({'one': [1., 2., 3.]})

In [35]: df_new.two = [4, 5, 6]

In [36]: df_new
Out[36]: 
   one
0  1.0
1  2.0
2  3.0

範囲のスライス#

任意の軸に沿って範囲をスライスする最も堅牢で一貫性のある方法は、.iloc メソッドを詳しく説明する 位置による選択 セクションで説明されています。ここでは、[] 演算子を使用したスライスのセマンティクスを説明します。

Seriesでは、構文はndarrayとまったく同じように機能し、値のスライスと対応するラベルを返します。

In [37]: s[:5]
Out[37]: 
2000-01-01    0.469112
2000-01-02    1.212112
2000-01-03   -0.861849
2000-01-04    0.721555
2000-01-05   -0.424972
Freq: D, Name: A, dtype: float64

In [38]: s[::2]
Out[38]: 
2000-01-01    0.469112
2000-01-03   -0.861849
2000-01-05   -0.424972
2000-01-07    0.404705
Freq: 2D, Name: A, dtype: float64

In [39]: s[::-1]
Out[39]: 
2000-01-08   -0.370647
2000-01-07    0.404705
2000-01-06   -0.673690
2000-01-05   -0.424972
2000-01-04    0.721555
2000-01-03   -0.861849
2000-01-02    1.212112
2000-01-01    0.469112
Freq: -1D, Name: A, dtype: float64

設定も機能することに注意してください。

In [40]: s2 = s.copy()

In [41]: s2[:5] = 0

In [42]: s2
Out[42]: 
2000-01-01    0.000000
2000-01-02    0.000000
2000-01-03    0.000000
2000-01-04    0.000000
2000-01-05    0.000000
2000-01-06   -0.673690
2000-01-07    0.404705
2000-01-08   -0.370647
Freq: D, Name: A, dtype: float64

DataFrameでは、[] 内のスライスは 行をスライスします。これは非常に一般的な操作であるため、主に利便性のために提供されています。

In [43]: df[:3]
Out[43]: 
                   A         B         C         D
2000-01-01 -0.282863  0.469112 -1.509059 -1.135632
2000-01-02 -0.173215  1.212112  0.119209 -1.044236
2000-01-03 -2.104569 -0.861849 -0.494929  1.071804

In [44]: df[::-1]
Out[44]: 
                   A         B         C         D
2000-01-08 -1.157892 -0.370647 -1.344312  0.844885
2000-01-07  0.577046  0.404705 -1.715002 -1.039268
2000-01-06  0.113648 -0.673690 -1.478427  0.524988
2000-01-05  0.567020 -0.424972  0.276232 -1.087401
2000-01-04 -0.706771  0.721555 -1.039575  0.271860
2000-01-03 -2.104569 -0.861849 -0.494929  1.071804
2000-01-02 -0.173215  1.212112  0.119209 -1.044236
2000-01-01 -0.282863  0.469112 -1.509059 -1.135632

ラベルによる選択#

警告

設定操作に対してコピーまたは参照のどちらが返されるかは、コンテキストによって異なる場合があります。これは時として chained assignment と呼ばれ、避けるべきです。ビューを返すかコピーを返すか を参照してください。

警告

.loc は、インデックス型と互換性のない(または変換できない)スライサーを提示すると厳密に動作します。例えば、DatetimeIndex で整数を使用する場合などです。これらは TypeError を発生させます。

In [45]: dfl = pd.DataFrame(np.random.randn(5, 4),
   ....:                    columns=list('ABCD'),
   ....:                    index=pd.date_range('20130101', periods=5))
   ....: 

In [46]: dfl
Out[46]: 
                   A         B         C         D
2013-01-01  1.075770 -0.109050  1.643563 -1.469388
2013-01-02  0.357021 -0.674600 -1.776904 -0.968914
2013-01-03 -1.294524  0.413738  0.276662 -0.472035
2013-01-04 -0.013960 -0.362543 -0.006154 -0.923061
2013-01-05  0.895717  0.805244 -1.206412  2.565646

In [47]: dfl.loc[2:3]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[47], line 1
----> 1 dfl.loc[2:3]

File ~/work/pandas/pandas/pandas/core/indexing.py:1191, in _LocationIndexer.__getitem__(self, key)
   1189 maybe_callable = com.apply_if_callable(key, self.obj)
   1190 maybe_callable = self._check_deprecated_callable_usage(key, maybe_callable)
-> 1191 return self._getitem_axis(maybe_callable, axis=axis)

File ~/work/pandas/pandas/pandas/core/indexing.py:1411, in _LocIndexer._getitem_axis(self, key, axis)
   1409 if isinstance(key, slice):
   1410     self._validate_key(key, axis)
-> 1411     return self._get_slice_axis(key, axis=axis)
   1412 elif com.is_bool_indexer(key):
   1413     return self._getbool_axis(key, axis=axis)

File ~/work/pandas/pandas/pandas/core/indexing.py:1443, in _LocIndexer._get_slice_axis(self, slice_obj, axis)
   1440     return obj.copy(deep=False)
   1442 labels = obj._get_axis(axis)
-> 1443 indexer = labels.slice_indexer(slice_obj.start, slice_obj.stop, slice_obj.step)
   1445 if isinstance(indexer, slice):
   1446     return self.obj._slice(indexer, axis=axis)

File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:682, in DatetimeIndex.slice_indexer(self, start, end, step)
    674 # GH#33146 if start and end are combinations of str and None and Index is not
    675 # monotonic, we can not use Index.slice_indexer because it does not honor the
    676 # actual elements, is only searching for start and end
    677 if (
    678     check_str_or_none(start)
    679     or check_str_or_none(end)
    680     or self.is_monotonic_increasing
    681 ):
--> 682     return Index.slice_indexer(self, start, end, step)
    684 mask = np.array(True)
    685 in_index = True

File ~/work/pandas/pandas/pandas/core/indexes/base.py:6678, in Index.slice_indexer(self, start, end, step)
   6634 def slice_indexer(
   6635     self,
   6636     start: Hashable | None = None,
   6637     end: Hashable | None = None,
   6638     step: int | None = None,
   6639 ) -> slice:
   6640     """
   6641     Compute the slice indexer for input labels and step.
   6642 
   (...)
   6676     slice(1, 3, None)
   6677     """
-> 6678     start_slice, end_slice = self.slice_locs(start, end, step=step)
   6680     # return a slice
   6681     if not is_scalar(start_slice):

File ~/work/pandas/pandas/pandas/core/indexes/base.py:6904, in Index.slice_locs(self, start, end, step)
   6902 start_slice = None
   6903 if start is not None:
-> 6904     start_slice = self.get_slice_bound(start, "left")
   6905 if start_slice is None:
   6906     start_slice = 0

File ~/work/pandas/pandas/pandas/core/indexes/base.py:6819, in Index.get_slice_bound(self, label, side)
   6815 original_label = label
   6817 # For datetime indices label may be a string that has to be converted
   6818 # to datetime boundary according to its resolution.
-> 6819 label = self._maybe_cast_slice_bound(label, side)
   6821 # we need to look up the label
   6822 try:

File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:642, in DatetimeIndex._maybe_cast_slice_bound(self, label, side)
    637 if isinstance(label, dt.date) and not isinstance(label, dt.datetime):
    638     # Pandas supports slicing with dates, treated as datetimes at midnight.
    639     # https://github.com/pandas-dev/pandas/issues/31501
    640     label = Timestamp(label).to_pydatetime()
--> 642 label = super()._maybe_cast_slice_bound(label, side)
    643 self._data._assert_tzawareness_compat(label)
    644 return Timestamp(label)

File ~/work/pandas/pandas/pandas/core/indexes/datetimelike.py:378, in DatetimeIndexOpsMixin._maybe_cast_slice_bound(self, label, side)
    376     return lower if side == "left" else upper
    377 elif not isinstance(label, self._data._recognized_scalars):
--> 378     self._raise_invalid_indexer("slice", label)
    380 return label

File ~/work/pandas/pandas/pandas/core/indexes/base.py:4308, in Index._raise_invalid_indexer(self, form, key, reraise)
   4306 if reraise is not lib.no_default:
   4307     raise TypeError(msg) from reraise
-> 4308 raise TypeError(msg)

TypeError: cannot do slice indexing on DatetimeIndex with these indexers [2] of type int

スライス内の文字列のようなものは、インデックスの型に変換可能であり、自然なスライスにつながる ことがあります

In [48]: dfl.loc['20130102':'20130104']
Out[48]: 
                   A         B         C         D
2013-01-02  0.357021 -0.674600 -1.776904 -0.968914
2013-01-03 -1.294524  0.413738  0.276662 -0.472035
2013-01-04 -0.013960 -0.362543 -0.006154 -0.923061

pandasは、**純粋なラベルベースのインデックス付け**を行うための一連のメソッドを提供します。これは厳密な包含ベースのプロトコルです。要求されたすべてのラベルはインデックス内に存在する必要があり、存在しない場合は KeyError が発生します。スライスの場合、開始境界 **と** 停止境界の 両方 が、インデックス内に存在すれば 含まれます。整数は有効なラベルですが、それらはラベルを指し、**位置を指しません**。

.loc 属性が主要なアクセス方法です。以下の入力が有効です。

  • 単一のラベル。例: 5 または 'a' (ここで 5 はインデックスの ラベル として解釈されます。この使用はインデックスに沿った整数位置 ではありません)。

  • ラベルのリストまたは配列 ['a', 'b', 'c']

  • ラベル 'a':'f' を含むスライスオブジェクト (通常のPythonスライスとは異なり、インデックスに存在する場合は開始と停止の 両方 が含まれます! ラベルによるスライス を参照)。

  • ブール配列。

  • callableCallableによる選択 を参照。

In [49]: s1 = pd.Series(np.random.randn(6), index=list('abcdef'))

In [50]: s1
Out[50]: 
a    1.431256
b    1.340309
c   -1.170299
d   -0.226169
e    0.410835
f    0.813850
dtype: float64

In [51]: s1.loc['c':]
Out[51]: 
c   -1.170299
d   -0.226169
e    0.410835
f    0.813850
dtype: float64

In [52]: s1.loc['b']
Out[52]: 1.3403088497993827

設定も機能することに注意してください。

In [53]: s1.loc['c':] = 0

In [54]: s1
Out[54]: 
a    1.431256
b    1.340309
c    0.000000
d    0.000000
e    0.000000
f    0.000000
dtype: float64

DataFrameの場合

In [55]: df1 = pd.DataFrame(np.random.randn(6, 4),
   ....:                    index=list('abcdef'),
   ....:                    columns=list('ABCD'))
   ....: 

In [56]: df1
Out[56]: 
          A         B         C         D
a  0.132003 -0.827317 -0.076467 -1.187678
b  1.130127 -1.436737 -1.413681  1.607920
c  1.024180  0.569605  0.875906 -2.211372
d  0.974466 -2.006747 -0.410001 -0.078638
e  0.545952 -1.219217 -1.226825  0.769804
f -1.281247 -0.727707 -0.121306 -0.097883

In [57]: df1.loc[['a', 'b', 'd'], :]
Out[57]: 
          A         B         C         D
a  0.132003 -0.827317 -0.076467 -1.187678
b  1.130127 -1.436737 -1.413681  1.607920
d  0.974466 -2.006747 -0.410001 -0.078638

ラベルスライスによるアクセス

In [58]: df1.loc['d':, 'A':'C']
Out[58]: 
          A         B         C
d  0.974466 -2.006747 -0.410001
e  0.545952 -1.219217 -1.226825
f -1.281247 -0.727707 -0.121306

ラベルを使った断面の取得(df.xs('a') と同じ)

In [59]: df1.loc['a']
Out[59]: 
A    0.132003
B   -0.827317
C   -0.076467
D   -1.187678
Name: a, dtype: float64

ブール配列で値を取得する

In [60]: df1.loc['a'] > 0
Out[60]: 
A     True
B    False
C    False
D    False
Name: a, dtype: bool

In [61]: df1.loc[:, df1.loc['a'] > 0]
Out[61]: 
          A
a  0.132003
b  1.130127
c  1.024180
d  0.974466
e  0.545952
f -1.281247

ブール配列中のNA値は False として伝播します。

In [62]: mask = pd.array([True, False, True, False, pd.NA, False], dtype="boolean")

In [63]: mask
Out[63]: 
<BooleanArray>
[True, False, True, False, <NA>, False]
Length: 6, dtype: boolean

In [64]: df1[mask]
Out[64]: 
          A         B         C         D
a  0.132003 -0.827317 -0.076467 -1.187678
c  1.024180  0.569605  0.875906 -2.211372

値を明示的に取得する

# this is also equivalent to ``df1.at['a','A']``
In [65]: df1.loc['a', 'A']
Out[65]: 0.13200317033032932

ラベルによるスライス#

.loc をスライスと一緒に使用する場合、開始ラベルと終了ラベルの両方がインデックス内に存在すれば、その両方を含む2つの間に 位置する 要素が返されます。

In [66]: s = pd.Series(list('abcde'), index=[0, 3, 2, 5, 4])

In [67]: s.loc[3:5]
Out[67]: 
3    b
2    c
5    d
dtype: object

2つのうち少なくとも1つが存在しないが、インデックスがソートされており、開始ラベルと停止ラベルと比較できる場合、2つの間に ランク付けされる ラベルを選択することによって、スライスは期待どおりに機能します。

In [68]: s.sort_index()
Out[68]: 
0    a
2    c
3    b
4    e
5    d
dtype: object

In [69]: s.sort_index().loc[1:6]
Out[69]: 
2    c
3    b
4    e
5    d
dtype: object

しかし、2つのうち少なくとも1つが存在せず、**かつ**インデックスがソートされていない場合、エラーが発生します(そうしないと計算コストが高く、また混合型インデックスでは曖昧になる可能性があるため)。例えば、上記の例で s.loc[1:6]KeyError を発生させます。

この動作の根拠については、エンドポイントは包含的 を参照してください。

In [70]: s = pd.Series(list('abcdef'), index=[0, 3, 2, 5, 4, 2])

In [71]: s.loc[3:5]
Out[71]: 
3    b
2    c
5    d
dtype: object

また、インデックスに重複するラベルがあり、**かつ**開始ラベルまたは停止ラベルのいずれかが重複している場合、エラーが発生します。例えば、上記の例で s.loc[2:5]KeyError を発生させます。

重複するラベルに関する詳細については、重複するラベル を参照してください。

位置による選択#

警告

設定操作に対してコピーまたは参照のどちらが返されるかは、コンテキストによって異なる場合があります。これは時として chained assignment と呼ばれ、避けるべきです。ビューを返すかコピーを返すか を参照してください。

pandasは、**純粋に整数ベースのインデックス付け**を行うための一連のメソッドを提供します。そのセマンティクスはPythonおよびNumPyのスライスに密接に従います。これらは 0-based インデックス付けです。スライスの場合、開始境界は 含まれ、上部境界は 除外されます。非整数、たとえ **有効な** ラベルであっても使用しようとすると IndexError が発生します。

.iloc 属性が主要なアクセス方法です。以下の入力が有効です。

  • 整数、例: 5

  • 整数のリストまたは配列 [4, 3, 0]

  • 整数 1:7 を含むスライスオブジェクト。

  • ブール配列。

  • callableCallableによる選択 を参照。

  • 行(および列)インデックスのタプルで、その要素は上記の型のいずれかです。

In [72]: s1 = pd.Series(np.random.randn(5), index=list(range(0, 10, 2)))

In [73]: s1
Out[73]: 
0    0.695775
2    0.341734
4    0.959726
6   -1.110336
8   -0.619976
dtype: float64

In [74]: s1.iloc[:3]
Out[74]: 
0    0.695775
2    0.341734
4    0.959726
dtype: float64

In [75]: s1.iloc[3]
Out[75]: -1.110336102891167

設定も機能することに注意してください。

In [76]: s1.iloc[:3] = 0

In [77]: s1
Out[77]: 
0    0.000000
2    0.000000
4    0.000000
6   -1.110336
8   -0.619976
dtype: float64

DataFrameの場合

In [78]: df1 = pd.DataFrame(np.random.randn(6, 4),
   ....:                    index=list(range(0, 12, 2)),
   ....:                    columns=list(range(0, 8, 2)))
   ....: 

In [79]: df1
Out[79]: 
           0         2         4         6
0   0.149748 -0.732339  0.687738  0.176444
2   0.403310 -0.154951  0.301624 -2.179861
4  -1.369849 -0.954208  1.462696 -1.743161
6  -0.826591 -0.345352  1.314232  0.690579
8   0.995761  2.396780  0.014871  3.357427
10 -0.317441 -1.236269  0.896171 -0.487602

整数スライスによる選択

In [80]: df1.iloc[:3]
Out[80]: 
          0         2         4         6
0  0.149748 -0.732339  0.687738  0.176444
2  0.403310 -0.154951  0.301624 -2.179861
4 -1.369849 -0.954208  1.462696 -1.743161

In [81]: df1.iloc[1:5, 2:4]
Out[81]: 
          4         6
2  0.301624 -2.179861
4  1.462696 -1.743161
6  1.314232  0.690579
8  0.014871  3.357427

整数リストによる選択

In [82]: df1.iloc[[1, 3, 5], [1, 3]]
Out[82]: 
           2         6
2  -0.154951 -2.179861
6  -0.345352  0.690579
10 -1.236269 -0.487602
In [83]: df1.iloc[1:3, :]
Out[83]: 
          0         2         4         6
2  0.403310 -0.154951  0.301624 -2.179861
4 -1.369849 -0.954208  1.462696 -1.743161
In [84]: df1.iloc[:, 1:3]
Out[84]: 
           2         4
0  -0.732339  0.687738
2  -0.154951  0.301624
4  -0.954208  1.462696
6  -0.345352  1.314232
8   2.396780  0.014871
10 -1.236269  0.896171
# this is also equivalent to ``df1.iat[1,1]``
In [85]: df1.iloc[1, 1]
Out[85]: -0.1549507744249032

整数位置による断面の取得(df.xs(1) と同等)

In [86]: df1.iloc[1]
Out[86]: 
0    0.403310
2   -0.154951
4    0.301624
6   -2.179861
Name: 2, dtype: float64

範囲外のスライスインデックスは、Python/NumPyと同様に適切に処理されます。

# these are allowed in Python/NumPy.
In [87]: x = list('abcdef')

In [88]: x
Out[88]: ['a', 'b', 'c', 'd', 'e', 'f']

In [89]: x[4:10]
Out[89]: ['e', 'f']

In [90]: x[8:10]
Out[90]: []

In [91]: s = pd.Series(x)

In [92]: s
Out[92]: 
0    a
1    b
2    c
3    d
4    e
5    f
dtype: object

In [93]: s.iloc[4:10]
Out[93]: 
4    e
5    f
dtype: object

In [94]: s.iloc[8:10]
Out[94]: Series([], dtype: object)

境界を超えるスライスを使用すると、空の軸(例えば、空のDataFrameが返される)になる可能性があることに注意してください。

In [95]: dfl = pd.DataFrame(np.random.randn(5, 2), columns=list('AB'))

In [96]: dfl
Out[96]: 
          A         B
0 -0.082240 -2.182937
1  0.380396  0.084844
2  0.432390  1.519970
3 -0.493662  0.600178
4  0.274230  0.132885

In [97]: dfl.iloc[:, 2:3]
Out[97]: 
Empty DataFrame
Columns: []
Index: [0, 1, 2, 3, 4]

In [98]: dfl.iloc[:, 1:3]
Out[98]: 
          B
0 -2.182937
1  0.084844
2  1.519970
3  0.600178
4  0.132885

In [99]: dfl.iloc[4:6]
Out[99]: 
         A         B
4  0.27423  0.132885

境界外の単一インデクサは IndexError を発生させます。任意の要素が境界外のインデクサのリストは IndexError を発生させます。

In [100]: dfl.iloc[[4, 5, 6]]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
File ~/work/pandas/pandas/pandas/core/indexing.py:1714, in _iLocIndexer._get_list_axis(self, key, axis)
   1713 try:
-> 1714     return self.obj._take_with_is_copy(key, axis=axis)
   1715 except IndexError as err:
   1716     # re-raise with different error message, e.g. test_getitem_ndarray_3d

File ~/work/pandas/pandas/pandas/core/generic.py:4172, in NDFrame._take_with_is_copy(self, indices, axis)
   4163 """
   4164 Internal version of the `take` method that sets the `_is_copy`
   4165 attribute to keep track of the parent dataframe (using in indexing
   (...)
   4170 See the docstring of `take` for full explanation of the parameters.
   4171 """
-> 4172 result = self.take(indices=indices, axis=axis)
   4173 # Maybe set copy if we didn't actually change the index.

File ~/work/pandas/pandas/pandas/core/generic.py:4152, in NDFrame.take(self, indices, axis, **kwargs)
   4148     indices = np.arange(
   4149         indices.start, indices.stop, indices.step, dtype=np.intp
   4150     )
-> 4152 new_data = self._mgr.take(
   4153     indices,
   4154     axis=self._get_block_manager_axis(axis),
   4155     verify=True,
   4156 )
   4157 return self._constructor_from_mgr(new_data, axes=new_data.axes).__finalize__(
   4158     self, method="take"
   4159 )

File ~/work/pandas/pandas/pandas/core/internals/managers.py:891, in BaseBlockManager.take(self, indexer, axis, verify)
    890 n = self.shape[axis]
--> 891 indexer = maybe_convert_indices(indexer, n, verify=verify)
    893 new_labels = self.axes[axis].take(indexer)

File ~/work/pandas/pandas/pandas/core/indexers/utils.py:282, in maybe_convert_indices(indices, n, verify)
    281     if mask.any():
--> 282         raise IndexError("indices are out-of-bounds")
    283 return indices

IndexError: indices are out-of-bounds

The above exception was the direct cause of the following exception:

IndexError                                Traceback (most recent call last)
Cell In[100], line 1
----> 1 dfl.iloc[[4, 5, 6]]

File ~/work/pandas/pandas/pandas/core/indexing.py:1191, in _LocationIndexer.__getitem__(self, key)
   1189 maybe_callable = com.apply_if_callable(key, self.obj)
   1190 maybe_callable = self._check_deprecated_callable_usage(key, maybe_callable)
-> 1191 return self._getitem_axis(maybe_callable, axis=axis)

File ~/work/pandas/pandas/pandas/core/indexing.py:1743, in _iLocIndexer._getitem_axis(self, key, axis)
   1741 # a list of integers
   1742 elif is_list_like_indexer(key):
-> 1743     return self._get_list_axis(key, axis=axis)
   1745 # a single integer
   1746 else:
   1747     key = item_from_zerodim(key)

File ~/work/pandas/pandas/pandas/core/indexing.py:1717, in _iLocIndexer._get_list_axis(self, key, axis)
   1714     return self.obj._take_with_is_copy(key, axis=axis)
   1715 except IndexError as err:
   1716     # re-raise with different error message, e.g. test_getitem_ndarray_3d
-> 1717     raise IndexError("positional indexers are out-of-bounds") from err

IndexError: positional indexers are out-of-bounds
In [101]: dfl.iloc[:, 4]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[101], line 1
----> 1 dfl.iloc[:, 4]

File ~/work/pandas/pandas/pandas/core/indexing.py:1184, in _LocationIndexer.__getitem__(self, key)
   1182     if self._is_scalar_access(key):
   1183         return self.obj._get_value(*key, takeable=self._takeable)
-> 1184     return self._getitem_tuple(key)
   1185 else:
   1186     # we by definition only have the 0th axis
   1187     axis = self.axis or 0

File ~/work/pandas/pandas/pandas/core/indexing.py:1690, in _iLocIndexer._getitem_tuple(self, tup)
   1689 def _getitem_tuple(self, tup: tuple):
-> 1690     tup = self._validate_tuple_indexer(tup)
   1691     with suppress(IndexingError):
   1692         return self._getitem_lowerdim(tup)

File ~/work/pandas/pandas/pandas/core/indexing.py:966, in _LocationIndexer._validate_tuple_indexer(self, key)
    964 for i, k in enumerate(key):
    965     try:
--> 966         self._validate_key(k, i)
    967     except ValueError as err:
    968         raise ValueError(
    969             "Location based indexing can only have "
    970             f"[{self._valid_types}] types"
    971         ) from err

File ~/work/pandas/pandas/pandas/core/indexing.py:1592, in _iLocIndexer._validate_key(self, key, axis)
   1590     return
   1591 elif is_integer(key):
-> 1592     self._validate_integer(key, axis)
   1593 elif isinstance(key, tuple):
   1594     # a tuple should already have been caught by this point
   1595     # so don't treat a tuple as a valid indexer
   1596     raise IndexingError("Too many indexers")

File ~/work/pandas/pandas/pandas/core/indexing.py:1685, in _iLocIndexer._validate_integer(self, key, axis)
   1683 len_axis = len(self.obj._get_axis(axis))
   1684 if key >= len_axis or key < -len_axis:
-> 1685     raise IndexError("single positional indexer is out-of-bounds")

IndexError: single positional indexer is out-of-bounds

callableによる選択#

.loc.iloc、および [] インデックス付けは、インデクサとして callable を受け入れることができます。callable は、1つの引数(呼び出し元のSeriesまたはDataFrame)を取り、インデックス付けに対して有効な出力を返す関数である必要があります。

.iloc インデックス付けの場合、行と列のインデックスのタプル分解が呼び出し可能オブジェクトを適用する 前に 発生するため、呼び出し可能オブジェクトからタプルを返すことはサポートされていません。

In [102]: df1 = pd.DataFrame(np.random.randn(6, 4),
   .....:                    index=list('abcdef'),
   .....:                    columns=list('ABCD'))
   .....: 

In [103]: df1
Out[103]: 
          A         B         C         D
a -0.023688  2.410179  1.450520  0.206053
b -0.251905 -2.213588  1.063327  1.266143
c  0.299368 -0.863838  0.408204 -1.048089
d -0.025747 -0.988387  0.094055  1.262731
e  1.289997  0.082423 -0.055758  0.536580
f -0.489682  0.369374 -0.034571 -2.484478

In [104]: df1.loc[lambda df: df['A'] > 0, :]
Out[104]: 
          A         B         C         D
c  0.299368 -0.863838  0.408204 -1.048089
e  1.289997  0.082423 -0.055758  0.536580

In [105]: df1.loc[:, lambda df: ['A', 'B']]
Out[105]: 
          A         B
a -0.023688  2.410179
b -0.251905 -2.213588
c  0.299368 -0.863838
d -0.025747 -0.988387
e  1.289997  0.082423
f -0.489682  0.369374

In [106]: df1.iloc[:, lambda df: [0, 1]]
Out[106]: 
          A         B
a -0.023688  2.410179
b -0.251905 -2.213588
c  0.299368 -0.863838
d -0.025747 -0.988387
e  1.289997  0.082423
f -0.489682  0.369374

In [107]: df1[lambda df: df.columns[0]]
Out[107]: 
a   -0.023688
b   -0.251905
c    0.299368
d   -0.025747
e    1.289997
f   -0.489682
Name: A, dtype: float64

Series で呼び出し可能インデックス付けを使用できます。

In [108]: df1['A'].loc[lambda s: s > 0]
Out[108]: 
c    0.299368
e    1.289997
Name: A, dtype: float64

これらのメソッド/インデクサを使用すると、一時変数を使用せずにデータ選択操作を連鎖させることができます。

In [109]: bb = pd.read_csv('data/baseball.csv', index_col='id')

In [110]: (bb.groupby(['year', 'team']).sum(numeric_only=True)
   .....:    .loc[lambda df: df['r'] > 100])
   .....: 
Out[110]: 
           stint    g    ab    r    h  X2b  ...     so   ibb   hbp    sh    sf  gidp
year team                                   ...                                     
2007 CIN       6  379   745  101  203   35  ...  127.0  14.0   1.0   1.0  15.0  18.0
     DET       5  301  1062  162  283   54  ...  176.0   3.0  10.0   4.0   8.0  28.0
     HOU       4  311   926  109  218   47  ...  212.0   3.0   9.0  16.0   6.0  17.0
     LAN      11  413  1021  153  293   61  ...  141.0   8.0   9.0   3.0   8.0  29.0
     NYN      13  622  1854  240  509  101  ...  310.0  24.0  23.0  18.0  15.0  48.0
     SFN       5  482  1305  198  337   67  ...  188.0  51.0   8.0  16.0   6.0  41.0
     TEX       2  198   729  115  200   40  ...  140.0   4.0   5.0   2.0   8.0  16.0
     TOR       4  459  1408  187  378   96  ...  265.0  16.0  12.0   4.0  16.0  38.0

[8 rows x 18 columns]

位置ベースとラベルベースのインデックス付けの組み合わせ#

「A」列のインデックスから0番目と2番目の要素を取得したい場合は、次のようにします。

In [111]: dfd = pd.DataFrame({'A': [1, 2, 3],
   .....:                     'B': [4, 5, 6]},
   .....:                    index=list('abc'))
   .....: 

In [112]: dfd
Out[112]: 
   A  B
a  1  4
b  2  5
c  3  6

In [113]: dfd.loc[dfd.index[[0, 2]], 'A']
Out[113]: 
a    1
c    3
Name: A, dtype: int64

これは .iloc を使用して表現することもできます。インデクサの場所を明示的に取得し、位置的 インデックス付けを使用して項目を選択します。

In [114]: dfd.iloc[[0, 2], dfd.columns.get_loc('A')]
Out[114]: 
a    1
c    3
Name: A, dtype: int64

複数のインデクサを取得するには、.get_indexer を使用します。

In [115]: dfd.iloc[[0, 2], dfd.columns.get_indexer(['A', 'B'])]
Out[115]: 
   A  B
a  1  4
c  3  6

再インデックス付け#

見つからない可能性のある要素を選択するための慣用的な方法は、.reindex() を使用することです。再インデックス付け のセクションも参照してください。

In [116]: s = pd.Series([1, 2, 3])

In [117]: s.reindex([1, 2, 3])
Out[117]: 
1    2.0
2    3.0
3    NaN
dtype: float64

あるいは、有効な キーのみを選択したい場合は、次の方法が慣用的で効率的です。これは選択のdtypeを保持することを保証します。

In [118]: labels = [1, 2, 3]

In [119]: s.loc[s.index.intersection(labels)]
Out[119]: 
1    2
2    3
dtype: int64

インデックスが重複していると、.reindex() でエラーが発生します。

In [120]: s = pd.Series(np.arange(4), index=['a', 'a', 'b', 'c'])

In [121]: labels = ['c', 'd']

In [122]: s.reindex(labels)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[122], line 1
----> 1 s.reindex(labels)

File ~/work/pandas/pandas/pandas/core/series.py:5164, in Series.reindex(self, index, axis, method, copy, level, fill_value, limit, tolerance)
   5147 @doc(
   5148     NDFrame.reindex,  # type: ignore[has-type]
   5149     klass=_shared_doc_kwargs["klass"],
   (...)
   5162     tolerance=None,
   5163 ) -> Series:
-> 5164     return super().reindex(
   5165         index=index,
   5166         method=method,
   5167         copy=copy,
   5168         level=level,
   5169         fill_value=fill_value,
   5170         limit=limit,
   5171         tolerance=tolerance,
   5172     )

File ~/work/pandas/pandas/pandas/core/generic.py:5629, in NDFrame.reindex(self, labels, index, columns, axis, method, copy, level, fill_value, limit, tolerance)
   5626     return self._reindex_multi(axes, copy, fill_value)
   5628 # perform the reindex on the axes
-> 5629 return self._reindex_axes(
   5630     axes, level, limit, tolerance, method, fill_value, copy
   5631 ).__finalize__(self, method="reindex")

File ~/work/pandas/pandas/pandas/core/generic.py:5652, in NDFrame._reindex_axes(self, axes, level, limit, tolerance, method, fill_value, copy)
   5649     continue
   5651 ax = self._get_axis(a)
-> 5652 new_index, indexer = ax.reindex(
   5653     labels, level=level, limit=limit, tolerance=tolerance, method=method
   5654 )
   5656 axis = self._get_axis_number(a)
   5657 obj = obj._reindex_with_indexers(
   5658     {axis: [new_index, indexer]},
   5659     fill_value=fill_value,
   5660     copy=copy,
   5661     allow_dups=False,
   5662 )

File ~/work/pandas/pandas/pandas/core/indexes/base.py:4436, in Index.reindex(self, target, method, level, limit, tolerance)
   4433     raise ValueError("cannot handle a non-unique multi-index!")
   4434 elif not self.is_unique:
   4435     # GH#42568
-> 4436     raise ValueError("cannot reindex on an axis with duplicate labels")
   4437 else:
   4438     indexer, _ = self.get_indexer_non_unique(target)

ValueError: cannot reindex on an axis with duplicate labels

一般的に、目的のラベルと現在の軸を交差させてから、再インデックス付けを行うことができます。

In [123]: s.loc[s.index.intersection(labels)].reindex(labels)
Out[123]: 
c    3.0
d    NaN
dtype: float64

しかし、結果のインデックスが重複している場合でも、これはエラーになります。

In [124]: labels = ['a', 'd']

In [125]: s.loc[s.index.intersection(labels)].reindex(labels)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[125], line 1
----> 1 s.loc[s.index.intersection(labels)].reindex(labels)

File ~/work/pandas/pandas/pandas/core/series.py:5164, in Series.reindex(self, index, axis, method, copy, level, fill_value, limit, tolerance)
   5147 @doc(
   5148     NDFrame.reindex,  # type: ignore[has-type]
   5149     klass=_shared_doc_kwargs["klass"],
   (...)
   5162     tolerance=None,
   5163 ) -> Series:
-> 5164     return super().reindex(
   5165         index=index,
   5166         method=method,
   5167         copy=copy,
   5168         level=level,
   5169         fill_value=fill_value,
   5170         limit=limit,
   5171         tolerance=tolerance,
   5172     )

File ~/work/pandas/pandas/pandas/core/generic.py:5629, in NDFrame.reindex(self, labels, index, columns, axis, method, copy, level, fill_value, limit, tolerance)
   5626     return self._reindex_multi(axes, copy, fill_value)
   5628 # perform the reindex on the axes
-> 5629 return self._reindex_axes(
   5630     axes, level, limit, tolerance, method, fill_value, copy
   5631 ).__finalize__(self, method="reindex")

File ~/work/pandas/pandas/pandas/core/generic.py:5652, in NDFrame._reindex_axes(self, axes, level, limit, tolerance, method, fill_value, copy)
   5649     continue
   5651 ax = self._get_axis(a)
-> 5652 new_index, indexer = ax.reindex(
   5653     labels, level=level, limit=limit, tolerance=tolerance, method=method
   5654 )
   5656 axis = self._get_axis_number(a)
   5657 obj = obj._reindex_with_indexers(
   5658     {axis: [new_index, indexer]},
   5659     fill_value=fill_value,
   5660     copy=copy,
   5661     allow_dups=False,
   5662 )

File ~/work/pandas/pandas/pandas/core/indexes/base.py:4436, in Index.reindex(self, target, method, level, limit, tolerance)
   4433     raise ValueError("cannot handle a non-unique multi-index!")
   4434 elif not self.is_unique:
   4435     # GH#42568
-> 4436     raise ValueError("cannot reindex on an axis with duplicate labels")
   4437 else:
   4438     indexer, _ = self.get_indexer_non_unique(target)

ValueError: cannot reindex on an axis with duplicate labels

ランダムサンプルの選択#

sample() メソッドを使用して、SeriesまたはDataFrameから行または列をランダムに選択します。このメソッドはデフォルトで行をサンプリングし、返す行/列の特定の数、または行の割合を受け入れます。

In [126]: s = pd.Series([0, 1, 2, 3, 4, 5])

# When no arguments are passed, returns 1 row.
In [127]: s.sample()
Out[127]: 
4    4
dtype: int64

# One may specify either a number of rows:
In [128]: s.sample(n=3)
Out[128]: 
0    0
4    4
1    1
dtype: int64

# Or a fraction of the rows:
In [129]: s.sample(frac=0.5)
Out[129]: 
5    5
3    3
1    1
dtype: int64

デフォルトでは、sample は各行を最大1回だけ返しますが、replace オプションを使用して置換サンプリングを行うこともできます。

In [130]: s = pd.Series([0, 1, 2, 3, 4, 5])

# Without replacement (default):
In [131]: s.sample(n=6, replace=False)
Out[131]: 
0    0
1    1
5    5
3    3
2    2
4    4
dtype: int64

# With replacement:
In [132]: s.sample(n=6, replace=True)
Out[132]: 
0    0
4    4
3    3
2    2
4    4
4    4
dtype: int64

デフォルトでは、各行が選択される確率は同じですが、行に異なる確率を持たせたい場合は、weights としてサンプリングウェイトを sample 関数に渡すことができます。これらのウェイトはリスト、NumPy配列、またはSeriesにすることができますが、サンプリングするオブジェクトと同じ長さである必要があります。欠損値はウェイトゼロとして扱われ、無限大の値は許可されません。ウェイトの合計が1にならない場合、すべてのウェイトをウェイトの合計で割ることによって再正規化されます。例えば

In [133]: s = pd.Series([0, 1, 2, 3, 4, 5])

In [134]: example_weights = [0, 0, 0.2, 0.2, 0.2, 0.4]

In [135]: s.sample(n=3, weights=example_weights)
Out[135]: 
5    5
4    4
3    3
dtype: int64

# Weights will be re-normalized automatically
In [136]: example_weights2 = [0.5, 0, 0, 0, 0, 0]

In [137]: s.sample(n=1, weights=example_weights2)
Out[137]: 
0    0
dtype: int64

DataFrameに適用する場合、DataFrameの列をサンプリングウェイトとして使用できます(行をサンプリングしていて列をサンプリングしていない場合)—単に列の名前を文字列として渡すだけです。

In [138]: df2 = pd.DataFrame({'col1': [9, 8, 7, 6],
   .....:                     'weight_column': [0.5, 0.4, 0.1, 0]})
   .....: 

In [139]: df2.sample(n=3, weights='weight_column')
Out[139]: 
   col1  weight_column
1     8            0.4
0     9            0.5
2     7            0.1

sample はまた、axis 引数を使用して行ではなく列をサンプリングすることもできます。

In [140]: df3 = pd.DataFrame({'col1': [1, 2, 3], 'col2': [2, 3, 4]})

In [141]: df3.sample(n=1, axis=1)
Out[141]: 
   col1
0     1
1     2
2     3

最後に、sample の乱数ジェネレーターのシードを random_state 引数で設定することもできます。これは整数(シードとして)またはNumPy RandomStateオブジェクトを受け入れます。

In [142]: df4 = pd.DataFrame({'col1': [1, 2, 3], 'col2': [2, 3, 4]})

# With a given seed, the sample will always draw the same rows.
In [143]: df4.sample(n=2, random_state=2)
Out[143]: 
   col1  col2
2     3     4
1     2     3

In [144]: df4.sample(n=2, random_state=2)
Out[144]: 
   col1  col2
2     3     4
1     2     3

拡大を伴う設定#

.loc/[] 操作は、その軸に存在しないキーを設定するときに拡大を実行できます。

Series の場合、これは実質的に追加操作となります。

In [145]: se = pd.Series([1, 2, 3])

In [146]: se
Out[146]: 
0    1
1    2
2    3
dtype: int64

In [147]: se[5] = 5.

In [148]: se
Out[148]: 
0    1.0
1    2.0
2    3.0
5    5.0
dtype: float64

DataFrame.loc を介してどちらの軸でも拡大できます。

In [149]: dfi = pd.DataFrame(np.arange(6).reshape(3, 2),
   .....:                    columns=['A', 'B'])
   .....: 

In [150]: dfi
Out[150]: 
   A  B
0  0  1
1  2  3
2  4  5

In [151]: dfi.loc[:, 'C'] = dfi.loc[:, 'A']

In [152]: dfi
Out[152]: 
   A  B  C
0  0  1  0
1  2  3  2
2  4  5  4

これは DataFrameappend 操作のようなものです。

In [153]: dfi.loc[3] = 5

In [154]: dfi
Out[154]: 
   A  B  C
0  0  1  0
1  2  3  2
2  4  5  4
3  5  5  5

高速なスカラー値の取得と設定#

[] によるインデックス付けは、多くのケース(単一ラベルアクセス、スライシング、ブールインデックス付けなど)を処理する必要があるため、何を要求しているかを把握するのに少しオーバーヘッドがあります。スカラー値にのみアクセスしたい場合、最も速い方法は、すべてのデータ構造に実装されている at および iat メソッドを使用することです。

loc と同様に、atラベル ベースのスカラー検索を提供し、iatiloc と同様に 整数 ベースの検索を提供します。

In [155]: s.iat[5]
Out[155]: 5

In [156]: df.at[dates[5], 'A']
Out[156]: 0.1136484096888855

In [157]: df.iat[3, 0]
Out[157]: -0.7067711336300845

これらの同じインデクサを使用して設定することもできます。

In [158]: df.at[dates[5], 'E'] = 7

In [159]: df.iat[3, 0] = 7

インデクサが欠落している場合、at は上記のようにオブジェクトをインプレースで拡大する可能性があります。

In [160]: df.at[dates[-1] + pd.Timedelta('1 day'), 0] = 7

In [161]: df
Out[161]: 
                   A         B         C         D    E    0
2000-01-01 -0.282863  0.469112 -1.509059 -1.135632  NaN  NaN
2000-01-02 -0.173215  1.212112  0.119209 -1.044236  NaN  NaN
2000-01-03 -2.104569 -0.861849 -0.494929  1.071804  NaN  NaN
2000-01-04  7.000000  0.721555 -1.039575  0.271860  NaN  NaN
2000-01-05  0.567020 -0.424972  0.276232 -1.087401  NaN  NaN
2000-01-06  0.113648 -0.673690 -1.478427  0.524988  7.0  NaN
2000-01-07  0.577046  0.404705 -1.715002 -1.039268  NaN  NaN
2000-01-08 -1.157892 -0.370647 -1.344312  0.844885  NaN  NaN
2000-01-09       NaN       NaN       NaN       NaN  NaN  7.0

ブールインデックス付け#

もう一つの一般的な操作は、ブールベクトルを使用してデータをフィルタリングすることです。演算子は、or の場合は |and の場合は &not の場合は ~ です。これらは、括弧を使用してグループ化 する必要があります。なぜなら、デフォルトではPythonは df['A'] > 2 & df['B'] < 3 のような式を df['A'] > (2 & df['B']) < 3 として評価し、目的の評価順序は (df['A'] > 2) & (df['B'] < 3) だからです。

ブールベクトルでSeriesをインデックス付けすると、NumPy ndarrayとまったく同じように機能します。

In [162]: s = pd.Series(range(-3, 4))

In [163]: s
Out[163]: 
0   -3
1   -2
2   -1
3    0
4    1
5    2
6    3
dtype: int64

In [164]: s[s > 0]
Out[164]: 
4    1
5    2
6    3
dtype: int64

In [165]: s[(s < -1) | (s > 0.5)]
Out[165]: 
0   -3
1   -2
4    1
5    2
6    3
dtype: int64

In [166]: s[~(s < 0)]
Out[166]: 
3    0
4    1
5    2
6    3
dtype: int64

DataFrameのインデックスと同じ長さのブールベクトル(例えば、DataFrameの列の1つから派生したもの)を使用してDataFrameから行を選択できます。

In [167]: df[df['A'] > 0]
Out[167]: 
                   A         B         C         D    E   0
2000-01-04  7.000000  0.721555 -1.039575  0.271860  NaN NaN
2000-01-05  0.567020 -0.424972  0.276232 -1.087401  NaN NaN
2000-01-06  0.113648 -0.673690 -1.478427  0.524988  7.0 NaN
2000-01-07  0.577046  0.404705 -1.715002 -1.039268  NaN NaN

リスト内包表記とSeriesの map メソッドを使用して、より複雑な条件を作成することもできます。

In [168]: df2 = pd.DataFrame({'a': ['one', 'one', 'two', 'three', 'two', 'one', 'six'],
   .....:                     'b': ['x', 'y', 'y', 'x', 'y', 'x', 'x'],
   .....:                     'c': np.random.randn(7)})
   .....: 

# only want 'two' or 'three'
In [169]: criterion = df2['a'].map(lambda x: x.startswith('t'))

In [170]: df2[criterion]
Out[170]: 
       a  b         c
2    two  y  0.041290
3  three  x  0.361719
4    two  y -0.238075

# equivalent but slower
In [171]: df2[[x.startswith('t') for x in df2['a']]]
Out[171]: 
       a  b         c
2    two  y  0.041290
3  three  x  0.361719
4    two  y -0.238075

# Multiple criteria
In [172]: df2[criterion & (df2['b'] == 'x')]
Out[172]: 
       a  b         c
3  three  x  0.361719

ラベルによる選択位置による選択、および 高度なインデックス付け の選択メソッドを使用すると、ブールベクトルと他のインデックス式を組み合わせて複数の軸に沿って選択できます。

In [173]: df2.loc[criterion & (df2['b'] == 'x'), 'b':'c']
Out[173]: 
   b         c
3  x  0.361719

警告

iloc は2種類のブールインデックス付けをサポートしています。インデクサがブール Series の場合、エラーが発生します。例えば、次の例では df.iloc[s.values, 1] は問題ありません。ブールインデクサは配列です。しかし df.iloc[s, 1]ValueError を発生させます。

In [174]: df = pd.DataFrame([[1, 2], [3, 4], [5, 6]],
   .....:                   index=list('abc'),
   .....:                   columns=['A', 'B'])
   .....: 

In [175]: s = (df['A'] > 2)

In [176]: s
Out[176]: 
a    False
b     True
c     True
Name: A, dtype: bool

In [177]: df.loc[s, 'B']
Out[177]: 
b    4
c    6
Name: B, dtype: int64

In [178]: df.iloc[s.values, 1]
Out[178]: 
b    4
c    6
Name: B, dtype: int64

isinによるインデックス付け#

Seriesisin() メソッドについて考えてみましょう。これは、Series要素が渡されたリストに存在する場所で真となるブールベクトルを返します。これにより、1つ以上の列に目的の値を持つ行を選択できます。

In [179]: s = pd.Series(np.arange(5), index=np.arange(5)[::-1], dtype='int64')

In [180]: s
Out[180]: 
4    0
3    1
2    2
1    3
0    4
dtype: int64

In [181]: s.isin([2, 4, 6])
Out[181]: 
4    False
3    False
2     True
1    False
0     True
dtype: bool

In [182]: s[s.isin([2, 4, 6])]
Out[182]: 
2    2
0    4
dtype: int64

同じメソッドが Index オブジェクトでも利用でき、探しているラベルのどれが実際に存在するか分からない場合に役立ちます。

In [183]: s[s.index.isin([2, 4, 6])]
Out[183]: 
4    0
2    2
dtype: int64

# compare it to the following
In [184]: s.reindex([2, 4, 6])
Out[184]: 
2    2.0
4    0.0
6    NaN
dtype: float64

それに加えて、MultiIndex は、メンバーシップチェックに使用する別のレベルを選択することを可能にします。

In [185]: s_mi = pd.Series(np.arange(6),
   .....:                  index=pd.MultiIndex.from_product([[0, 1], ['a', 'b', 'c']]))
   .....: 

In [186]: s_mi
Out[186]: 
0  a    0
   b    1
   c    2
1  a    3
   b    4
   c    5
dtype: int64

In [187]: s_mi.iloc[s_mi.index.isin([(1, 'a'), (2, 'b'), (0, 'c')])]
Out[187]: 
0  c    2
1  a    3
dtype: int64

In [188]: s_mi.iloc[s_mi.index.isin(['a', 'c', 'e'], level=1)]
Out[188]: 
0  a    0
   c    2
1  a    3
   c    5
dtype: int64

DataFrameも isin() メソッドを持っています。isin を呼び出す際には、値のセットを配列またはdictとして渡します。値が配列の場合、isin は元のDataFrameと同じ形状のブールDataFrameを返し、要素が値のシーケンスにある場合はTrueとなります。

In [189]: df = pd.DataFrame({'vals': [1, 2, 3, 4], 'ids': ['a', 'b', 'f', 'n'],
   .....:                    'ids2': ['a', 'n', 'c', 'n']})
   .....: 

In [190]: values = ['a', 'b', 1, 3]

In [191]: df.isin(values)
Out[191]: 
    vals    ids   ids2
0   True   True   True
1  False   True  False
2   True  False  False
3  False  False  False

多くの場合、特定の列に特定の値を対応させたいと思うでしょう。値を、キーが列で、値がチェックしたい項目のリストである dict にするだけです。

In [192]: values = {'ids': ['a', 'b'], 'vals': [1, 3]}

In [193]: df.isin(values)
Out[193]: 
    vals    ids   ids2
0   True   True  False
1  False   True  False
2   True  False  False
3  False  False  False

値が元のDataFrameに ない ブール値のDataFrameを返すには、~ 演算子を使用します。

In [194]: values = {'ids': ['a', 'b'], 'vals': [1, 3]}

In [195]: ~df.isin(values)
Out[195]: 
    vals    ids  ids2
0  False  False  True
1   True  False  True
2  False   True  True
3   True   True  True

DataFrameの isinany() および all() メソッドと組み合わせて、与えられた基準を満たすデータのサブセットを迅速に選択します。各列が独自の基準を満たす行を選択するには、次のようにします。

In [196]: values = {'ids': ['a', 'b'], 'ids2': ['a', 'c'], 'vals': [1, 3]}

In [197]: row_mask = df.isin(values).all(1)

In [198]: df[row_mask]
Out[198]: 
   vals ids ids2
0     1   a    a

where() メソッドとマスキング#

ブールベクトルでSeriesから値を選択すると、一般的にデータのサブセットが返されます。選択の出力が元のデータと同じ形状であることを保証するには、SeriesDataFramewhere メソッドを使用できます。

選択した行のみを返すには

In [199]: s[s > 0]
Out[199]: 
3    1
2    2
1    3
0    4
dtype: int64

元のSeriesと同じ形状のSeriesを返すには

In [200]: s.where(s > 0)
Out[200]: 
4    NaN
3    1.0
2    2.0
1    3.0
0    4.0
dtype: float64

ブール条件でDataFrameから値を選択する場合も、入力データの形状が維持されます。where は実装として内部で使用されます。以下のコードは df.where(df < 0) と同等です。

In [201]: dates = pd.date_range('1/1/2000', periods=8)

In [202]: df = pd.DataFrame(np.random.randn(8, 4),
   .....:                   index=dates, columns=['A', 'B', 'C', 'D'])
   .....: 

In [203]: df[df < 0]
Out[203]: 
                   A         B         C         D
2000-01-01 -2.104139 -1.309525       NaN       NaN
2000-01-02 -0.352480       NaN -1.192319       NaN
2000-01-03 -0.864883       NaN -0.227870       NaN
2000-01-04       NaN -1.222082       NaN -1.233203
2000-01-05       NaN -0.605656 -1.169184       NaN
2000-01-06       NaN -0.948458       NaN -0.684718
2000-01-07 -2.670153 -0.114722       NaN -0.048048
2000-01-08       NaN       NaN -0.048788 -0.808838

さらに、where はオプションの other 引数を受け入れ、返されるコピーで条件がFalseの場合の値を置き換えます。

In [204]: df.where(df < 0, -df)
Out[204]: 
                   A         B         C         D
2000-01-01 -2.104139 -1.309525 -0.485855 -0.245166
2000-01-02 -0.352480 -0.390389 -1.192319 -1.655824
2000-01-03 -0.864883 -0.299674 -0.227870 -0.281059
2000-01-04 -0.846958 -1.222082 -0.600705 -1.233203
2000-01-05 -0.669692 -0.605656 -1.169184 -0.342416
2000-01-06 -0.868584 -0.948458 -2.297780 -0.684718
2000-01-07 -2.670153 -0.114722 -0.168904 -0.048048
2000-01-08 -0.801196 -1.392071 -0.048788 -0.808838

いくつかのブール条件に基づいて値を設定したい場合があります。これは次のように直感的に行うことができます。

In [205]: s2 = s.copy()

In [206]: s2[s2 < 0] = 0

In [207]: s2
Out[207]: 
4    0
3    1
2    2
1    3
0    4
dtype: int64

In [208]: df2 = df.copy()

In [209]: df2[df2 < 0] = 0

In [210]: df2
Out[210]: 
                   A         B         C         D
2000-01-01  0.000000  0.000000  0.485855  0.245166
2000-01-02  0.000000  0.390389  0.000000  1.655824
2000-01-03  0.000000  0.299674  0.000000  0.281059
2000-01-04  0.846958  0.000000  0.600705  0.000000
2000-01-05  0.669692  0.000000  0.000000  0.342416
2000-01-06  0.868584  0.000000  2.297780  0.000000
2000-01-07  0.000000  0.000000  0.168904  0.000000
2000-01-08  0.801196  1.392071  0.000000  0.000000

where は、データの変更されたコピーを返します。

DataFrame.where() のシグネチャは numpy.where() と異なります。おおよそ df1.where(m, df2)np.where(m, df1, df2) と同等です。

In [211]: df.where(df < 0, -df) == np.where(df < 0, df, -df)
Out[211]: 
               A     B     C     D
2000-01-01  True  True  True  True
2000-01-02  True  True  True  True
2000-01-03  True  True  True  True
2000-01-04  True  True  True  True
2000-01-05  True  True  True  True
2000-01-06  True  True  True  True
2000-01-07  True  True  True  True
2000-01-08  True  True  True  True

アライメント

さらに、where は入力ブール条件 (ndarray または DataFrame) を整列するため、設定を伴う部分選択が可能です。これは、.loc を介した部分設定 (軸ラベルではなく内容に対して) と同様です。

In [212]: df2 = df.copy()

In [213]: df2[df2[1:4] > 0] = 3

In [214]: df2
Out[214]: 
                   A         B         C         D
2000-01-01 -2.104139 -1.309525  0.485855  0.245166
2000-01-02 -0.352480  3.000000 -1.192319  3.000000
2000-01-03 -0.864883  3.000000 -0.227870  3.000000
2000-01-04  3.000000 -1.222082  3.000000 -1.233203
2000-01-05  0.669692 -0.605656 -1.169184  0.342416
2000-01-06  0.868584 -0.948458  2.297780 -0.684718
2000-01-07 -2.670153 -0.114722  0.168904 -0.048048
2000-01-08  0.801196  1.392071 -0.048788 -0.808838

Where は axis および level パラメータも受け入れ、where を実行する際の入力を整列させます。

In [215]: df2 = df.copy()

In [216]: df2.where(df2 > 0, df2['A'], axis='index')
Out[216]: 
                   A         B         C         D
2000-01-01 -2.104139 -2.104139  0.485855  0.245166
2000-01-02 -0.352480  0.390389 -0.352480  1.655824
2000-01-03 -0.864883  0.299674 -0.864883  0.281059
2000-01-04  0.846958  0.846958  0.600705  0.846958
2000-01-05  0.669692  0.669692  0.669692  0.342416
2000-01-06  0.868584  0.868584  2.297780  0.868584
2000-01-07 -2.670153 -2.670153  0.168904 -2.670153
2000-01-08  0.801196  1.392071  0.801196  0.801196

これは次のコードと同等ですが、(より高速です)。

In [217]: df2 = df.copy()

In [218]: df.apply(lambda x, y: x.where(x > 0, y), y=df['A'])
Out[218]: 
                   A         B         C         D
2000-01-01 -2.104139 -2.104139  0.485855  0.245166
2000-01-02 -0.352480  0.390389 -0.352480  1.655824
2000-01-03 -0.864883  0.299674 -0.864883  0.281059
2000-01-04  0.846958  0.846958  0.600705  0.846958
2000-01-05  0.669692  0.669692  0.669692  0.342416
2000-01-06  0.868584  0.868584  2.297780  0.868584
2000-01-07 -2.670153 -2.670153  0.168904 -2.670153
2000-01-08  0.801196  1.392071  0.801196  0.801196

where は、条件および other 引数として呼び出し可能オブジェクトを受け入れることができます。この関数は、1つの引数(呼び出し元のSeriesまたはDataFrame)を取り、条件および other 引数として有効な出力を返す必要があります。

In [219]: df3 = pd.DataFrame({'A': [1, 2, 3],
   .....:                     'B': [4, 5, 6],
   .....:                     'C': [7, 8, 9]})
   .....: 

In [220]: df3.where(lambda x: x > 4, lambda x: x + 10)
Out[220]: 
    A   B  C
0  11  14  7
1  12   5  8
2  13   6  9

マスク#

mask()where の逆ブール演算です。

In [221]: s.mask(s >= 0)
Out[221]: 
4   NaN
3   NaN
2   NaN
1   NaN
0   NaN
dtype: float64

In [222]: df.mask(df >= 0)
Out[222]: 
                   A         B         C         D
2000-01-01 -2.104139 -1.309525       NaN       NaN
2000-01-02 -0.352480       NaN -1.192319       NaN
2000-01-03 -0.864883       NaN -0.227870       NaN
2000-01-04       NaN -1.222082       NaN -1.233203
2000-01-05       NaN -0.605656 -1.169184       NaN
2000-01-06       NaN -0.948458       NaN -0.684718
2000-01-07 -2.670153 -0.114722       NaN -0.048048
2000-01-08       NaN       NaN -0.048788 -0.808838

numpy() を使用した条件付きでの拡大による設定#

where() の代わりに、numpy.where() を使用する方法もあります。これを新しい列の設定と組み合わせると、値が条件付きで決定されるDataFrameを拡大するために使用できます。

以下のDataFrameで2つの選択肢があるとします。2番目の列に 'Z' がある場合に、新しい列 'color' を 'green' に設定したいとします。次のようにすることができます。

In [223]: df = pd.DataFrame({'col1': list('ABBC'), 'col2': list('ZZXY')})

In [224]: df['color'] = np.where(df['col2'] == 'Z', 'green', 'red')

In [225]: df
Out[225]: 
  col1 col2  color
0    A    Z  green
1    B    Z  green
2    B    X    red
3    C    Y    red

複数の条件がある場合は、numpy.select() を使用してそれを実現できます。例えば、3つの条件に対応する3つの色の選択肢があり、4番目の色がフォールバックとしてある場合、次のようにすることができます。

In [226]: conditions = [
   .....:     (df['col2'] == 'Z') & (df['col1'] == 'A'),
   .....:     (df['col2'] == 'Z') & (df['col1'] == 'B'),
   .....:     (df['col1'] == 'B')
   .....: ]
   .....: 

In [227]: choices = ['yellow', 'blue', 'purple']

In [228]: df['color'] = np.select(conditions, choices, default='black')

In [229]: df
Out[229]: 
  col1 col2   color
0    A    Z  yellow
1    B    Z    blue
2    B    X  purple
3    C    Y   black

query() メソッド#

DataFrame オブジェクトには、式を使用して選択を可能にする query() メソッドがあります。

b の値が列 ac の値の間にあるフレームの値を取得できます。例えば

In [230]: n = 10

In [231]: df = pd.DataFrame(np.random.rand(n, 3), columns=list('abc'))

In [232]: df
Out[232]: 
          a         b         c
0  0.438921  0.118680  0.863670
1  0.138138  0.577363  0.686602
2  0.595307  0.564592  0.520630
3  0.913052  0.926075  0.616184
4  0.078718  0.854477  0.898725
5  0.076404  0.523211  0.591538
6  0.792342  0.216974  0.564056
7  0.397890  0.454131  0.915716
8  0.074315  0.437913  0.019794
9  0.559209  0.502065  0.026437

# pure python
In [233]: df[(df['a'] < df['b']) & (df['b'] < df['c'])]
Out[233]: 
          a         b         c
1  0.138138  0.577363  0.686602
4  0.078718  0.854477  0.898725
5  0.076404  0.523211  0.591538
7  0.397890  0.454131  0.915716

# query
In [234]: df.query('(a < b) & (b < c)')
Out[234]: 
          a         b         c
1  0.138138  0.577363  0.686602
4  0.078718  0.854477  0.898725
5  0.076404  0.523211  0.591538
7  0.397890  0.454131  0.915716

同じことを行いますが、名前が a の列がない場合は、名前付きインデックスにフォールバックします。

In [235]: df = pd.DataFrame(np.random.randint(n / 2, size=(n, 2)), columns=list('bc'))

In [236]: df.index.name = 'a'

In [237]: df
Out[237]: 
   b  c
a      
0  0  4
1  0  1
2  3  4
3  4  3
4  1  4
5  0  3
6  0  1
7  3  4
8  2  3
9  1  1

In [238]: df.query('a < b and b < c')
Out[238]: 
   b  c
a      
2  3  4

代わりにインデックスに名前を付けたくない場合、または名前を付けられない場合は、クエリ式で index という名前を使用できます。

In [239]: df = pd.DataFrame(np.random.randint(n, size=(n, 2)), columns=list('bc'))

In [240]: df
Out[240]: 
   b  c
0  3  1
1  3  0
2  5  6
3  5  2
4  7  4
5  0  1
6  2  5
7  0  1
8  6  0
9  7  9

In [241]: df.query('index < b < c')
Out[241]: 
   b  c
2  5  6

インデックスの名前が列名と重複する場合、列名が優先されます。例:

In [242]: df = pd.DataFrame({'a': np.random.randint(5, size=5)})

In [243]: df.index.name = 'a'

In [244]: df.query('a > 2')  # uses the column 'a', not the index
Out[244]: 
   a
a   
1  3
3  3

特別な識別子「index」を使用することで、クエリ式でインデックスを依然として使用できます。

In [245]: df.query('index > 2')
Out[245]: 
   a
a   
3  3
4  2

何らかの理由で index という名前の列がある場合、インデックスを ilevel_0 としても参照できますが、この時点では列名をあいまいさの少ないものに変更することを検討すべきです。

MultiIndex query() 構文#

MultiIndex を持つ DataFrame のレベルを、フレーム内の列であるかのように使用することもできます。

In [246]: n = 10

In [247]: colors = np.random.choice(['red', 'green'], size=n)

In [248]: foods = np.random.choice(['eggs', 'ham'], size=n)

In [249]: colors
Out[249]: 
array(['red', 'red', 'red', 'green', 'green', 'green', 'green', 'green',
       'green', 'green'], dtype='<U5')

In [250]: foods
Out[250]: 
array(['ham', 'ham', 'eggs', 'eggs', 'eggs', 'ham', 'ham', 'eggs', 'eggs',
       'eggs'], dtype='<U4')

In [251]: index = pd.MultiIndex.from_arrays([colors, foods], names=['color', 'food'])

In [252]: df = pd.DataFrame(np.random.randn(n, 2), index=index)

In [253]: df
Out[253]: 
                   0         1
color food                    
red   ham   0.194889 -0.381994
      ham   0.318587  2.089075
      eggs -0.728293 -0.090255
green eggs -0.748199  1.318931
      eggs -2.029766  0.792652
      ham   0.461007 -0.542749
      ham  -0.305384 -0.479195
      eggs  0.095031 -0.270099
      eggs -0.707140 -0.773882
      eggs  0.229453  0.304418

In [254]: df.query('color == "red"')
Out[254]: 
                   0         1
color food                    
red   ham   0.194889 -0.381994
      ham   0.318587  2.089075
      eggs -0.728293 -0.090255

MultiIndex のレベルに名前がない場合、特別な名前を使用して参照できます。

In [255]: df.index.names = [None, None]

In [256]: df
Out[256]: 
                   0         1
red   ham   0.194889 -0.381994
      ham   0.318587  2.089075
      eggs -0.728293 -0.090255
green eggs -0.748199  1.318931
      eggs -2.029766  0.792652
      ham   0.461007 -0.542749
      ham  -0.305384 -0.479195
      eggs  0.095031 -0.270099
      eggs -0.707140 -0.773882
      eggs  0.229453  0.304418

In [257]: df.query('ilevel_0 == "red"')
Out[257]: 
                 0         1
red ham   0.194889 -0.381994
    ham   0.318587  2.089075
    eggs -0.728293 -0.090255

慣例は ilevel_0 であり、これは index の0番目のレベルを意味する「インデックスレベル0」を意味します。

query() のユースケース#

query() のユースケースは、共通の列名 (またはインデックスレベル/名前) のサブセットを持つ DataFrame オブジェクトのコレクションがある場合です。クエリするフレームを指定 せずに 、同じクエリを両方のフレームに渡すことができます。

In [258]: df = pd.DataFrame(np.random.rand(n, 3), columns=list('abc'))

In [259]: df
Out[259]: 
          a         b         c
0  0.224283  0.736107  0.139168
1  0.302827  0.657803  0.713897
2  0.611185  0.136624  0.984960
3  0.195246  0.123436  0.627712
4  0.618673  0.371660  0.047902
5  0.480088  0.062993  0.185760
6  0.568018  0.483467  0.445289
7  0.309040  0.274580  0.587101
8  0.258993  0.477769  0.370255
9  0.550459  0.840870  0.304611

In [260]: df2 = pd.DataFrame(np.random.rand(n + 2, 3), columns=df.columns)

In [261]: df2
Out[261]: 
           a         b         c
0   0.357579  0.229800  0.596001
1   0.309059  0.957923  0.965663
2   0.123102  0.336914  0.318616
3   0.526506  0.323321  0.860813
4   0.518736  0.486514  0.384724
5   0.190804  0.505723  0.614533
6   0.891939  0.623977  0.676639
7   0.480559  0.378528  0.460858
8   0.420223  0.136404  0.141295
9   0.732206  0.419540  0.604675
10  0.604466  0.848974  0.896165
11  0.589168  0.920046  0.732716

In [262]: expr = '0.0 <= a <= c <= 0.5'

In [263]: map(lambda frame: frame.query(expr), [df, df2])
Out[263]: <map at 0x7f946de71e40>

query() Python対pandas構文比較#

完全なnumpyライクな構文

In [264]: df = pd.DataFrame(np.random.randint(n, size=(n, 3)), columns=list('abc'))

In [265]: df
Out[265]: 
   a  b  c
0  7  8  9
1  1  0  7
2  2  7  2
3  6  2  2
4  2  6  3
5  3  8  2
6  1  7  2
7  5  1  5
8  9  8  0
9  1  5  0

In [266]: df.query('(a < b) & (b < c)')
Out[266]: 
   a  b  c
0  7  8  9

In [267]: df[(df['a'] < df['b']) & (df['b'] < df['c'])]
Out[267]: 
   a  b  c
0  7  8  9

括弧を削除することで少し見やすくなります(比較演算子は & および | よりも強く結合します)。

In [268]: df.query('a < b & b < c')
Out[268]: 
   a  b  c
0  7  8  9

記号の代わりに英語を使用する

In [269]: df.query('a < b and b < c')
Out[269]: 
   a  b  c
0  7  8  9

紙に書くのとかなり近い。

In [270]: df.query('a < b < c')
Out[270]: 
   a  b  c
0  7  8  9

innot in 演算子#

query() は、Pythonの in および not in 比較演算子の特殊な使用もサポートしており、Series または DataFrameisin メソッドを呼び出すための簡潔な構文を提供します。

# get all rows where columns "a" and "b" have overlapping values
In [271]: df = pd.DataFrame({'a': list('aabbccddeeff'), 'b': list('aaaabbbbcccc'),
   .....:                    'c': np.random.randint(5, size=12),
   .....:                    'd': np.random.randint(9, size=12)})
   .....: 

In [272]: df
Out[272]: 
    a  b  c  d
0   a  a  2  6
1   a  a  4  7
2   b  a  1  6
3   b  a  2  1
4   c  b  3  6
5   c  b  0  2
6   d  b  3  3
7   d  b  2  1
8   e  c  4  3
9   e  c  2  0
10  f  c  0  6
11  f  c  1  2

In [273]: df.query('a in b')
Out[273]: 
   a  b  c  d
0  a  a  2  6
1  a  a  4  7
2  b  a  1  6
3  b  a  2  1
4  c  b  3  6
5  c  b  0  2

# How you'd do it in pure Python
In [274]: df[df['a'].isin(df['b'])]
Out[274]: 
   a  b  c  d
0  a  a  2  6
1  a  a  4  7
2  b  a  1  6
3  b  a  2  1
4  c  b  3  6
5  c  b  0  2

In [275]: df.query('a not in b')
Out[275]: 
    a  b  c  d
6   d  b  3  3
7   d  b  2  1
8   e  c  4  3
9   e  c  2  0
10  f  c  0  6
11  f  c  1  2

# pure Python
In [276]: df[~df['a'].isin(df['b'])]
Out[276]: 
    a  b  c  d
6   d  b  3  3
7   d  b  2  1
8   e  c  4  3
9   e  c  2  0
10  f  c  0  6
11  f  c  1  2

これを他の式と組み合わせることで、非常に簡潔なクエリを作成できます。

# rows where cols a and b have overlapping values
# and col c's values are less than col d's
In [277]: df.query('a in b and c < d')
Out[277]: 
   a  b  c  d
0  a  a  2  6
1  a  a  4  7
2  b  a  1  6
4  c  b  3  6
5  c  b  0  2

# pure Python
In [278]: df[df['b'].isin(df['a']) & (df['c'] < df['d'])]
Out[278]: 
    a  b  c  d
0   a  a  2  6
1   a  a  4  7
2   b  a  1  6
4   c  b  3  6
5   c  b  0  2
10  f  c  0  6
11  f  c  1  2

in および not innumexpr にこの操作の同等物がないため、Pythonで評価されることに注意してください。ただし、**in/not in 式自体のみ** が純粋なPythonで評価されます。例えば、以下の式では、

df.query('a in b + c + d')

(b + c + d)numexpr によって評価され、その後 in 演算は純粋なPythonで評価されます。一般的に、numexpr を使用して評価できる操作はすべて評価されます。

== 演算子と list オブジェクトの特殊な使用#

列と値の list==/!= で比較することは、in/not in と同様に機能します。

In [279]: df.query('b == ["a", "b", "c"]')
Out[279]: 
    a  b  c  d
0   a  a  2  6
1   a  a  4  7
2   b  a  1  6
3   b  a  2  1
4   c  b  3  6
5   c  b  0  2
6   d  b  3  3
7   d  b  2  1
8   e  c  4  3
9   e  c  2  0
10  f  c  0  6
11  f  c  1  2

# pure Python
In [280]: df[df['b'].isin(["a", "b", "c"])]
Out[280]: 
    a  b  c  d
0   a  a  2  6
1   a  a  4  7
2   b  a  1  6
3   b  a  2  1
4   c  b  3  6
5   c  b  0  2
6   d  b  3  3
7   d  b  2  1
8   e  c  4  3
9   e  c  2  0
10  f  c  0  6
11  f  c  1  2

In [281]: df.query('c == [1, 2]')
Out[281]: 
    a  b  c  d
0   a  a  2  6
2   b  a  1  6
3   b  a  2  1
7   d  b  2  1
9   e  c  2  0
11  f  c  1  2

In [282]: df.query('c != [1, 2]')
Out[282]: 
    a  b  c  d
1   a  a  4  7
4   c  b  3  6
5   c  b  0  2
6   d  b  3  3
8   e  c  4  3
10  f  c  0  6

# using in/not in
In [283]: df.query('[1, 2] in c')
Out[283]: 
    a  b  c  d
0   a  a  2  6
2   b  a  1  6
3   b  a  2  1
7   d  b  2  1
9   e  c  2  0
11  f  c  1  2

In [284]: df.query('[1, 2] not in c')
Out[284]: 
    a  b  c  d
1   a  a  4  7
4   c  b  3  6
5   c  b  0  2
6   d  b  3  3
8   e  c  4  3
10  f  c  0  6

# pure Python
In [285]: df[df['c'].isin([1, 2])]
Out[285]: 
    a  b  c  d
0   a  a  2  6
2   b  a  1  6
3   b  a  2  1
7   d  b  2  1
9   e  c  2  0
11  f  c  1  2

ブール演算子#

not という単語または ~ 演算子でブール式を否定できます。

In [286]: df = pd.DataFrame(np.random.rand(n, 3), columns=list('abc'))

In [287]: df['bools'] = np.random.rand(len(df)) > 0.5

In [288]: df.query('~bools')
Out[288]: 
          a         b         c  bools
2  0.697753  0.212799  0.329209  False
7  0.275396  0.691034  0.826619  False
8  0.190649  0.558748  0.262467  False

In [289]: df.query('not bools')
Out[289]: 
          a         b         c  bools
2  0.697753  0.212799  0.329209  False
7  0.275396  0.691034  0.826619  False
8  0.190649  0.558748  0.262467  False

In [290]: df.query('not bools') == df[~df['bools']]
Out[290]: 
      a     b     c  bools
2  True  True  True   True
7  True  True  True   True
8  True  True  True   True

もちろん、式は任意の複雑さを持つことができます。

# short query syntax
In [291]: shorter = df.query('a < b < c and (not bools) or bools > 2')

# equivalent in pure Python
In [292]: longer = df[(df['a'] < df['b'])
   .....:             & (df['b'] < df['c'])
   .....:             & (~df['bools'])
   .....:             | (df['bools'] > 2)]
   .....: 

In [293]: shorter
Out[293]: 
          a         b         c  bools
7  0.275396  0.691034  0.826619  False

In [294]: longer
Out[294]: 
          a         b         c  bools
7  0.275396  0.691034  0.826619  False

In [295]: shorter == longer
Out[295]: 
      a     b     c  bools
7  True  True  True   True

query() のパフォーマンス#

numexpr を使用する DataFrame.query() は、大規模なフレームではPythonよりもわずかに高速です。

../_images/query-perf.png

DataFrame.query()numexpr エンジンを使用するパフォーマンス上のメリットは、フレームが約100,000行を超える場合にのみ見られます。

このプロットは、それぞれ numpy.random.randn() を使用して生成された浮動小数点値を含む3つの列を持つ DataFrame を使用して作成されました。

In [296]: df = pd.DataFrame(np.random.randn(8, 4),
   .....:                   index=dates, columns=['A', 'B', 'C', 'D'])
   .....: 

In [297]: df2 = df.copy()

重複データ#

DataFrame内の重複する行を特定して削除したい場合、duplicateddrop_duplicates の2つのメソッドが役立ちます。それぞれ、重複する行を特定するために使用する列を引数として取ります。

  • duplicated は、行数と同じ長さのブールベクトルを返し、行が重複しているかどうかを示します。

  • drop_duplicates は重複する行を削除します。

デフォルトでは、重複するセットの最初の行がユニークと見なされますが、各メソッドには保持するターゲットを指定する keep パラメータがあります。

  • keep='first' (デフォルト): 最初の出現を除く重複をマーク/削除します。

  • keep='last': 最後の出現を除く重複をマーク/削除します。

  • keep=False: すべての重複をマーク/削除します。

In [298]: df2 = pd.DataFrame({'a': ['one', 'one', 'two', 'two', 'two', 'three', 'four'],
   .....:                     'b': ['x', 'y', 'x', 'y', 'x', 'x', 'x'],
   .....:                     'c': np.random.randn(7)})
   .....: 

In [299]: df2
Out[299]: 
       a  b         c
0    one  x -1.067137
1    one  y  0.309500
2    two  x -0.211056
3    two  y -1.842023
4    two  x -0.390820
5  three  x -1.964475
6   four  x  1.298329

In [300]: df2.duplicated('a')
Out[300]: 
0    False
1     True
2    False
3     True
4     True
5    False
6    False
dtype: bool

In [301]: df2.duplicated('a', keep='last')
Out[301]: 
0     True
1    False
2     True
3     True
4    False
5    False
6    False
dtype: bool

In [302]: df2.duplicated('a', keep=False)
Out[302]: 
0     True
1     True
2     True
3     True
4     True
5    False
6    False
dtype: bool

In [303]: df2.drop_duplicates('a')
Out[303]: 
       a  b         c
0    one  x -1.067137
2    two  x -0.211056
5  three  x -1.964475
6   four  x  1.298329

In [304]: df2.drop_duplicates('a', keep='last')
Out[304]: 
       a  b         c
1    one  y  0.309500
4    two  x -0.390820
5  three  x -1.964475
6   four  x  1.298329

In [305]: df2.drop_duplicates('a', keep=False)
Out[305]: 
       a  b         c
5  three  x -1.964475
6   four  x  1.298329

また、重複を識別するために列のリストを渡すこともできます。

In [306]: df2.duplicated(['a', 'b'])
Out[306]: 
0    False
1    False
2    False
3    False
4     True
5    False
6    False
dtype: bool

In [307]: df2.drop_duplicates(['a', 'b'])
Out[307]: 
       a  b         c
0    one  x -1.067137
1    one  y  0.309500
2    two  x -0.211056
3    two  y -1.842023
5  three  x -1.964475
6   four  x  1.298329

インデックス値による重複を削除するには、Index.duplicated を使用し、次にスライスを実行します。keep パラメータには同じオプションセットが利用可能です。

In [308]: df3 = pd.DataFrame({'a': np.arange(6),
   .....:                     'b': np.random.randn(6)},
   .....:                    index=['a', 'a', 'b', 'c', 'b', 'a'])
   .....: 

In [309]: df3
Out[309]: 
   a         b
a  0  1.440455
a  1  2.456086
b  2  1.038402
c  3 -0.894409
b  4  0.683536
a  5  3.082764

In [310]: df3.index.duplicated()
Out[310]: array([False,  True, False, False,  True,  True])

In [311]: df3[~df3.index.duplicated()]
Out[311]: 
   a         b
a  0  1.440455
b  2  1.038402
c  3 -0.894409

In [312]: df3[~df3.index.duplicated(keep='last')]
Out[312]: 
   a         b
c  3 -0.894409
b  4  0.683536
a  5  3.082764

In [313]: df3[~df3.index.duplicated(keep=False)]
Out[313]: 
   a         b
c  3 -0.894409

辞書ライクな get() メソッド#

SeriesとDataFrameのそれぞれに、デフォルト値を返すことができる get メソッドがあります。

In [314]: s = pd.Series([1, 2, 3], index=['a', 'b', 'c'])

In [315]: s.get('a')  # equivalent to s['a']
Out[315]: 1

In [316]: s.get('x', default=-1)
Out[316]: -1

インデックス/列ラベルによる値の検索#

行ラベルと列ラベルのシーケンスが与えられたときに値のセットを抽出したい場合があります。これは pandas.factorize とNumPyインデックス付けで実現できます。例えば

In [317]: df = pd.DataFrame({'col': ["A", "A", "B", "B"],
   .....:                    'A': [80, 23, np.nan, 22],
   .....:                    'B': [80, 55, 76, 67]})
   .....: 

In [318]: df
Out[318]: 
  col     A   B
0   A  80.0  80
1   A  23.0  55
2   B   NaN  76
3   B  22.0  67

In [319]: idx, cols = pd.factorize(df['col'])

In [320]: df.reindex(cols, axis=1).to_numpy()[np.arange(len(df)), idx]
Out[320]: array([80., 23., 76., 67.])

以前は、これは専用の DataFrame.lookup メソッドで実現できましたが、これはバージョン1.2.0で非推奨となり、バージョン2.0.0で削除されました。

インデックスオブジェクト#

pandas Index クラスとそのサブクラスは、順序付きマルチセット を実装していると見なすことができます。重複は許可されます。

Index は、ルックアップ、データアライメント、再インデックス付けに必要なインフラストラクチャも提供します。Index を直接作成する最も簡単な方法は、list または他のシーケンスを Index に渡すことです。

In [321]: index = pd.Index(['e', 'd', 'a', 'b'])

In [322]: index
Out[322]: Index(['e', 'd', 'a', 'b'], dtype='object')

In [323]: 'd' in index
Out[323]: True

または数字を使用します

In [324]: index = pd.Index([1, 5, 12])

In [325]: index
Out[325]: Index([1, 5, 12], dtype='int64')

In [326]: 5 in index
Out[326]: True

dtypeが指定されていない場合、Index はデータからdtypeを推測しようとします。Index をインスタンス化する際に明示的なdtypeを与えることも可能です。

In [327]: index = pd.Index(['e', 'd', 'a', 'b'], dtype="string")

In [328]: index
Out[328]: Index(['e', 'd', 'a', 'b'], dtype='string')

In [329]: index = pd.Index([1, 5, 12], dtype="int8")

In [330]: index
Out[330]: Index([1, 5, 12], dtype='int8')

In [331]: index = pd.Index([1, 5, 12], dtype="float32")

In [332]: index
Out[332]: Index([1.0, 5.0, 12.0], dtype='float32')

インデックスに保存する name を渡すこともできます。

In [333]: index = pd.Index(['e', 'd', 'a', 'b'], name='something')

In [334]: index.name
Out[334]: 'something'

名前が設定されている場合、コンソール表示に表示されます。

In [335]: index = pd.Index(list(range(5)), name='rows')

In [336]: columns = pd.Index(['A', 'B', 'C'], name='cols')

In [337]: df = pd.DataFrame(np.random.randn(5, 3), index=index, columns=columns)

In [338]: df
Out[338]: 
cols         A         B         C
rows                              
0     1.295989 -1.051694  1.340429
1    -2.366110  0.428241  0.387275
2     0.433306  0.929548  0.278094
3     2.154730 -0.315628  0.264223
4     1.126818  1.132290 -0.353310

In [339]: df['A']
Out[339]: 
rows
0    1.295989
1   -2.366110
2    0.433306
3    2.154730
4    1.126818
Name: A, dtype: float64

メタデータの設定#

インデックスは「ほとんど変更不可能」ですが、name 属性を設定および変更することは可能です。renameset_names を使用してこれらの属性を直接設定でき、デフォルトではコピーを返します。

MultiIndexの使用法については、高度なインデックス付け を参照してください。

In [340]: ind = pd.Index([1, 2, 3])

In [341]: ind.rename("apple")
Out[341]: Index([1, 2, 3], dtype='int64', name='apple')

In [342]: ind
Out[342]: Index([1, 2, 3], dtype='int64')

In [343]: ind = ind.set_names(["apple"])

In [344]: ind.name = "bob"

In [345]: ind
Out[345]: Index([1, 2, 3], dtype='int64', name='bob')

set_namesset_levels、および set_codes もオプションの level 引数を受け入れます。

In [346]: index = pd.MultiIndex.from_product([range(3), ['one', 'two']], names=['first', 'second'])

In [347]: index
Out[347]: 
MultiIndex([(0, 'one'),
            (0, 'two'),
            (1, 'one'),
            (1, 'two'),
            (2, 'one'),
            (2, 'two')],
           names=['first', 'second'])

In [348]: index.levels[1]
Out[348]: Index(['one', 'two'], dtype='object', name='second')

In [349]: index.set_levels(["a", "b"], level=1)
Out[349]: 
MultiIndex([(0, 'a'),
            (0, 'b'),
            (1, 'a'),
            (1, 'b'),
            (2, 'a'),
            (2, 'b')],
           names=['first', 'second'])

Indexオブジェクトに対する集合演算#

2つの主な操作は unionintersection です。.difference() メソッドを介して差分が提供されます。

In [350]: a = pd.Index(['c', 'b', 'a'])

In [351]: b = pd.Index(['c', 'e', 'd'])

In [352]: a.difference(b)
Out[352]: Index(['a', 'b'], dtype='object')

symmetric_difference 演算も利用可能で、これは idx1 または idx2 のいずれかに表示されるが、両方には表示されない要素を返します。これは、重複が削除された idx1.difference(idx2).union(idx2.difference(idx1)) によって作成されたインデックスと同等です。

In [353]: idx1 = pd.Index([1, 2, 3, 4])

In [354]: idx2 = pd.Index([2, 3, 4, 5])

In [355]: idx1.symmetric_difference(idx2)
Out[355]: Index([1, 5], dtype='int64')

集合演算から得られるインデックスは昇順にソートされます。

異なるdtypeを持つインデックス間で Index.union() を実行する場合、インデックスは共通のdtypeにキャストされる必要があります。通常、常にではありませんが、これはobject dtypeです。例外は、整数データと浮動小数点データの間でunionを実行する場合です。この場合、整数値は浮動小数点に変換されます。

In [356]: idx1 = pd.Index([0, 1, 2])

In [357]: idx2 = pd.Index([0.5, 1.5])

In [358]: idx1.union(idx2)
Out[358]: Index([0.0, 0.5, 1.0, 1.5, 2.0], dtype='float64')

欠損値#

重要

Index は欠損値(NaN)を保持できますが、予期しない結果を望まない場合は避けるべきです。例えば、一部の操作は欠損値を暗黙的に除外します。

Index.fillna は、欠損値を指定されたスカラ値で埋めます。

In [359]: idx1 = pd.Index([1, np.nan, 3, 4])

In [360]: idx1
Out[360]: Index([1.0, nan, 3.0, 4.0], dtype='float64')

In [361]: idx1.fillna(2)
Out[361]: Index([1.0, 2.0, 3.0, 4.0], dtype='float64')

In [362]: idx2 = pd.DatetimeIndex([pd.Timestamp('2011-01-01'),
   .....:                          pd.NaT,
   .....:                          pd.Timestamp('2011-01-03')])
   .....: 

In [363]: idx2
Out[363]: DatetimeIndex(['2011-01-01', 'NaT', '2011-01-03'], dtype='datetime64[ns]', freq=None)

In [364]: idx2.fillna(pd.Timestamp('2011-01-02'))
Out[364]: DatetimeIndex(['2011-01-01', '2011-01-02', '2011-01-03'], dtype='datetime64[ns]', freq=None)

インデックスの設定/リセット#

DataFrameにデータセットを読み込んだり作成したりした後で、インデックスを追加したい場合があります。いくつかの異なる方法があります。

インデックスを設定#

DataFrameには set_index() メソッドがあり、列名(通常の Index の場合)または列名のリスト(MultiIndex の場合)を受け取ります。新しい、再インデックスされたDataFrameを作成するには、

In [365]: data = pd.DataFrame({'a': ['bar', 'bar', 'foo', 'foo'],
   .....:                      'b': ['one', 'two', 'one', 'two'],
   .....:                      'c': ['z', 'y', 'x', 'w'],
   .....:                      'd': [1., 2., 3, 4]})
   .....: 

In [366]: data
Out[366]: 
     a    b  c    d
0  bar  one  z  1.0
1  bar  two  y  2.0
2  foo  one  x  3.0
3  foo  two  w  4.0

In [367]: indexed1 = data.set_index('c')

In [368]: indexed1
Out[368]: 
     a    b    d
c               
z  bar  one  1.0
y  bar  two  2.0
x  foo  one  3.0
w  foo  two  4.0

In [369]: indexed2 = data.set_index(['a', 'b'])

In [370]: indexed2
Out[370]: 
         c    d
a   b          
bar one  z  1.0
    two  y  2.0
foo one  x  3.0
    two  w  4.0

append キーワードオプションを使用すると、既存のインデックスを保持し、指定された列をMultiIndexに追加できます。

In [371]: frame = data.set_index('c', drop=False)

In [372]: frame = frame.set_index(['a', 'b'], append=True)

In [373]: frame
Out[373]: 
           c    d
c a   b          
z bar one  z  1.0
y bar two  y  2.0
x foo one  x  3.0
w foo two  w  4.0

set_index の他のオプションを使用すると、インデックス列を削除しないことができます。

In [374]: data.set_index('c', drop=False)
Out[374]: 
     a    b  c    d
c                  
z  bar  one  z  1.0
y  bar  two  y  2.0
x  foo  one  x  3.0
w  foo  two  w  4.0

インデックスのリセット#

利便性のために、DataFrameには reset_index() と呼ばれる新しい関数があり、インデックス値をDataFrameの列に転送し、単純な整数インデックスを設定します。これは set_index() の逆操作です。

In [375]: data
Out[375]: 
     a    b  c    d
0  bar  one  z  1.0
1  bar  two  y  2.0
2  foo  one  x  3.0
3  foo  two  w  4.0

In [376]: data.reset_index()
Out[376]: 
   index    a    b  c    d
0      0  bar  one  z  1.0
1      1  bar  two  y  2.0
2      2  foo  one  x  3.0
3      3  foo  two  w  4.0

出力はSQLテーブルやレコード配列に似ています。インデックスから派生した列の名前は、names 属性に格納されているものです。

level キーワードを使用して、インデックスの一部のみを削除できます。

In [377]: frame
Out[377]: 
           c    d
c a   b          
z bar one  z  1.0
y bar two  y  2.0
x foo one  x  3.0
w foo two  w  4.0

In [378]: frame.reset_index(level=1)
Out[378]: 
         a  c    d
c b               
z one  bar  z  1.0
y two  bar  y  2.0
x one  foo  x  3.0
w two  foo  w  4.0

reset_index はオプションのパラメータ drop を受け入れます。これがTrueの場合、インデックス値をDataFrameの列に配置する代わりに、インデックスを単純に破棄します。

アドホックなインデックスの追加#

index 属性にカスタムインデックスを割り当てることができます。

In [379]: df_idx = pd.DataFrame(range(4))

In [380]: df_idx.index = pd.Index([10, 20, 30, 40], name="a")

In [381]: df_idx
Out[381]: 
    0
a    
10  0
20  1
30  2
40  3

ビューを返すかコピーを返すか#

警告

Copy-on-Write はpandas 3.0で新しいデフォルトになります。これは、チェインインデックスは決して機能しないことを意味します。結果として、SettingWithCopyWarning はもう不要になります。詳細については、このセクション を参照してください。pandas 3.0が利用可能になる前に、以下の設定でCopy-on-Writeを有効にして改善を活用することをお勧めします。

` pd.options.mode.copy_on_write = True `

たとえpandas 3.0がまだ利用可能でなくても。

pandasオブジェクトに値を設定する際、chained indexing と呼ばれるものを避けるために注意が必要です。以下に例を示します。

In [382]: dfmi = pd.DataFrame([list('abcd'),
   .....:                      list('efgh'),
   .....:                      list('ijkl'),
   .....:                      list('mnop')],
   .....:                     columns=pd.MultiIndex.from_product([['one', 'two'],
   .....:                                                         ['first', 'second']]))
   .....: 

In [383]: dfmi
Out[383]: 
    one          two       
  first second first second
0     a      b     c      d
1     e      f     g      h
2     i      j     k      l
3     m      n     o      p

これら2つのアクセス方法を比較してください。

In [384]: dfmi['one']['second']
Out[384]: 
0    b
1    f
2    j
3    n
Name: second, dtype: object
In [385]: dfmi.loc[:, ('one', 'second')]
Out[385]: 
0    b
1    f
2    j
3    n
Name: (one, second), dtype: object

どちらも同じ結果を生成しますが、どちらを使用すべきでしょうか?これらの操作の順序と、なぜメソッド2(.loc)がメソッド1(連結された [])よりもはるかに好まれるのかを理解することは有益です。

dfmi['one'] は列の最初のレベルを選択し、単一インデックスのDataFrameを返します。次に、別のPython操作 dfmi_with_one['second']'second' でインデックス付けされたSeriesを選択します。これは変数 dfmi_with_one で示されています。なぜなら、pandasはこれらの操作を個別のイベント、つまり __getitem__ への個別の呼び出しとして認識するため、線形操作として扱わなければならず、連続して発生するからです。

これに対し、df.loc[:,('one','second')] は、(slice(None),('one','second')) のネストされたタプルを __getitem__ への単一の呼び出しに渡します。これにより、pandasはこれを単一のエンティティとして扱うことができます。さらに、この操作順序は 大幅に 高速になる可能性があり、必要に応じて 両方の 軸をインデックス付けすることができます。

チェインインデックスを使用すると割り当てが失敗するのはなぜですか?#

警告

Copy-on-Write はpandas 3.0で新しいデフォルトになります。これは、チェインインデックスは決して機能しないことを意味します。結果として、SettingWithCopyWarning はもう不要になります。詳細については、このセクション を参照してください。

` pd.options.mode.copy_on_write = True `

たとえpandas 3.0がまだ利用可能でなくても。

前のセクションの問題は単なるパフォーマンスの問題です。SettingWithCopy 警告はどうなっているのでしょうか?通常、数ミリ秒余計にかかるようなことをしても警告を出すことはありません!

しかし、連鎖インデックス付けの生成物に代入することは、本質的に予測不可能な結果をもたらすことが判明しました。これを見るには、Pythonインタプリタがこのコードをどのように実行するかを考えてみてください。

dfmi.loc[:, ('one', 'second')] = value
# becomes
dfmi.loc.__setitem__((slice(None), ('one', 'second')), value)

しかし、このコードは異なる方法で処理されます。

dfmi['one']['second'] = value
# becomes
dfmi.__getitem__('one').__setitem__('second', value)

そこに __getitem__ があることに気づきましたか?単純なケースを除いて、それがビューを返すかコピーを返すかを予測するのは非常に困難であり(配列のメモリレイアウトに依存しますが、pandasはその保証をしません)、したがって __setitem__dfmi を変更するのか、それともすぐに破棄される一時オブジェクトを変更するのかも予測できません。**それが** SettingWithCopy が警告していることです!

最初の例の loc プロパティについて心配すべきかどうか疑問に思っているかもしれません。しかし、dfmi.loc は変更されたインデックス付け動作を持つ dfmi 自体であることが保証されているため、dfmi.loc.__getitem__ / dfmi.loc.__setitem__dfmi に直接作用します。もちろん、dfmi.loc.__getitem__(idx)dfmi のビューまたはコピーである可能性があります。

時として、連鎖インデックス付けが明確に行われていないように見える場合でも、SettingWithCopy 警告が発生することがあります。**これら**こそが、SettingWithCopy が捕捉するように設計されたバグです!pandasは、おそらくあなたが次のことをしたと警告しようとしています。

def do_something(df):
    foo = df[['bar', 'baz']]  # Is foo a view? A copy? Nobody knows!
    # ... many lines here ...
    # We don't know whether this will modify df or not!
    foo['quux'] = value
    return foo

おやまあ!

評価順序が重要#

警告

Copy-on-Write はpandas 3.0で新しいデフォルトになります。これは、チェインインデックスは決して機能しないことを意味します。結果として、SettingWithCopyWarning はもう不要になります。詳細については、このセクション を参照してください。

` pd.options.mode.copy_on_write = True `

たとえpandas 3.0がまだ利用可能でなくても。

連鎖インデックス付けを使用する場合、インデックス付け操作の順序とタイプによって、結果が元のオブジェクトへのスライスになるか、スライスのコピーになるかが部分的に決定されます。

pandasには SettingWithCopyWarning があります。なぜなら、スライスのコピーに代入することは意図的ではないことが多く、スライスが期待された場所で連鎖インデックス付けがコピーを返すことによって引き起こされる間違いだからです。

pandasが、連結インデックス式への代入について、より信頼するように、または信頼しないようにするには、オプション mode.chained_assignment を以下のいずれかの値に設定できます。

  • デフォルトの'warn'は、SettingWithCopyWarningが出力されることを意味します。

  • 'raise'は、pandasが対処しなければならないSettingWithCopyErrorを発生させることを意味します。

  • Noneは、警告を完全に抑制します。

In [386]: dfb = pd.DataFrame({'a': ['one', 'one', 'two',
   .....:                           'three', 'two', 'one', 'six'],
   .....:                     'c': np.arange(7)})
   .....: 

# This will show the SettingWithCopyWarning
# but the frame values will be set
In [387]: dfb['c'][dfb['a'].str.startswith('o')] = 42

ただし、これはコピーに対して操作を行っているため、機能しません。

In [388]: with pd.option_context('mode.chained_assignment','warn'):
   .....:     dfb[dfb['a'].str.startswith('o')]['c'] = 42
   .....: 

連鎖代入は、混合dtypeフレームでの設定でも発生する可能性があります。

これらの設定ルールは、.loc/.ilocのすべてに適用されます。

以下は、複数のアイテムに対して(maskを使用して).locを使用し、固定インデックスを使用して単一のアイテムにアクセスするための推奨される方法です。

In [389]: dfc = pd.DataFrame({'a': ['one', 'one', 'two',
   .....:                           'three', 'two', 'one', 'six'],
   .....:                     'c': np.arange(7)})
   .....: 

In [390]: dfd = dfc.copy()

# Setting multiple items using a mask
In [391]: mask = dfd['a'].str.startswith('o')

In [392]: dfd.loc[mask, 'c'] = 42

In [393]: dfd
Out[393]: 
       a   c
0    one  42
1    one  42
2    two   2
3  three   3
4    two   4
5    one  42
6    six   6

# Setting a single item
In [394]: dfd = dfc.copy()

In [395]: dfd.loc[2, 'a'] = 11

In [396]: dfd
Out[396]: 
       a  c
0    one  0
1    one  1
2     11  2
3  three  3
4    two  4
5    one  5
6    six  6

以下は時に機能することもありますが、保証されていないため、避けるべきです。

In [397]: dfd = dfc.copy()

In [398]: dfd['a'][2] = 111

In [399]: dfd
Out[399]: 
       a  c
0    one  0
1    one  1
2    111  2
3  three  3
4    two  4
5    one  5
6    six  6

最後に、続く例はまったく機能しないため、避けるべきです。

In [400]: with pd.option_context('mode.chained_assignment','raise'):
   .....:     dfd.loc[0]['a'] = 1111
   .....: 
---------------------------------------------------------------------------
SettingWithCopyError                      Traceback (most recent call last)
<ipython-input-400-32ce785aaa5b> in ?()
      1 with pd.option_context('mode.chained_assignment','raise'):
----> 2     dfd.loc[0]['a'] = 1111

~/work/pandas/pandas/pandas/core/series.py in ?(self, key, value)
   1293                 )
   1294 
   1295         check_dict_or_set_indexers(key)
   1296         key = com.apply_if_callable(key, self)
-> 1297         cacher_needs_updating = self._check_is_chained_assignment_possible()
   1298 
   1299         if key is Ellipsis:
   1300             key = slice(None)

~/work/pandas/pandas/pandas/core/series.py in ?(self)
   1498             ref = self._get_cacher()
   1499             if ref is not None and ref._is_mixed_type:
   1500                 self._check_setitem_copy(t="referent", force=True)
   1501             return True
-> 1502         return super()._check_is_chained_assignment_possible()

~/work/pandas/pandas/pandas/core/generic.py in ?(self)
   4414         single-dtype meaning that the cacher should be updated following
   4415         setting.
   4416         """
   4417         if self._is_copy:
-> 4418             self._check_setitem_copy(t="referent")
   4419         return False

~/work/pandas/pandas/pandas/core/generic.py in ?(self, t, force)
   4488                 "indexing.html#returning-a-view-versus-a-copy"
   4489             )
   4490 
   4491         if value == "raise":
-> 4492             raise SettingWithCopyError(t)
   4493         if value == "warn":
   4494             warnings.warn(t, SettingWithCopyWarning, stacklevel=find_stack_level())

SettingWithCopyError: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.dokyumento.jp/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

警告

連鎖代入の警告/例外は、ユーザーに無効な代入の可能性を知らせることを目的としています。誤検知、つまり連鎖代入が誤って報告される状況があるかもしれません。