note.nkmk.me

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

Date: 2017-12-28 / Modified: 2019-09-03 / tags: Python, pandas, 正規表現

pandasで特定の文字列を含む要素を持つ行を抽出する方法について、以下の内容を説明する。

  • 行を抽出(選択)する方法
  • 完全一致
    • ==
  • 部分一致
    • str.contains(): 特定の文字列を含む
      • 引数na: 欠損値NaNの処理
      • 引数case: 大文字小文字の処理
      • 引数regex: 正規表現パターンの使用
    • str.endswith(): 特定の文字列で終わる
    • str.startswith(): 特定の文字列で始まる
    • str.match(): 正規表現のパターンに一致する

部分一致する行を抽出して抜き出すためには、pandasに準備されている文字列メソッド(str.xxx())を条件に応じて使う。

ここではブールインデックスを用いたレガシーな方法を説明するが、query()メソッドを使うとより簡潔に書ける。以下の記事を参照。

今回は例として以下のデータを使用する。

import pandas as pd

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

サンプルのcsvファイルはコチラ。

例はpandas.DataFrameだが、pandas.Seriesでも同様。

データ(要素)ではなく、行名や列名に対して文字列メソッドを適用し、条件を満たす行や列を抽出することもできる。以下の記事を参照。

スポンサーリンク

行を抽出(選択)する方法

まず、pandas.DataFrameから行を抽出(選択)して新しいpandas.DataFrameを取得する方法を示す。

真偽値boolのリスト(配列)またはpandas.Seriesを使うと、Trueの行だけが抽出(選択)できる。

mask = [True, False, True]
df_mask = df[mask]
print(df_mask)
#       name  age state  point
# 0    Alice   24    NY     64
# 2  Charlie   18    CA     70

したがって、文字列要素を持つ列に対して条件に応じたboolのリストを取得できればよい。

完全一致(==)

==を使うと、要素が文字列に完全一致すると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

str.contains(): 特定の文字列を含む

pandas.Seriesの文字列メソッド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

後述のように、第一引数に指定した文字列はデフォルトで正規表現パターンとして処理されるので要注意。

引数na: 欠損値NaNの処理

要素が欠損値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 index with vector 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: 正規表現パターンの使用

str.contains()を使う際に要注意なのが、第一引数に指定した文字列はデフォルトで正規表現パターンとして処理されるということ。

print(df['name'].str.contains('i.*e'))
# 0     True
# 1    False
# 2     True
# Name: name, dtype: bool

引数ragexFalseとすると第一引数の文字列そのものが含まれているかを判定する。

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('\?'))
# 0    False
# 1    False
# 2     True
# Name: name, dtype: bool

str.contains()re.search()に相当し、引数flagsで正規表現のフラグを指定することも可能。後述のように、re.match()に相当するstr.match()もある。

なお、この例のように末尾の?などを判定するのであれば次に説明するstr.endswith()のほうが簡単。

str.endswith(): 特定の文字列で終わる

pandas.Seriesの文字列メソッド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.startswith(): 特定の文字列で始まる

pandas.Seriesの文字列メソッド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.match(): 正規表現のパターンに一致する

pandas.Seriesの文字列メソッドstr.match()を使うと、要素が正規表現のパターンに一致するとTrueとなるpandas.Seriesを取得できる。

print(df['name'].str.match('.*i.*e'))
# 0     True
# 1    False
# 2     True
# Name: name, dtype: bool

print(df[df['name'].str.match('.*i.*e')])
#       name  age state  point
# 0    Alice   24    NY     64
# 2  Charlie   18    CA     70

上述のように、str.match()re.match()に相当し、文字列の先頭がパターンにマッチするかを判定する。先頭にマッチしないとFalseとなる。

print(df['name'].str.match('.*i'))
# 0     True
# 1    False
# 2     True
# Name: name, dtype: bool

print(df['name'].str.match('i.*e'))
# 0    False
# 1    False
# 2    False
# Name: name, dtype: bool

先頭に限らずパターンにマッチする部分を含むかどうかを判定したい場合は、上述のようにre.search()に相当するre.contains()をデフォルト(regex=True)で使用する。

re.match()re.search()についての詳細は以下の記事を参照。

str.match()str.contains()と同様に引数na, case, flagsを指定可能。

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事