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

pandasオブジェクトの軸ラベル情報は、多くの目的を果たします。

  • 既知の指標を使用してデータを識別し(つまり、メタデータを提供します)、分析、可視化、対話型コンソール表示に重要です。

  • 自動的および明示的なデータの整合を可能にします。

  • データセットの部分集合の直感的な取得と設定を可能にします。

このセクションでは、最後の点、つまりpandasオブジェクトの部分集合のスライス、分割、および一般的な取得と設定方法に焦点を当てます。この分野ではSeriesとDataFrameがより多くの開発の注目を集めているため、主にそれらに焦点を当てます。

注記

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

警告

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

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

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

インデックス付けの異なる選択肢#

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

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

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

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

    • ラベル付きのスライスオブジェクト'a':'f'(通常のPythonのスライスとは異なり、インデックスに存在する場合、両方の開始と終了が含められます!ラベルによるスライス端点は包含的ですを参照してください)。

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

    • 1つの引数(呼び出し元のSeriesまたはDataFrame)を持つ関数で、インデックス付けに対して有効な出力を返す関数(上記いずれか)。

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

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

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

    • 整数、例:5

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

    • 整数付きのスライスオブジェクト1:7

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

    • 1つの引数(呼び出し元のSeriesまたはDataFrame)を持つ関数で、インデックス付けに対して有効な出力を返す関数(上記いずれか)。

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

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

  • .loc.iloc、および[]インデックス付けは、callableをインデックス付け子として受け入れることができます。呼び出し可能オブジェクトによる選択で詳細を参照してください。

    注記

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

多次元軸選択を使用してオブジェクトから値を取得するには、次の表記法を使用します(例として.locを使用しますが、以下は.ilocにも適用されます)。軸アクセッサのいずれかは、ヌルスライス:にすることができます。仕様から除外された軸は:であるとみなされます。例:p.loc['a']p.loc['a', :]と同等です。

オブジェクトの種類

インデックス付け子

Series

s.loc[indexer]

DataFrame

df.loc[row_indexer,column_indexer]

基本#

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

オブジェクトの種類

選択

戻り値の種類

Series

series[label]

スカラー値

DataFrame

frame[colname]

colnameに対応するSeries

ここでは、インデックス付け機能を示すために使用する単純な時系列データセットを作成します。

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

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

In [3]: df
Out[3]: 
                   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 [4]: s = df['A']

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

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

In [6]: df
Out[6]: 
                   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 [7]: df[['B', 'A']] = df[['A', 'B']]

In [8]: df
Out[8]: 
                   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からSeriesDataFrameを設定する際にすべての軸を整合させます。

列の整合は値の代入の前に行われるため、これはdfを変更しません。

In [9]: df[['A', 'B']]
Out[9]: 
                   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 [10]: df.loc[:, ['B', 'A']] = df[['A', 'B']]

In [11]: df[['A', 'B']]
Out[11]: 
                   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 [12]: df.loc[:, ['B', 'A']] = df[['A', 'B']].to_numpy()

In [13]: df[['A', 'B']]
Out[13]: 
                   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

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

列の整合は値の代入の前に実行されないため、これはdfを変更します。

In [14]: df[['A', 'B']]
Out[14]: 
                   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 [15]: df.iloc[:, [1, 0]] = df[['A', 'B']]

In [16]: df[['A','B']]
Out[16]: 
                   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 [17]: sa = pd.Series([1, 2, 3], index=list('abc'))

In [18]: dfa = df.copy()
In [19]: sa.b
Out[19]: 2

In [20]: dfa.A
Out[20]: 
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 [21]: sa.a = 5

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

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

In [24]: dfa
Out[24]: 
            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 [25]: dfa['A'] = list(range(len(dfa.index)))  # use this form to create a new column

In [26]: dfa
Out[26]: 
            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環境を使用している場合は、タブ補完を使用して、これらのアクセス可能な属性を確認することもできます。

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

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

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

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

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

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

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

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

範囲のスライス#

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

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

In [33]: s[:5]
Out[33]: 
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 [34]: s[::2]
Out[34]: 
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 [35]: s[::-1]
Out[35]: 
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 [36]: s2 = s.copy()

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

In [38]: s2
Out[38]: 
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 [39]: df[:3]
Out[39]: 
                   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 [40]: df[::-1]
Out[40]: 
                   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 [41]: dfl = pd.DataFrame(np.random.randn(5, 4),
   ....:                    columns=list('ABCD'),
   ....:                    index=pd.date_range('20130101', periods=5))
   ....: 

In [42]: dfl
Out[42]: 
                   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 [43]: dfl.loc[2:3]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[43], 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:6662, in Index.slice_indexer(self, start, end, step)
   6618 def slice_indexer(
   6619     self,
   6620     start: Hashable | None = None,
   6621     end: Hashable | None = None,
   6622     step: int | None = None,
   6623 ) -> slice:
   6624     """
   6625     Compute the slice indexer for input labels and step.
   6626 
   (...)
   6660     slice(1, 3, None)
   6661     """
-> 6662     start_slice, end_slice = self.slice_locs(start, end, step=step)
   6664     # return a slice
   6665     if not is_scalar(start_slice):

File ~/work/pandas/pandas/pandas/core/indexes/base.py:6879, in Index.slice_locs(self, start, end, step)
   6877 start_slice = None
   6878 if start is not None:
-> 6879     start_slice = self.get_slice_bound(start, "left")
   6880 if start_slice is None:
   6881     start_slice = 0

File ~/work/pandas/pandas/pandas/core/indexes/base.py:6794, in Index.get_slice_bound(self, label, side)
   6790 original_label = label
   6792 # For datetime indices label may be a string that has to be converted
   6793 # to datetime boundary according to its resolution.
-> 6794 label = self._maybe_cast_slice_bound(label, side)
   6796 # we need to look up the label
   6797 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:4301, in Index._raise_invalid_indexer(self, form, key, reraise)
   4299 if reraise is not lib.no_default:
   4300     raise TypeError(msg) from reraise
-> 4301 raise TypeError(msg)

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

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

In [44]: dfl.loc['20130102':'20130104']
Out[44]: 
                   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スライスとは異なり、インデックスに存在する場合は、開始と終了の**両方**が含まれます! ラベルによるスライスを参照してください)。

  • ブール値の配列。

  • 呼び出し可能オブジェクト、呼び出し可能オブジェクトによる選択を参照してください。

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

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

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

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

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

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

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

DataFrameの場合

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

In [52]: df1
Out[52]: 
          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 [53]: df1.loc[['a', 'b', 'd'], :]
Out[53]: 
          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 [54]: df1.loc['d':, 'A':'C']
Out[54]: 
          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 [55]: df1.loc['a']
Out[55]: 
A    0.132003
B   -0.827317
C   -0.076467
D   -1.187678
Name: a, dtype: float64

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

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

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

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

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

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

In [60]: df1[mask]
Out[60]: 
          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 [61]: df1.loc['a', 'A']
Out[61]: 0.13200317033032932

ラベルによるスライス#

.locをスライスで使用する場合、開始ラベルと終了ラベルの両方がインデックスに存在する場合は、2つの間にある要素(それらを含む)が返されます。

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

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

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

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

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

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

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

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

In [67]: s.loc[3:5]
Out[67]: 
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

  • ブール値の配列。

  • 呼び出し可能オブジェクト、呼び出し可能オブジェクトによる選択を参照してください。

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

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

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

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

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

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

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

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

DataFrameの場合

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

In [75]: df1
Out[75]: 
           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 [76]: df1.iloc[:3]
Out[76]: 
          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 [77]: df1.iloc[1:5, 2:4]
Out[77]: 
          4         6
2  0.301624 -2.179861
4  1.462696 -1.743161
6  1.314232  0.690579
8  0.014871  3.357427

整数リストによる選択

In [78]: df1.iloc[[1, 3, 5], [1, 3]]
Out[78]: 
           2         6
2  -0.154951 -2.179861
6  -0.345352  0.690579
10 -1.236269 -0.487602
In [79]: df1.iloc[1:3, :]
Out[79]: 
          0         2         4         6
2  0.403310 -0.154951  0.301624 -2.179861
4 -1.369849 -0.954208  1.462696 -1.743161
In [80]: df1.iloc[:, 1:3]
Out[80]: 
           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 [81]: df1.iloc[1, 1]
Out[81]: -0.1549507744249032

整数位置を使用してクロスセクションを取得する場合(df.xs(1)と同等)

In [82]: df1.iloc[1]
Out[82]: 
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 [83]: x = list('abcdef')

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

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

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

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

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

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

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

範囲外の範囲を使用すると、空の軸(例:空のDataFrameが返される)になる可能性があることに注意してください。

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

In [92]: dfl
Out[92]: 
          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 [93]: dfl.iloc[:, 2:3]
Out[93]: 
Empty DataFrame
Columns: []
Index: [0, 1, 2, 3, 4]

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

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

範囲外の単一インデクサーはIndexErrorを発生させます。いずれかの要素が範囲外のインデクサーのリストはIndexErrorを発生させます。

In [96]: 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:4150, in NDFrame._take_with_is_copy(self, indices, axis)
   4141 """
   4142 Internal version of the `take` method that sets the `_is_copy`
   4143 attribute to keep track of the parent dataframe (using in indexing
   (...)
   4148 See the docstring of `take` for full explanation of the parameters.
   4149 """
-> 4150 result = self.take(indices=indices, axis=axis)
   4151 # Maybe set copy if we didn't actually change the index.

File ~/work/pandas/pandas/pandas/core/generic.py:4130, in NDFrame.take(self, indices, axis, **kwargs)
   4126     indices = np.arange(
   4127         indices.start, indices.stop, indices.step, dtype=np.intp
   4128     )
-> 4130 new_data = self._mgr.take(
   4131     indices,
   4132     axis=self._get_block_manager_axis(axis),
   4133     verify=True,
   4134 )
   4135 return self._constructor_from_mgr(new_data, axes=new_data.axes).__finalize__(
   4136     self, method="take"
   4137 )

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[96], 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 [97]: dfl.iloc[:, 4]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[97], 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

呼び出し可能オブジェクトによる選択#

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

注記

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

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

In [99]: df1
Out[99]: 
          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 [100]: df1.loc[lambda df: df['A'] > 0, :]
Out[100]: 
          A         B         C         D
c  0.299368 -0.863838  0.408204 -1.048089
e  1.289997  0.082423 -0.055758  0.536580

In [101]: df1.loc[:, lambda df: ['A', 'B']]
Out[101]: 
          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 [102]: df1.iloc[:, lambda df: [0, 1]]
Out[102]: 
          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 [103]: df1[lambda df: df.columns[0]]
Out[103]: 
a   -0.023688
b   -0.251905
c    0.299368
d   -0.025747
e    1.289997
f   -0.489682
Name: A, dtype: float64

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

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

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

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

In [106]: (bb.groupby(['year', 'team']).sum(numeric_only=True)
   .....:    .loc[lambda df: df['r'] > 100])
   .....: 
Out[106]: 
           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 [107]: dfd = pd.DataFrame({'A': [1, 2, 3],
   .....:                     'B': [4, 5, 6]},
   .....:                    index=list('abc'))
   .....: 

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

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

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

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

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

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

インデックスの再設定#

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

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

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

あるいは、有効なキーのみを選択する場合は、次の方法が慣用的で効率的です。選択のdtypeは確実に保持されます。

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

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

重複したインデックスがある場合、.reindex()はエラーを発生させます。

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

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

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

File ~/work/pandas/pandas/pandas/core/series.py:5144, in Series.reindex(self, index, axis, method, copy, level, fill_value, limit, tolerance)
   5127 @doc(
   5128     NDFrame.reindex,  # type: ignore[has-type]
   5129     klass=_shared_doc_kwargs["klass"],
   (...)
   5142     tolerance=None,
   5143 ) -> Series:
-> 5144     return super().reindex(
   5145         index=index,
   5146         method=method,
   5147         copy=copy,
   5148         level=level,
   5149         fill_value=fill_value,
   5150         limit=limit,
   5151         tolerance=tolerance,
   5152     )

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

File ~/work/pandas/pandas/pandas/core/generic.py:5630, in NDFrame._reindex_axes(self, axes, level, limit, tolerance, method, fill_value, copy)
   5627     continue
   5629 ax = self._get_axis(a)
-> 5630 new_index, indexer = ax.reindex(
   5631     labels, level=level, limit=limit, tolerance=tolerance, method=method
   5632 )
   5634 axis = self._get_axis_number(a)
   5635 obj = obj._reindex_with_indexers(
   5636     {axis: [new_index, indexer]},
   5637     fill_value=fill_value,
   5638     copy=copy,
   5639     allow_dups=False,
   5640 )

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

ValueError: cannot reindex on an axis with duplicate labels

一般的に、目的のラベルと現在の軸を交差させ、インデックスを再設定できます。

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

ただし、結果のインデックスが重複している場合でも、エラーが発生します。

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

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

File ~/work/pandas/pandas/pandas/core/series.py:5144, in Series.reindex(self, index, axis, method, copy, level, fill_value, limit, tolerance)
   5127 @doc(
   5128     NDFrame.reindex,  # type: ignore[has-type]
   5129     klass=_shared_doc_kwargs["klass"],
   (...)
   5142     tolerance=None,
   5143 ) -> Series:
-> 5144     return super().reindex(
   5145         index=index,
   5146         method=method,
   5147         copy=copy,
   5148         level=level,
   5149         fill_value=fill_value,
   5150         limit=limit,
   5151         tolerance=tolerance,
   5152     )

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

File ~/work/pandas/pandas/pandas/core/generic.py:5630, in NDFrame._reindex_axes(self, axes, level, limit, tolerance, method, fill_value, copy)
   5627     continue
   5629 ax = self._get_axis(a)
-> 5630 new_index, indexer = ax.reindex(
   5631     labels, level=level, limit=limit, tolerance=tolerance, method=method
   5632 )
   5634 axis = self._get_axis_number(a)
   5635 obj = obj._reindex_with_indexers(
   5636     {axis: [new_index, indexer]},
   5637     fill_value=fill_value,
   5638     copy=copy,
   5639     allow_dups=False,
   5640 )

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

ValueError: cannot reindex on an axis with duplicate labels

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

拡張による設定#

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

Seriesの場合、これは事実上追加操作です。

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

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

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

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

DataFrameは、.locを使用していずれかの軸で拡張できます。

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

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

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

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

これはDataFrameに対するappend操作に似ています。

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

In [150]: dfi
Out[150]: 
   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 [151]: s.iat[5]
Out[151]: 5

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

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

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

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

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

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

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

In [157]: df
Out[157]: 
                   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 [158]: s = pd.Series(range(-3, 4))

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

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

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

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

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

In [163]: df[df['A'] > 0]
Out[163]: 
                   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 [164]: 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 [165]: criterion = df2['a'].map(lambda x: x.startswith('t'))

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

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

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

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

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

警告

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

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

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

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

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

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

isinを使用したインデックス付け#

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

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

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

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

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

同じメソッドはIndexオブジェクトでも使用でき、検索するラベルのうち実際に存在するラベルがどれであるかわからない場合に便利です。

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

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

さらに、MultiIndexでは、メンバーシップチェックで使用する別々のレベルを選択できます。

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

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

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

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

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

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

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

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

多くの場合、特定の値を特定の列と一致させる必要があります。値をキーが列で、値がチェックするアイテムのリストである辞書にするだけです。

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

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

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

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

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

DataFrameのisinany()およびall()メソッドを組み合わせて、特定の条件を満たすデータのサブセットをすばやく選択します。各列が独自の条件を満たす行を選択するには

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

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

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

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

ブールベクトルを使用してSeriesから値を選択すると、一般的にデータのサブセットが返されます。選択結果が元のデータと同じ形状になるようにするには、SeriesDataFramewhereメソッドを使用できます。

選択された行のみを返すには

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

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

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

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

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

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

In [199]: df[df < 0]
Out[199]: 
                   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は、条件がFalseである値の置換に使用するオプションのother引数を取ります。

In [200]: df.where(df < 0, -df)
Out[200]: 
                   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 [201]: s2 = s.copy()

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

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

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

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

In [206]: df2
Out[206]: 
                   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 [207]: df.where(df < 0, -df) == np.where(df < 0, df, -df)
Out[207]: 
               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 [208]: df2 = df.copy()

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

In [210]: df2
Out[210]: 
                   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

wherewhereを実行する際に、入力の整列を行うaxisおよびlevelパラメータを受け入れることができます。

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

In [212]: df2.where(df2 > 0, df2['A'], axis='index')
Out[212]: 
                   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 [213]: df2 = df.copy()

In [214]: df.apply(lambda x, y: x.where(x > 0, y), y=df['A'])
Out[214]: 
                   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 [215]: df3 = pd.DataFrame({'A': [1, 2, 3],
   .....:                     'B': [4, 5, 6],
   .....:                     'C': [7, 8, 9]})
   .....: 

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

マスク#

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

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

In [218]: df.mask(df >= 0)
Out[218]: 
                   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 [219]: df = pd.DataFrame({'col1': list('ABBC'), 'col2': list('ZZXY')})

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

In [221]: df
Out[221]: 
  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 [222]: conditions = [
   .....:     (df['col2'] == 'Z') & (df['col1'] == 'A'),
   .....:     (df['col2'] == 'Z') & (df['col1'] == 'B'),
   .....:     (df['col1'] == 'B')
   .....: ]
   .....: 

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

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

In [225]: df
Out[225]: 
  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 [226]: n = 10

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

In [228]: df
Out[228]: 
          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 [229]: df[(df['a'] < df['b']) & (df['b'] < df['c'])]
Out[229]: 
          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 [230]: df.query('(a < b) & (b < c)')
Out[230]: 
          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 [231]: df = pd.DataFrame(np.random.randint(n / 2, size=(n, 2)), columns=list('bc'))

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

In [233]: df
Out[233]: 
   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 [234]: df.query('a < b and b < c')
Out[234]: 
   b  c
a      
2  3  4

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

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

In [236]: df
Out[236]: 
   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 [237]: df.query('index < b < c')
Out[237]: 
   b  c
2  5  6

注記

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

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

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

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

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

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

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

MultiIndex query()構文#

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

In [242]: n = 10

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

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

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

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

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

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

In [249]: df
Out[249]: 
                   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 [250]: df.query('color == "red"')
Out[250]: 
                   0         1
color food                    
red   ham   0.194889 -0.381994
      ham   0.318587  2.089075
      eggs -0.728293 -0.090255

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

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

In [252]: df
Out[252]: 
                   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 [253]: df.query('ilevel_0 == "red"')
Out[253]: 
                 0         1
red ham   0.194889 -0.381994
    ham   0.318587  2.089075
    eggs -0.728293 -0.090255

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

query()の使用例#

query()の使用例としては、共通の列名(またはインデックスレベル/名)のサブセットを持つDataFrameオブジェクトのコレクションがある場合が挙げられます。クエリ対象のフレームを指定しなくても、両方のフレームに同じクエリを渡すことができます。

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

In [255]: df
Out[255]: 
          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 [256]: df2 = pd.DataFrame(np.random.rand(n + 2, 3), columns=df.columns)

In [257]: df2
Out[257]: 
           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 [258]: expr = '0.0 <= a <= c <= 0.5'

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

query():Pythonとpandasの構文比較#

完全なNumPyライクな構文

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

In [261]: df
Out[261]: 
   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 [262]: df.query('(a < b) & (b < c)')
Out[262]: 
   a  b  c
0  7  8  9

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

(比較演算子は&|よりも強くバインドされるため)括弧を削除することで、少し見やすくなります。

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

記号の代わりに英語を使用します。

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

紙に書く方法に非常に近いです。

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

innot in演算子#

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

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

In [268]: df
Out[268]: 
    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 [269]: df.query('a in b')
Out[269]: 
   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 [270]: df[df['a'].isin(df['b'])]
Out[270]: 
   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 [271]: df.query('a not in b')
Out[271]: 
    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 [272]: df[~df['a'].isin(df['b'])]
Out[272]: 
    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 [273]: df.query('a in b and c < d')
Out[273]: 
   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 [274]: df[df['b'].isin(df['a']) & (df['c'] < df['d'])]
Out[274]: 
    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

注記

numexprにはこの演算の等価物が存在しないため、innot inは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 [275]: df.query('b == ["a", "b", "c"]')
Out[275]: 
    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 [276]: df[df['b'].isin(["a", "b", "c"])]
Out[276]: 
    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 [277]: df.query('c == [1, 2]')
Out[277]: 
    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 [278]: df.query('c != [1, 2]')
Out[278]: 
    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 [279]: df.query('[1, 2] in c')
Out[279]: 
    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 [280]: df.query('[1, 2] not in c')
Out[280]: 
    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 [281]: df[df['c'].isin([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

ブール演算子#

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

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

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

In [284]: df.query('~bools')
Out[284]: 
          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 [285]: df.query('not bools')
Out[285]: 
          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 [286]: df.query('not bools') == df[~df['bools']]
Out[286]: 
      a     b     c  bools
2  True  True  True   True
7  True  True  True   True
8  True  True  True   True

もちろん、式は任意に複雑にすることもできます。

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

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

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

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

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

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

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

../_images/query-perf.png

DataFrame.query()numexprエンジンを使用することによるパフォーマンス上の利点は、フレームの行数が約10万行を超えている場合にのみ見られます。

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

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

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

重複データ#

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

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

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

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

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

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

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

In [294]: 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 [295]: df2
Out[295]: 
       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 [296]: df2.duplicated('a')
Out[296]: 
0    False
1     True
2    False
3     True
4     True
5    False
6    False
dtype: bool

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

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

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

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

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

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

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

In [303]: df2.drop_duplicates(['a', 'b'])
Out[303]: 
       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 [304]: df3 = pd.DataFrame({'a': np.arange(6),
   .....:                     'b': np.random.randn(6)},
   .....:                    index=['a', 'a', 'b', 'c', 'b', 'a'])
   .....: 

In [305]: df3
Out[305]: 
   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 [306]: df3.index.duplicated()
Out[306]: array([False,  True, False, False,  True,  True])

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

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

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

辞書のようなget()メソッド#

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

または数値を使用します。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

In [334]: df
Out[334]: 
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 [335]: df['A']
Out[335]: 
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 [336]: ind = pd.Index([1, 2, 3])

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

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

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

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

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

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

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

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

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

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

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

2つの主要な演算は、unionintersectionです。差分は.difference()メソッドで提供されます。

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

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

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

また、symmetric_difference演算も利用できます。これは、idx1またはidx2のどちらか一方にのみ出現する要素を返します。これは、idx1.difference(idx2).union(idx2.difference(idx1))によって作成されたIndexと同等であり、重複は削除されます。

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

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

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

注記

集合演算の結果として得られるIndexは、昇順にソートされます。

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

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

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

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

欠損値#

重要

Indexは欠損値(NaN)を保持できますが、予期しない結果を避けたい場合は、それを避けるべきです。たとえば、一部の演算では暗黙的に欠損値が除外されます。

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

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

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

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

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

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

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

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

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

インデックスの設定#

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

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

In [362]: data
Out[362]: 
     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 [363]: indexed1 = data.set_index('c')

In [364]: indexed1
Out[364]: 
     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 [365]: indexed2 = data.set_index(['a', 'b'])

In [366]: indexed2
Out[366]: 
         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 [367]: frame = data.set_index('c', drop=False)

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

In [369]: frame
Out[369]: 
           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 [370]: data.set_index('c', drop=False)
Out[370]: 
     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 [371]: data
Out[371]: 
     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 [372]: data.reset_index()
Out[372]: 
   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 [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

In [374]: frame.reset_index(level=1)
Out[374]: 
         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 [375]: df_idx = pd.DataFrame(range(4))

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

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

ビューとコピーの返却#

警告

コピーオンライトは、pandas 3.0で新しいデフォルトになります。これは、連鎖インデックスが機能しなくなることを意味します。その結果、SettingWithCopyWarningは不要になります。このセクションで詳細を確認してください。pandas 3.0が利用可能になる前にでも、以下の設定でコピーオンライトを有効にして改善点を活用することをお勧めします。

` pd.options.mode.copy_on_write = True `

pandas 3.0が利用可能になる前でも。

pandasオブジェクトの値を設定する際には、「連鎖インデックス」と呼ばれるものを避けるために注意が必要です。例を以下に示します。

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

In [379]: dfmi
Out[379]: 
    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 [380]: dfmi['one']['second']
Out[380]: 
0    b
1    f
2    j
3    n
Name: second, dtype: object
In [381]: dfmi.loc[:, ('one', 'second')]
Out[381]: 
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'でインデックス付けされたシリーズを選択します。これは、pandasがこれらの操作を別々のイベント(つまり、__getitem__への別々の呼び出し)と見なすため、変数dfmi_with_oneによって示されます。そのため、線形操作として扱う必要があり、順番に実行されます。

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

連鎖インデックスを使用すると、代入が失敗するのはなぜですか?#

警告

コピーオンライトは、pandas 3.0で新しいデフォルトになります。これは、連鎖インデックスが機能しなくなることを意味します。その結果、SettingWithCopyWarningは不要になります。このセクションで詳細を確認してください。pandas 3.0が利用可能になる前にでも、以下の設定でコピーオンライトを有効にして改善点を活用することをお勧めします。

` 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

大変だ!

評価順序が重要#

警告

コピーオンライトは、pandas 3.0で新しいデフォルトになります。これは、連鎖インデックスが機能しなくなることを意味します。その結果、SettingWithCopyWarningは不要になります。このセクションで詳細を確認してください。pandas 3.0が利用可能になる前にでも、以下の設定でコピーオンライトを有効にして改善点を活用することをお勧めします。

` pd.options.mode.copy_on_write = True `

pandas 3.0が利用可能になる前でも。

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

pandasにはSettingWithCopyWarningがあります。これは、スライスのコピーへの代入が頻繁に意図的なものではなく、連鎖インデックスがスライスを期待した場所にコピーを返すことによって発生する間違いであるためです。

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

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

  • 'raise'は、pandasが対処する必要があるSettingWithCopyErrorを発生させることを意味します。

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

In [382]: 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 [383]: dfb['c'][dfb['a'].str.startswith('o')] = 42

ただし、これはコピーを操作しており、機能しません。

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

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

注記

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

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

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

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

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

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

In [389]: dfd
Out[389]: 
       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 [390]: dfd = dfc.copy()

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

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

以下は、場合によっては機能する可能性がありますが、保証されておらず、そのため避けるべきです。

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

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

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

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

In [396]: with pd.option_context('mode.chained_assignment','raise'):
   .....:     dfd.loc[0]['a'] = 1111
   .....: 
---------------------------------------------------------------------------
SettingWithCopyError                      Traceback (most recent call last)
<ipython-input-396-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)
   1275                 )
   1276 
   1277         check_dict_or_set_indexers(key)
   1278         key = com.apply_if_callable(key, self)
-> 1279         cacher_needs_updating = self._check_is_chained_assignment_possible()
   1280 
   1281         if key is Ellipsis:
   1282             key = slice(None)

~/work/pandas/pandas/pandas/core/series.py in ?(self)
   1480             ref = self._get_cacher()
   1481             if ref is not None and ref._is_mixed_type:
   1482                 self._check_setitem_copy(t="referent", force=True)
   1483             return True
-> 1484         return super()._check_is_chained_assignment_possible()

~/work/pandas/pandas/pandas/core/generic.py in ?(self)
   4392         single-dtype meaning that the cacher should be updated following
   4393         setting.
   4394         """
   4395         if self._is_copy:
-> 4396             self._check_setitem_copy(t="referent")
   4397         return False

~/work/pandas/pandas/pandas/core/generic.py in ?(self, t, force)
   4466                 "indexing.html#returning-a-view-versus-a-copy"
   4467             )
   4468 
   4469         if value == "raise":
-> 4470             raise SettingWithCopyError(t)
   4471         if value == "warn":
   4472             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

警告

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