自分のキャリアをあれこれ考えながら、Pythonで様々なデータを分析していくブログです

[pandas] その2 条件式でのデータ抽出方法をまとめてみた

Python
Python
ヒノマルク
ヒノマルク

前回は行の番号や列のラベルでデータを抽出していました。
今回はセルの値を条件にして抽出する方法を調べて見ます。

下記記事の続きになります。

[pandas] その1 行と列の選択でのデータ抽出の方法をまとめてみた
ヒノマルク pandasの使い方をまとめていきます。 まずは行と列のデータの抽出方法です。 中々忘れがちですよね。pandasのバージョンによって非推奨になる機能もあります。 事前に下記記事ご覧になっておくとより理解が深まると思います。 条...
スポンサーリンク

Boolean Indexing (ブール索引) によるデータ抽出

ある行や列を特定の値で絞り込みたい場合、boolean vectors (ブールベクトル)と呼ばれるTrue/Falseのベクトルを利用する方法がよく使われます。

Another common operation is the use of boolean vectors to filter the data. The operators are: | for or, & for and, and ~ for not. These must be grouped by using parentheses
引用: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#boolean-indexing

SeriesやDataFrameにbool型のベクトルをseries[boolean vector]、df[boolean vector]のように書くと対象の行(Trueの行)のみ抽出されます。

また、前回の記事で推奨されていたlocメソッドではbooleanの配列も利用可能なので、series.loc[boolean vector]やdf.loc[boolean vector]という抽出方法も出来そうです。

.loc[] is primarily label based, but may also be used with a boolean array.
引用: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.loc.html

それでは使い方を色々見てみます。

ますデータフレームをボストンの住宅価格のデータセットを使って作成します。

ボストンの住宅価格のデータセットを準備
df = pd.read_csv("http://lib.stat.cmu.edu/datasets/boston_corrected.txt", encoding='Windows-1252',skiprows=9,sep="\t")
上から5件を確認
df.head()
Out[0]
OBS. TOWN TOWN# TRACT LON LAT MEDV CMEDV CRIM ZN ... CHAS NOX RM AGE DIS RAD TAX PTRATIO B LSTAT
0 1 Nahant 0 2011 -70.955 42.2550 24.0 24.0 0.00632 18.0 ... 0 0.538 6.575 65.2 4.0900 1 296 15.3 396.90 4.98
1 2 Swampscott 1 2021 -70.950 42.2875 21.6 21.6 0.02731 0.0 ... 0 0.469 6.421 78.9 4.9671 2 242 17.8 396.90 9.14
2 3 Swampscott 1 2022 -70.936 42.2830 34.7 34.7 0.02729 0.0 ... 0 0.469 7.185 61.1 4.9671 2 242 17.8 392.83 4.03
3 4 Marblehead 2 2031 -70.928 42.2930 33.4 33.4 0.03237 0.0 ... 0 0.458 6.998 45.8 6.0622 3 222 18.7 394.63 2.94
4 5 Marblehead 2 2032 -70.922 42.2980 36.2 36.2 0.06905 0.0 ... 0 0.458 7.147 54.2 6.0622 3 222 18.7 396.90 5.33

5 rows × 21 columns

どんなカラムがあるか確認
df.columns
Out[0]
Index(['OBS.', 'TOWN', 'TOWN#', 'TRACT', 'LON', 'LAT', 'MEDV', 'CMEDV', 'CRIM',
       'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX',
       'PTRATIO', 'B', 'LSTAT'],
      dtype='object')

今回はTOWNカラムとRMカラムを利用するので、どんなデータか確認しておきます。

各町名のサイズを確認
df.pivot_table(columns=["TOWN"],aggfunc="size")
Out[0]
TOWN
Arlington     7
Ashland       2
Bedford       2
Belmont       8
Beverly       6
             ..
Weymouth      8
Wilmington    3
Winchester    5
Winthrop      5
Woburn        6
Length: 92, dtype: int64

pivot_tableメソッドでExcelのpivot_tableみたいなことが出来るようです。今回は町名カラムのレコード数を調べるのに使いました。
ヒストグラムとかでみるとなお良かったかもしれません。

RMカラムのパーセンタイル確認
df.loc[:,"RM"].quantile([0,0.1,0.25,0.5,0.75,0.9,0.95,0.99,1])
Out[0]
0.00    3.5610
0.10    5.5935
0.25    5.8855
0.50    6.2085
0.75    6.6235
0.90    7.1515
0.95    7.5875
0.99    8.3350
1.00    8.7800
Name: RM, dtype: float64

数値型のRMカラムの中身を俯瞰します。今回はパーセンタイルという指標で確認しました。

最小3.5、中央値6.2、最大値8.7のようです。

Series (文字列) から特定条件を含む行の抽出

町名のカラムだけ取り出し、Seriesを作成します。

町名のSeriesを作成
s = df.loc[:, "TOWN"]
上から5件を確認
s.head()
Out[0]
0        Nahant
1    Swampscott
2    Swampscott
3    Marblehead
4    Marblehead
Name: TOWN, dtype: object
Boolean vectorの作成
s == "Swampscott"
Out[0]
0      False
1       True
2       True
3      False
4      False
       ...  
501    False
502    False
503    False
504    False
505    False
Name: TOWN, Length: 506, dtype: bool

町名がSwampscottのインデックスのみTrueになるようです。

Boolean vectorを使って抽出してみる
s[s == "Swampscott"] #or s.loc[s == "Swampscott"]
Out[0]
1    Swampscott
2    Swampscott
Name: TOWN, dtype: object

2行だけ抽出されました。
個人的な好みですが、s.locの方が見やすいですね。

複数条件での抽出
s[(s == "Swampscott") | (s == "Ashland")]
# or s.loc[(s == "Swampscott") | (s == "Ashland")]
Out[0]
1      Swampscott
2      Swampscott
254       Ashland
255       Ashland
Name: TOWN, dtype: object

町名がSwampscottとAshlandの行を抽出しました。

ただしこのままだとインデックスが1,2,254,255と飛び飛びになっています。使い勝手が悪いのでインデックスを抽出したデータに合わせて振り直すということをしたいと思います。

indexを振り直す
s[(s == "Swampscott") | (s == "Ashland")].reset_index(drop=True)
Out[0]
0    Swampscott
1    Swampscott
2       Ashland
3       Ashland
Name: TOWN, dtype: object

インデックスが振り直されました。
上から3つの情報を取得したい場合はs.loc[0:2]で取得することができます。

Series (数値) から特定条件を含む行の抽出

数値の扱いも確認したいため、RM (平均部屋数) のみ抽出してseriesを作成します。

RMのSeriesを作成
sn = df.loc[:, "RM"]
上から5件を取得
sn.head()
Out[0]
0    6.575
1    6.421
2    7.185
3    6.998
4    7.147
Name: RM, dtype: float64
平均部屋数が7以上のboolean vectorを作成
sn >= 7
Out[0]
0      False
1      False
2       True
3      False
4       True
       ...  
501    False
502    False
503    False
504    False
505    False
Name: RM, Length: 506, dtype: bool
平均部屋数が7以上の行を抽出
sn[sn >= 7] # or sn.loc[sn >= 7]
Out[0]
2      7.185
4      7.147
40     7.024
55     7.249
64     7.104
       ...  
364    8.780
370    7.016
375    7.313
453    7.393
482    7.061
Name: RM, Length: 64, dtype: float64
複数条件での抽出

複数条件を適用したい場合、条件式を1つずつ括弧で囲む必要があるようです。

sn[(sn >= 7.5) & (sn <= 8)]
# or sn.loc[(sn >= 7.5) & (sn <= 8)]
Out[0]
98     7.820
162    7.802
166    7.929
180    7.765
186    7.831
195    7.875
202    7.610
203    7.853
228    7.686
261    7.520
273    7.691
280    7.820
282    7.645
283    7.923
Name: RM, dtype: float64

平均部屋数が7.5以上8以下の行を抽出できたようです。

インデックスのリセット
sn[(sn >= 7.5) & (sn <= 8)].reset_index(drop=True)
Out[0]
0     7.820
1     7.802
2     7.929
3     7.765
4     7.831
5     7.875
6     7.610
7     7.853
8     7.686
9     7.520
10    7.691
11    7.820
12    7.645
13    7.923
Name: RM, dtype: float64

DataFrameから特定条件を含む行の抽出 (文字列カラムが対象)

TOWNとRMのデータフレームを抽出
## listを渡すとdataframe
df2 = df.loc[:, ["TOWN","RM"]]
中身の確認
df2
Out[0]
TOWN RM
0 Nahant 6.575
1 Swampscott 6.421
2 Swampscott 7.185
3 Marblehead 6.998
4 Marblehead 7.147
... ... ...
501 Winthrop 6.593
502 Winthrop 6.120
503 Winthrop 6.976
504 Winthrop 6.794
505 Winthrop 6.030

506 rows × 2 columns

Boolean vector作成
df2["TOWN"] == "Swampscott"
Out[0]
0      False
1       True
2       True
3      False
4      False
       ...  
501    False
502    False
503    False
504    False
505    False
Name: TOWN, Length: 506, dtype: bool

町名カラムがSwampscottの行がTrueになり、それ以外がFalseになる。

Boolean vectorを適用しデータを抽出する
df2[df2["TOWN"] == "Swampscott"]
# or df2.loc[df2["TOWN"] == "Swampscott"]
Out[0]
TOWN RM
1 Swampscott 6.421
2 Swampscott 7.185

DataFrameから特定条件を含む行の抽出 (数値カラムが対象)

まだ作成しなければdf2を下記コマンドで作成する。

(再掲) TOWNとRMのデータフレームを抽出
## listを渡すとdataframe
df2 = df.loc[:, ["TOWN","RM"]]
Boolean vectorを作成
df2["RM"] >= 7

部屋数が7以上という条件でBoolean vectorを作成する

Out[0]
0      False
1      False
2       True
3      False
4       True
       ...  
501    False
502    False
503    False
504    False
505    False
Name: RM, Length: 506, dtype: bool
平均部屋数が7以上の行を抽出
df2[df2["RM"] >= 7]
# or df2.loc[df2["RM"] >= 7]
Out[0]
TOWN RM
2 Swampscott 7.185
4 Marblehead 7.147
40 Lynnfield 7.024
55 Topsfield 7.249
64 Manchester 7.104
... ... ...
364 Boston Back Bay 8.780
370 Boston Beacon Hill 7.016
375 Boston Charlestown 7.313
453 Boston Savin Hill 7.393
482 Boston West Roxbury 7.061

64 rows × 2 columns

DataFrame全体に対して特定条件で検索したらどうなるか

下記のようなコードでBoolean vectorsを作成するとどうなるか確認して見ました。

カラム指定なしで抽出条件を指定
df2 == "Swampscott"
Out[0]
TOWN RM
0 False False
1 True False
2 True False
3 False False
4 False False
... ... ...
501 False False
502 False False
503 False False
504 False False
505 False False

506 rows × 2 columns

型の確認
type(df2 == "Swampscott")
Out[0]
pandas.core.frame.DataFrame

TOWNとRM両方のカラムに対して、値がSwampscottと一致するかどうかTrue/Falseを返すようです。TOWN列の該当行だけTrueになっているようです。最終的にSeriesではなく、DataFrameとして返ってきました。

Boolean vectorsを適用し、データ抽出を試みる
df2[df2 == "Swampscott"]
# or df2.loc[df2 == "Swampscott"]
Out[0]
TOWN RM
0 NaN NaN
1 Swampscott NaN
2 Swampscott NaN
3 NaN NaN
4 NaN NaN
... ... ...
501 NaN NaN
502 NaN NaN
503 NaN NaN
504 NaN NaN
505 NaN NaN

506 rows × 2 columns

trueの場所以外はNaNになるようです。

次に比較演算子を試して見ます。

7以上という条件でBoolean vectorsを作成
df2 >= 7
Out[0]
TypeError Traceback (most recent call last)

Input In [166], in 
----> 1 df2 >= 7

・・・省略・・・

TypeError: '>=' not supported between instances of 'str' and 'int'

なんとエラーになりました。文字列に対して比較演算子を使用しているためのようです。

DataFrameの各カラムを使用して複合条件を作成するのが間違いなさそうです。

Boolean vectorを作成
# 下記はOK
(df2["TOWN"] == "Swampscott") & (df2["RM"] >= 7)
Out[0]
0      False
1      False
2       True
3      False
4      False
       ...  
501    False
502    False
503    False
504    False
505    False
Length: 506, dtype: bool

TOWN列がSwampscottでかつ、RMが7以上の行を抽出する条件を作成しました。

複合条件でデータ抽出
df2[(df2["TOWN"] == "Swampscott") & (df2["RM"] >= 7)]
# or df2.loc[(df2["TOWN"] == "Swampscott") & (df2["RM"] >= 7)]
Out[0]
TOWN RM
2 Swampscott 7.185

1行のみが抽出されました。

スポンサーリンク

まとめ

今回の記事でBoolean Indexingについて学べたと思います。

前回までの記事と合わせると、データをDataFrameに格納して行列で抽出したり、抽出条件を作成して特定行のみを選択することも可能になりました。

次回は queryやevalメソッドでの抽出方法をまとめたいと思います。

queryやevalメソッドはnumexprというライブラリを内部的に利用して数値計算を高速処理することが可能になるようです。(データ量が少ない場合は逆に遅くなるようですので注意が必要です。)

その後は、欠損値処理やDataFrameへカラムを追加する方法などをまとめることができれば、仕事で使うデータ操作の知識はほとんど網羅出来るのかなと思っています。

スポンサーリンク

参考

https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#boolean-indexing
https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.loc.html
https://pandas.pydata.org/docs/reference/api/pandas.Series.reset_index.html

タイトルとURLをコピーしました