[pandas]特定の条件を満たす行を削除する

f:id:soratokimitonoaidani:20190110230412p:plain
DataFrameから、特定の条件を満たす行を削除する方法について。

例を挙げよう。

import pandas as pd

df = pd.DataFrame({
    'name'    : ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Fred'],
    'English' : [12, 34, 56, 78, -1, 90],
    'Math'    : [88, 66, -1, 44, 22, -1]    
})

df
# ->
      name  English  Math
0    Alice       12    88
1      Bob       34    66
2  Charlie       56    -1
3    David       78    44
4      Eve       -1    22
5     Fred       90    -1

上記の通り、生徒と試験の点数が表(DataFrame)になっているとしよう。
試験を欠席した人は点数が-1になっている、としよう。
Mathの得点を集計する前に、Mathの数値が-1である行を除外したい。
どうすればよいだろうか。
NaNを削除するのであればdropna関数が使える。しかし今回の状況では使えない。

正解

df[df['Math'] != -1]

# ->

    name  English  Math
0  Alice       12    88
1    Bob       34    66
3  David       78    44
4    Eve       -1    22

ちょっとした発想の転換が必要である。
「行を削除する」という発想で考えると、なかなか思いつかなかった。
-1の行を削除するので、-1でない行を選択・抽出すれば良い。

df[df.Math != -1]

でも上手くいく。'Math'の列を指定する際の書き方が違うだけだ。

別解

一応、最初に考えた方法もあわせて書いておく。
以下の方法は簡潔ではないことを注意されたい。参考と自分用の整理のために残しておく。
該当行の削除はdropでできるだろう、というところから考え始めると、こうなる。

該当する行を求めるのは以下のコードで可能である。

condition_boolen = (df["Math"] == -1)
condition_boolen

# ->

0    False
1    False
2     True
3    False
4    False
5     True
Name: Math, dtype: bool

しかし、pandas.DataFrame.drop 関数は、True/Falseが並んでいるこの配列(Series)を引数に取れない。

df.drop(condition_boolen)
# ->
エラー(内容は下の折りたたみ部分)

エラー内容(クリックすると展開されます)

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-7-ba417e4cb86b> in <module>
----> 1 df.drop(condition_boolen)

c:\program files\python37\lib\site-packages\pandas\core\frame.py in drop(self, labels, axis, index, columns, level, inplace, errors)
   3695                                            index=index, columns=columns,
   3696                                            level=level, inplace=inplace,
-> 3697                                            errors=errors)
   3698 
   3699     @rewrite_axis_style_signature('mapper', [('copy', True),

c:\program files\python37\lib\site-packages\pandas\core\generic.py in drop(self, labels, axis, index, columns, level, inplace, errors)
   3109         for axis, labels in axes.items():
   3110             if labels is not None:
-> 3111                 obj = obj._drop_axis(labels, axis, level=level, errors=errors)
   3112 
   3113         if inplace:

c:\program files\python37\lib\site-packages\pandas\core\generic.py in _drop_axis(self, labels, axis, level, errors)
   3141                 new_axis = axis.drop(labels, level=level, errors=errors)
   3142             else:
-> 3143                 new_axis = axis.drop(labels, errors=errors)
   3144             result = self.reindex(**{axis_name: new_axis})
   3145 

c:\program files\python37\lib\site-packages\pandas\core\indexes\base.py in drop(self, labels, errors)
   4402             if errors != 'ignore':
   4403                 raise KeyError(
-> 4404                     '{} not found in axis'.format(labels[mask]))
   4405             indexer = indexer[~mask]
   4406         return self.delete(indexer)

KeyError: '[False False  True False False  True] not found in axis'

ではどうすれば良いかというと、 drop関数の引数には、削除したい列のindexのリストを指定する必要がある。

labels : single label or list-like Index or column labels to drop. https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.drop.html より

rows_to_drop = df.index[df["Math"] == -1]
rows_to_drop

# ->
Int64Index([2, 5], dtype='int64')

df.drop(rows_to_drop)
# ->
    name  English  Math
0  Alice       12    88
1    Bob       34    66
3  David       78    44
4    Eve       -1    22

"Math"が-1の行を選択する操作と、indexを取る操作とは、交換可能である。

df[df["Math"] == -1].index

# ->
Int64Index([2, 5], dtype='int64')

余談だが、インデックス()を df.index は df['index']と書くことはできない。エラーになる。

df['index']
# ->
エラー(内容は下の折りたたみ部分)

エラー内容(クリックすると展開されます)

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
c:\program files\python37\lib\site-packages\pandas\core\indexes\base.py in get_loc(self, key, method, tolerance)
   3077             try:
-> 3078                 return self._engine.get_loc(key)
   3079             except KeyError:

pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

KeyError: 'index'

During handling of the above exception, another exception occurred:

KeyError                                  Traceback (most recent call last)
<ipython-input-11-1cd2fcbdc823> in <module>
----> 1 df['index']

c:\program files\python37\lib\site-packages\pandas\core\frame.py in __getitem__(self, key)
   2686             return self._getitem_multilevel(key)
   2687         else:
-> 2688             return self._getitem_column(key)
   2689 
   2690     def _getitem_column(self, key):

c:\program files\python37\lib\site-packages\pandas\core\frame.py in _getitem_column(self, key)
   2693         # get column
   2694         if self.columns.is_unique:
-> 2695             return self._get_item_cache(key)
   2696 
   2697         # duplicate columns & possible reduce dimensionality

c:\program files\python37\lib\site-packages\pandas\core\generic.py in _get_item_cache(self, item)
   2487         res = cache.get(item)
   2488         if res is None:
-> 2489             values = self._data.get(item)
   2490             res = self._box_item_values(item, values)
   2491             cache[item] = res

c:\program files\python37\lib\site-packages\pandas\core\internals.py in get(self, item, fastpath)
   4113 
   4114             if not isna(item):
-> 4115                 loc = self.items.get_loc(item)
   4116             else:
   4117                 indexer = np.arange(len(self.items))[isna(self.items)]

c:\program files\python37\lib\site-packages\pandas\core\indexes\base.py in get_loc(self, key, method, tolerance)
   3078                 return self._engine.get_loc(key)
   3079             except KeyError:
-> 3080                 return self._engine.get_loc(self._maybe_cast_indexer(key))
   3081 
   3082         indexer = self.get_indexer([key], method=method, tolerance=tolerance)

pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

KeyError: 'index'

以上。それでは。