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