pandasで特定の文字列を含む行を抽出(完全一致、部分一致)

Modified: | Tags: Python, 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に対して、真偽値型boolTrue, 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

したがって、所望の条件に対応するboolpandas.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.Seriesisin()メソッドは、引数に指定したリストのいずれかの要素に完全一致する要素に対して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()の引数naNaNの結果を置き換える値を指定できる。

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

デフォルトでは大文字と小文字は区別して処理される。引数caseFalseとすると大文字小文字が区別されない。

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

引数regexFalseとすると、第一引数の文字列は正規表現パターンではなくそのままの文字列として扱われる。

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

引数flagsre.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を指定可能。

関連カテゴリー

関連記事