pandasの文字列を区切り文字や正規表現で複数の列に分割

Modified: | Tags: Python, 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

複数の列に分割せずリストのまま処理したほうが便利なことも多い。以下の記事を参照。

最大分割回数を指定: 引数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.DataFrameexpand=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

関連カテゴリー

関連記事