【Python入門】テキスト操作で便利な正規表現について機械学習認定講師が解説!

【Python入門】テキスト操作で便利な正規表現について機械学習認定講師が解説!

こんにちは、キカガク認定講師の新井です。

この記事では、正規表現とは何か、どのような場面で役立つのか、また正規表現を実際に Python で使うためにはどのような記述をするのか、などをご紹介いたします。おおよそ20分で読了できます。

キカガク認定講師 新井

人材業界でWebディレクターとして働く傍ら、独学でデータエンジニアリング、データマネジメントを学び、2023〜2024年にかけてキカガク長期コースを受講。長期コース修了後、データエンジニアへのキャリアチェンジに成功。

Python の正規表現とは何か?

正規表現とは?

正規表現を使うと、

  • 「ある条件に合致する文字列」がレコード内に存在するか判定する
  • 「ある条件に合致する文字列」を抽出する
  • 「ある条件に合致する文字列」を別の文字列に変換する

などデータの操作を行なうことができます。この「ある条件に合致する文字列」という条件を指定する記述を正規表現と呼びます。正規表現は Python に限定して使われるものではなく、さまざまなプログラミング言語で使用されています。

正規表現が使用される場面

一例として、以下のような顧客情報が格納されている変数 customer を考えます。

Python
customer = {
    "name" : "佐藤 一郎",
    "gender" : "male",
    "birthday" : "1996-08-24",
    "prefecture" : "東京都",
    "address1" : "渋谷区渋谷1-2-3",
    "address2" : "キカガクタワー1001"
}

この顧客が東京都に住んでいるかどうかを判定する場合、「prefecture の値が『東京都』に一致するか」を確認することで実現できます。

Python
print(customer["prefecture"] == "東京都")
True

print(customer["prefecture"] == "沖縄県")
False

では、もしこの顧客が渋谷区に住んでいるかどうかを判定したい場合、どのようなコードを書けばいいでしょうか?

考え方としては、「address1 の値が『渋谷区』から始まっている」場合は渋谷区に住んでいると言えるので、そのような条件を表現するコードを記述する必要があります。このように、ある一定のルールに沿った文字列のパターンを表現する場合に正規表現が活躍します。

Pythonの正規表現の使い方

正規表現の関数を用いて操作

Pythonで正規表現を利用する方法はいくつかありますが、本稿では関数を使った方法をご紹介します。

関数を使って正規表現の処理を行なうには、 re モジュールをインポートする必要があります。
※ re は、「正規表現」を表す英語 Regular Expression の頭文字です。

Python
import re

match:文字列の先頭一致

文字列の先頭一致を確認するときは、 re.match() 関数を使い以下のように記述します。

Python
re.match(検索する文字列, 実行対象)

前述の例から、顧客が渋谷区に住んでいるかどうかを確認してみましょう。

Python
print(re.match("渋谷区", customer["address1"]))
<re.Match object; span=(0, 3), match='渋谷区'>

print(re.match("新宿区", customer["address1"]))
None

指定した条件に合致する場合、文字列の開始位置、終了位置、一致した文字列が返ってきます。条件に合致しない場合は None が返ってきます。

search:文字列の部分一致

文字列の部分一致の確認には re.search() 関数を使い以下のように記述します。

Python
re.search(検索する文字列, 実行対象)

顧客の住所に「渋谷」という文字列が含まれるかを確認してみましょう。

Python
print(re.search("渋谷", customer["address1"]))
<re.Match object; span=(0, 2), match='渋谷'>

print(re.search("品川", customer["address1"]))
None

re.match と同様、指定した条件に合致する場合、文字列の開始位置、終了位置、一致した文字列が返ってきます。条件に合致しない場合は None が返ってきます。

ここで、上記例の文字列の開始位置、終了位置に注目してください。

address1 には渋谷という文字列が 2 回出てきていますが、 re.search の結果では開始位置が 0、終了位置が 2 となっています。これは、1つ目の「渋谷」のみを検出していることを意味します。

このように、 re.search() 関数は条件に合致する文字列が複数ある場合、1回目に出現した文字列を検出して結果を返します。

fullmatch:文字列の完全一致

文字列の完全一致の確認は re.fullmatch() 関数を使い以下のように記述します。

Python
re.fullmatch(検索する文字列, 実行対象)

冒頭の「東京都に住んでいるかどうか」の例で確認してみましょう。 prefecture カラムの値が「東京都」と完全一致するかを判定します。

Python
print(re.fullmatch("東京都", customer["prefecture"]))
<re.Match object; span=(0, 3), match='東京都'>

print(re.fullmatch("東京", customer["prefecture"]))
None

「東京都」での一致を判定したときは開始位置、終了位置を含む結果が返却されています。それに対し「東京」での一致を判定したときは、部分一致ではあるが完全一致ではないため、 None が返ってきています。

findall:一致する部分すべてをリストで取得

re.findall() 関数は、指定した条件に一致した文字列をすべて含むリスト形式を返します。

Python
re.findall(検索する文字列, 実行対象)

re.search の項で1回目に出てきた「渋谷」しか検出できなかった例を使い、検証してみましょう。

Python
print(re.findall("渋谷", customer["address1"]))
['渋谷', '渋谷']

このように、2回出現する「渋谷」をどちらも検出し、 [‘渋谷’, ‘渋谷’] とリスト形式にして返しています。

finditer:一致する部分すべてをイテレータで取得

re.finditer() 関数は、 findall と同様に一致する部分をすべて検出しますが、検出した文字列を返すのではなく、 match や search と同様に検出した位置をイテレータという形式で返却します。

Python
re.finditer(検索する文字列, 実行対象)

イテレータとは、どのような内容をどのような順番で保存しているか、を表すものです。実際にコードを書いて確認してみましょう。

Python
print(re.finditer("渋谷", customer["address1"]))
<callable_iterator object at 0x7cb94911e350>

「渋谷」を2回検出しているはずですが、この時点ではイテレータが何を表すかを理解することができません。イテレータに保存されている情報がどのようなものか、 for 文で取り出してみましょう。

Python
for word in re.finditer("渋谷", customer["address1"]):
  print(word)
<re.Match object; span=(0, 2), match='渋谷'>
<re.Match object; span=(3, 5), match='渋谷'>

finditer の実行結果の中からすべての結果を出力すると、開始位置 0 と開始位置 3 の2か所の「渋谷」を検出できていることがわかります。

sub/subn:一致する部分を置換

re.sub() 関数は、指定した条件に合致する箇所を別の文字列に置き換えることができます。なお、 sub の本来の英単語は substitute (代わりとなるもの)です。

Python
re.sub(検索する文字列, 置き換え後の文字列, 実行対象)

re.subn(検索する文字列, 置き換え後の文字列, 実行対象)

サンプルの address2 カラムに含まれる建物名を置き換えてみましょう。

Python
print(re.sub("キカガクタワー", "渋谷キカガクタワー", customer["address2"]))
渋谷キカガクタワー1001

re.subn() 関数を使うと、文字列を置き換える処理に加え、置き換えた回数をカウントすることができます。 address1 の「渋谷」を「新宿」に置き換えてみましょう。

Python
print(re.subn("渋谷", "新宿", customer["address1"]))
('新宿区新宿1-2-3', 2)

「渋谷」が「新宿」に置き換えられた文字列に加え、置換された回数 2 が結果に含まれています。この2つの要素が、タプル形式で返ってきています。

split:指定した条件で文字列を分割

re.split() 関数を使うと、文字列を指定した条件で分割し、リスト形式で返すことができます。

Python
re.split(分割条件の文字列, 実行対象)

サンプルの name カラムにはフルネームが格納されていますが、名字と名前の間に半角スペースが含まれています。この半角スペースを文字列分割の条件として、名字と名前に分割してみましょう。

Python
print(re.split(" ", customer["name"]))
['佐藤', '一郎']

半角スペースの左右にある名字と名前に要素が分割され、リスト形式で結果を得ることができました。

まとめ

いかがでしたでしょうか?正規表現操作の re モジュールを使うことで、ある一定のルールに沿った文字列のパターンに対し、文字列を検出する、文字列を置き換えるといった操作を行なうことが可能です。

データサイエンスの文脈では、データの前処理において文字列の一部変換や一部削除が求められるケースが多くあります。正規表現をうまく活用することで、効率よく前処理を行なうことができるので、ぜひ習得してください。