pandasの文字列を区切り文字や正規表現で複数の列に分割
pandasで文字列の列を区切り文字や正規表現パターンで複数の列に分割するには文字列メソッドstr.split(), str.extract()を使う。
ここでは以下の内容について説明する。
- 区切り文字・正規表現パターンで分割:
str.split()- 区切り文字・正規表現パターンを指定: 引数
pat,regex - 複数の列に分割: 引数
expand - 最大分割回数を指定: 引数
n
- 区切り文字・正規表現パターンを指定: 引数
- 正規表現にマッチした部分を抽出して分割:
str.extract() pandas.DataFrameの場合
文字列メソッドはpandas.Seriesのメソッド。pandas.Seriesまたはpandas.DataFrameの列(= pandas.Series)から呼べる。
pandasにおける正規表現による文字列の置換や抽出は以下の記事を参照。
区切り文字・正規表現パターンで分割: str.split()
区切り文字(デリミタ)または正規表現パターンで分割するには、文字列メソッドstr.split()を使う。
区切り文字・正規表現パターンを指定: 引数pat, regex
以下のpandas.Seriesを例とする。
import pandas as pd
print(pd.__version__)
# 1.5.3
s_org = pd.Series(['aaa@xxx.com', 'bbb@yyy.com', 'ccc'], index=['A', 'B', 'C'])
print(s_org)
# A aaa@xxx.com
# B bbb@yyy.com
# C ccc
# dtype: object
print(type(s_org))
# <class 'pandas.core.series.Series'>
第一引数patに区切り文字を指定する。分割された文字列のリストを要素とするpandas.Seriesが返される。引数patを省略すると空白で分割される。
s = s_org.str.split('@')
print(s)
# A [aaa, xxx.com]
# B [bbb, yyy.com]
# C [ccc]
# dtype: object
print(type(s))
# <class 'pandas.core.series.Series'>
pandas1.4.0より前のバージョンでは引数patは常に通常の文字列として扱われていたが、1.4.0で引数regexが追加され、その設定によって引数patの扱いが変わるようになった。
デフォルト(regex=None)では、文字数が1の場合は通常文字列、それ以外の場合は正規表現パターンとして扱われる。
print(s_org.str.split(r'@.+\.'))
# A [aaa, com]
# B [bbb, com]
# C [ccc]
# dtype: object
引数patにコンパイルした正規表現パターンを指定することも可能。
import re
pat = re.compile(r'@.+\.')
print(s_org.str.split(pat))
# A [aaa, com]
# B [bbb, com]
# C [ccc]
# dtype: object
引数patは、regex=Trueで常に正規表現パターンとして、Falseで常に通常の文字列として扱われる。1文字の正規表現パターンや2文字以上の通常文字列を指定したい場合は、TrueまたはFalseを指定すればよい。regex=Falseとした場合、patにコンパイルした正規表現パターンを指定するとエラーになるので注意。
複数の列に分割: 引数expand
複数の列に分割してpandas.DataFrameとして取得するには、引数expand=Trueを指定する。デフォルトはexpand=False。
分割数が少ない行の足りない分の要素はNoneとなる。
df = s_org.str.split('@', expand=True)
print(df)
# 0 1
# A aaa xxx.com
# B bbb yyy.com
# C ccc None
print(type(df))
# <class 'pandas.core.frame.DataFrame'>
取得したpandas.DataFrameの列名は0始まりの連番。columns属性などで変更できる。
df.columns = ['local', 'domain']
print(df)
# local domain
# A aaa xxx.com
# B bbb yyy.com
# C ccc None
複数の列に分割せずリストのまま処理したほうが便利なことも多い。以下の記事を参照。
- 関連記事: pandasの要素としてリストを格納し処理
最大分割回数を指定: 引数n
引数nで最大分割回数を指定できる。デフォルトはn=-1で、すべての区切り文字・正規表現パターンで分割される。
s_org = pd.Series(['a-b-c-d', 'x-y-z', '1'], index=['A', 'B', 'C'])
print(s_org)
# A a-b-c-d
# B x-y-z
# C 1
# dtype: object
print(s_org.str.split('-'))
# A [a, b, c, d]
# B [x, y, z]
# C [1]
# dtype: object
print(s_org.str.split('-', n=1))
# A [a, b-c-d]
# B [x, y-z]
# C [1]
# dtype: object
正規表現にマッチした部分を抽出して分割: str.extract()
正規表現にマッチした部分を抽出して分割するには文字列メソッドstr.extract()を使う。
以下のpandas.Seriesを例とする。
s_org = pd.Series(['aaa@xxx.com', 'bbb@yyy.com', 'ccc'], index=['A', 'B', 'C'])
print(s_org)
# A aaa@xxx.com
# B bbb@yyy.com
# C ccc
# dtype: object
第一引数patに正規表現パターンを指定する。正規表現の()で囲まれたグループ部分にマッチする文字列ごとに分割される。複数のグループが抽出される場合はpandas.DataFrameが返される。マッチしない場合はNaN。
df = s_org.str.extract(r'(.+)@(.+)\.(.+)')
print(df)
# 0 1 2
# A aaa xxx com
# B bbb yyy com
# C NaN NaN NaN
グループが一つの場合は引数expand=Trueだとpandas.DataFrame、expand=Falseだとpandas.Seriesを返す。デフォルトはexpand=True。
df = s_org.str.extract(r'(\w+)', expand=True)
print(df)
# 0
# A aaa
# B bbb
# C ccc
print(type(df))
# <class 'pandas.core.frame.DataFrame'>
s = s_org.str.extract(r'(\w+)', expand=False)
print(s)
# A aaa
# B bbb
# C ccc
# dtype: object
print(type(s))
# <class 'pandas.core.series.Series'>
正規表現パターンに名前付きグループ(?P<name>...)を使うと名前がそのまま列名(カラム名)になる。
df_name = s_org.str.extract(
r'(?P<local>.*)@(?P<second_LD>.*)\.(?P<TLD>.*)', expand=True
)
print(df_name)
# local second_LD TLD
# A aaa xxx com
# B bbb yyy com
# C NaN NaN NaN
引数flagsに正規表現のフラグ(re.IGNORECASEなど)を指定できる。詳細は以下の記事を参照。
なお、マッチ部分が複数ある場合、str.extract()では最初のマッチ部分のみ抽出される。すべてのマッチ部分を抽出するにはstr.extractall()メソッドを使う。以下の記事を参照。
pandas.DataFrameの場合
pandas.DataFrameの特定の列を複数の列に分割して更新する例を紹介する。もっといいやり方があるかもしれない。
以下、str.split()を例として用いるが、str.extract()でも考え方は同じ。
先に作成したpandas.DataFrameを例とする。
print(df)
# local domain
# A aaa xxx.com
# B bbb yyy.com
# C ccc None
特定の列にstr.split()を使うと、分割されたpandas.DataFrameが得られる。
print(df['domain'].str.split('.', expand=True))
# 0 1
# A xxx com
# B yyy com
# C None None
これをpd.concat()を使って元のpandas.DataFrameと連結(結合)し、元の列をdrop()メソッドで削除する。
df2 = pd.concat([df, df['domain'].str.split('.', expand=True)], axis=1).drop(
'domain', axis=1
)
print(df2)
# local 0 1
# A aaa xxx com
# B bbb yyy com
# C ccc None None
連結(結合)するときに元のpandas.DataFrameから必要な列だけ選択してもいい。
df3 = pd.concat([df['local'], df['domain'].str.split('.', expand=True)], axis=1)
print(df3)
# local 0 1
# A aaa xxx com
# B bbb yyy com
# C ccc None None
特定の列名を変更するにはrename()メソッドを使う。
df3.rename(columns={0: 'second_LD', 1: 'TLD'}, inplace=True)
print(df3)
# local second_LD TLD
# A aaa xxx com
# B bbb yyy com
# C ccc None None