pandasで特定の文字列を含む行を抽出(完全一致、部分一致)
pandas.DataFrameから特定の文字列を含む要素を持つ行を抽出する方法(完全一致・部分一致)について説明する。
ここではブーリアンインデックス(Boolean indexing)を用いた方法を説明するが、query()メソッドを使うことも可能。
データ(要素)ではなく、行名・列名が特定の文字列を含む行・列を抽出するにはfilter()メソッドを使う。以下の記事を参照。
本記事のサンプルコードのpandasのバージョンは以下の通り。以下のpandas.DataFrameを例として使う。
import pandas as pd
print(pd.__version__)
# 2.0.3
df = pd.read_csv('data/src/sample_pandas_normal.csv').head(3)
print(df)
# name age state point
# 0 Alice 24 NY 64
# 1 Bob 42 CA 92
# 2 Charlie 18 CA 70
例はpandas.DataFrameだが、pandas.Seriesでも同様。
条件を満たす行を抽出する方法(ブーリアンインデックス)
pandas.DataFrameに対して、真偽値型bool(True, False)を要素とするリストやpandas.Seriesを[]で指定すると、Trueの行が抽出される。ブーリアンインデックス(Boolean indexing)と呼ばれる。
mask = [True, False, True]
print(df[mask])
# name age state point
# 0 Alice 24 NY 64
# 2 Charlie 18 CA 70
したがって、所望の条件に対応するboolのpandas.Seriesを取得できれば、その行を抽出できる。
複数条件で抽出する場合は&(AND)、|(OR)を使う。~(NOT)も使用可能。詳細は以下の記事を参照。
特定の文字列と完全一致: ==, isin()
==を使うと、指定した文字列と完全一致する要素がTrueとなるpandas.Seriesを取得できる。
print(df['state'] == 'CA')
# 0 False
# 1 True
# 2 True
# Name: state, dtype: bool
print(df[df['state'] == 'CA'])
# name age state point
# 1 Bob 42 CA 92
# 2 Charlie 18 CA 70
また、pandas.Seriesのisin()メソッドは、引数に指定したリストのいずれかの要素に完全一致する要素に対してTrueを返す。
指定した複数の文字列のいずれかと完全一致する要素を抽出するにはこちらを使う。
print(df['state'].isin(['NY', 'CA']))
# 0 True
# 1 True
# 2 True
# Name: state, dtype: bool
print(df[df['state'].isin(['NY', 'CA'])])
# name age state point
# 0 Alice 24 NY 64
# 1 Bob 42 CA 92
# 2 Charlie 18 CA 70
特定の文字列を含む(部分一致): str.contains()
str.contains()を使うと、要素が特定の文字列を含むとTrueとなるpandas.Seriesを取得できる。
print(df['name'].str.contains('li'))
# 0 True
# 1 False
# 2 True
# Name: name, dtype: bool
print(df[df['name'].str.contains('li')])
# name age state point
# 0 Alice 24 NY 64
# 2 Charlie 18 CA 70
後述のように、第一引数に指定した文字列はデフォルトで正規表現パターンとして処理されるので注意。
欠損値NaNの処理: 引数na
要素が欠損値NaNである場合、デフォルトではTrueでもFalseでもなくNaNを返す。このため、そのpandas.Seriesを使って行を抽出するとエラーになる。
df_nan = df.copy()
df_nan.iloc[2, 0] = float('nan')
print(df_nan)
# name age state point
# 0 Alice 24 NY 64
# 1 Bob 42 CA 92
# 2 NaN 18 CA 70
print(df_nan['name'].str.contains('li'))
# 0 True
# 1 False
# 2 NaN
# Name: name, dtype: object
# print(df_nan[df_nan['name'].str.contains('li')])
# ValueError: Cannot mask with non-boolean array containing NA / NaN values
str.contains()の引数naにNaNの結果を置き換える値を指定できる。
print(df_nan['name'].str.contains('li', na=False))
# 0 True
# 1 False
# 2 False
# Name: name, dtype: bool
print(df_nan['name'].str.contains('li', na=True))
# 0 True
# 1 False
# 2 True
# Name: name, dtype: bool
条件として使う場合、na=TrueとすればNaNの行も選択され、na=FalseとすればNaNの行は選択されない。
大文字小文字の処理: 引数case
デフォルトでは大文字と小文字は区別して処理される。引数caseをFalseとすると大文字小文字が区別されない。
print(df['name'].str.contains('LI'))
# 0 False
# 1 False
# 2 False
# Name: name, dtype: bool
print(df['name'].str.contains('LI', case=False))
# 0 True
# 1 False
# 2 True
# Name: name, dtype: bool
正規表現パターンの使用: 引数regex, flags
str.contains()では、第一引数に指定した文字列はデフォルトで正規表現パターンとして処理される。
print(df['name'].str.contains('i.*e'))
# 0 True
# 1 False
# 2 True
# Name: name, dtype: bool
引数regexをFalseとすると、第一引数の文字列は正規表現パターンではなくそのままの文字列として扱われる。
print(df['name'].str.contains('i.*e', regex=False))
# 0 False
# 1 False
# 2 False
# Name: name, dtype: bool
例えば?や., *などの正規表現で特殊文字として扱われる文字自体を含むかどうかを判定したい場合はregex=Falseとする必要がある。もちろん、\?のように特殊文字をエスケープした正規表現パターンを指定してもよい。
デフォルトだとエラーになってしまう場合があるので注意。
df_q = df.copy()
df_q.iloc[2, 0] += '?'
print(df_q)
# name age state point
# 0 Alice 24 NY 64
# 1 Bob 42 CA 92
# 2 Charlie? 18 CA 70
# print(df_q['name'].str.contains('?'))
# error: nothing to repeat at position 0
print(df_q['name'].str.contains('?', regex=False))
# 0 False
# 1 False
# 2 True
# Name: name, dtype: bool
print(df_q['name'].str.contains(r'\?'))
# 0 False
# 1 False
# 2 True
# Name: name, dtype: bool
引数flagsでre.IGNORECASEなどの正規表現フラグを指定することも可能。また、str.contains()はre.search()に相当するが、後述のように、re.match()に相当するstr.match()もある。
特定の文字列で始まる(前方一致): str.startswith()
str.startswith()を使うと、要素が特定の文字列で始まるとTrueとなるpandas.Seriesを取得できる。
print(df['name'].str.startswith('B'))
# 0 False
# 1 True
# 2 False
# Name: name, dtype: bool
print(df[df['name'].str.startswith('B')])
# name age state point
# 1 Bob 42 CA 92
str.startswith()も引数naを持つ。欠損値NaNの行を選択したい場合はna=True、選択したくない場合はna=Falseとする。
引数caseはなく、常に大文字小文字が区別される。
また、第一引数の文字列がそのまま判定に使われ、正規表現パターンとして処理されることはない。
特定の文字列で終わる(後方一致): str.endswith()
str.endswith()を使うと、要素が特定の文字列で終わるとTrueとなるpandas.Seriesを取得できる。
print(df['name'].str.endswith('e'))
# 0 True
# 1 False
# 2 True
# Name: name, dtype: bool
print(df[df['name'].str.endswith('e')])
# name age state point
# 0 Alice 24 NY 64
# 2 Charlie 18 CA 70
str.endswith()も引数naを持つ。欠損値NaNの行を選択したい場合はna=True、選択したくない場合はna=Falseとする。
引数caseはなく、常に大文字小文字が区別される。
また、第一引数の文字列がそのまま判定に使われ、正規表現パターンとして処理されることはない。
先頭が正規表現のパターンに一致する: str.match()
str.match()を使うと、要素の先頭が正規表現のパターンに一致するとTrueとなるpandas.Seriesを取得できる。
print(df['name'].str.match('.*i'))
# 0 True
# 1 False
# 2 True
# Name: name, dtype: bool
print(df[df['name'].str.match('.*i')])
# name age state point
# 0 Alice 24 NY 64
# 2 Charlie 18 CA 70
上述のように、str.match()はre.match()に相当し、文字列の先頭がパターンにマッチするかを判定する。先頭にマッチしないとFalseとなる。
先頭に限らずパターンにマッチする部分を含むかどうかを判定したい場合は、上述のようにre.search()に相当するre.contains()をデフォルト(regex=True)で使用する。
print(df['name'].str.match('i.*e'))
# 0 False
# 1 False
# 2 False
# Name: name, dtype: bool
print(df['name'].str.contains('i.*e'))
# 0 True
# 1 False
# 2 True
# Name: name, dtype: bool
re.match()やre.search()についての詳細は以下の記事を参照。
str.match()はstr.contains()と同様に引数na, case, flagsを指定可能。