note.nkmk.me

pandasの文字列から正規表現で抽出して新たな列を生成

Date: 2018-04-09 / Modified: 2019-08-06 / tags: Python, pandas

文字列を要素とするpandas.DataFrameの列、pandas.Seriesから正規表現で特定の文字列を抽出して新たな列を生成する方法を説明する。

以下の文字列メソッドを使う。

  • str.extract(): 最初のマッチ部分のみ抽出
  • str.extractall(): すべてのマッチ部分を抽出

pandas.DataFrameの列から抽出したい場合はdf['列名']のように列(= pandas.Series)を指定してstr.extract()str.extractall()を呼べばOK。

文字列の位置・長さを指定して抽出する場合はスライスを使うと簡単。以下の記事を参照。

スポンサーリンク

str.extract(): 最初のマッチ部分のみ抽出

以下のpandas.Seriesを例とする。

import pandas as pd

s_org = pd.Series(['aaa@xxx.com', 'bbb@yyy.net', 'ccc@zzz.co.jp'], index=['A', 'B', 'C'])
print(s_org)
# A      aaa@xxx.com
# B      bbb@yyy.net
# C    ccc@zzz.co.jp
# dtype: object

正規表現の最初のマッチ部分のみ抽出するにはstr.extract()メソッドを使う。

str.extract()の第一引数に正規表現パターンを指定すると() で囲まれたグループ部分にマッチする文字列が抽出される。

引数expandTrueとするとpandas.DataFrameFalseとするとpandas.Seriesとして新たなオブジェクトが生成される。

df_single = s_org.str.extract('(.+)@', expand=True)
print(df_single)
print(type(df_single))
#      0
# A  aaa
# B  bbb
# C  ccc
# <class 'pandas.core.frame.DataFrame'>

s = s_org.str.extract('(.+)@', expand=False)
print(s)
print(type(s))
# A    aaa
# B    bbb
# C    ccc
# dtype: object
# <class 'pandas.core.series.Series'>

バージョン0.22.0ではexpand=Falseがデフォルトだが、将来的にはexpand=Trueがデフォルトになるとのこと。バージョンによって結果が変わってしまうのでexpandは明示的に指定しておいたほうが無難。

FutureWarning: currently extract(expand=None) means expand=False (return Index/Series/DataFrame)
but in a future version of pandas this will be changed to expand=True (return DataFrame)

正規表現パターンに名前付きグループ(?P<name>...)を使うと名前がそのまま列名(カラム名)になる。

df_name = s_org.str.extract('(?P<local>.+)@', expand=True)
print(df_name)
print(type(df_name))
#   local
# A   aaa
# B   bbb
# C   ccc
# <class 'pandas.core.frame.DataFrame'>

()で囲まれたグループが複数あると、各グループで抽出された部分がそれぞれ列となるpandas.DataFrameが返る。この場合は引数expandTrueでもFalseでもpandas.DataFrame

デフォルトでは0始まりの連番が列名となり、名前付きグループ(?P<name>...)を使うとそれが列名となる。

print(s_org.str.extract('(.+)@(.+)'))
#      0          1
# A  aaa    xxx.com
# B  bbb    yyy.net
# C  ccc  zzz.co.jp

print(s_org.str.extract('(?P<local>.+)@(?P<domain>.+)'))
#   local     domain
# A   aaa    xxx.com
# B   bbb    yyy.net
# C   ccc  zzz.co.jp

なお、この例の場合はstr.split()メソッドで区切り文字を@として分割することも可能。文字列の分割については以下の記事を参照。

マッチする部分がない場合はNaNとなる。

print(s_org.str.extract('(a+)', expand=True))
#      0
# A  aaa
# B  NaN
# C  NaN

str.extractall(): すべてのマッチ部分を抽出

以下のpandas.Seriesを例とする。

s_org2 = pd.Series(['aaa@xxx.com, iii@xxx.com', 'bbb@yyy.net, jjj@yyy.net', 'ccc@zzz.co.jp'],
                   index=['A', 'B', 'C'])
print(s_org2)
# A    aaa@xxx.com, iii@xxx.com
# B    bbb@yyy.net, jjj@yyy.net
# C               ccc@zzz.co.jp
# dtype: object

str.extract()は最初のマッチ部分のみを返すので、以下のような結果となる。

print(s_org2.str.extract('([a-z]+)@([a-z.]+)', expand=True))
#      0          1
# A  aaa    xxx.com
# B  bbb    yyy.net
# C  ccc  zzz.co.jp

すべてのマッチ部分を抽出するにはstr.extractall()メソッドを使う。

str.extractall()の結果は以下の通り。str.extractall()には引数expandは無く、常にindexがマルチインデックスのpandas.DataFrameを返す。

df_all = s_org2.str.extractall('([a-z]+)@([a-z.]+)')
print(df_all)
#            0          1
#   match                
# A 0      aaa    xxx.com
#   1      iii    xxx.com
# B 0      bbb    yyy.net
#   1      jjj    yyy.net
# C 0      ccc  zzz.co.jp

print(df_all.index)
# MultiIndex(levels=[['A', 'B', 'C'], [0, 1]],
#            labels=[[0, 0, 1, 1, 2], [0, 1, 0, 1, 0]],
#            names=[None, 'match'])

マルチインデックスのpandas.DataFrameの要素の指定・選択については以下の記事を参照。

マッチする部分が一つしかなくてもindexはマルチインデックスとなるので注意。str.extract()の例で使用したSeriesを使っている。

print(s_org.str.extractall('([a-z]+)@([a-z.]+)'))
#            0          1
#   match                
# A 0      aaa    xxx.com
# B 0      bbb    yyy.net
# C 0      ccc  zzz.co.jp
スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事