見出し画像

日本語の文章から正規表現による数値の取得(python)

作成者:RMR(Rosso Machinelearning Reportage)
マイブーム:AtCoder(開始5ヶ月で水色コーダー到達)
特技:ルービックキューブ20秒以内

はじめに

データはいつも簡単に手に入るとは限りません。
文字列に紛れ込んでいる数値を大量に取得する必要があるかもしれません。

  • 利用者4億5千万人→450000000

  • 新規契約五千三百人→5300

  • 2.5万→25000

「億、万、千」に対応するために正規表現で無理やり突破してみます。


日本語が混ざった数値の取得

まず前処理。全角→半角には便利なmojimojiを使用。

import mojimoji

#漢数字を半角数字に
trans_dict = {"〇":"0","一":"1","二":"2","三":"3","四":"4","五":"5","六":"6","七":"7","八":"8","九":"9"}

def preprocess(text):
    text = mojimoji.zen_to_han(text) # 1->1
    text = text.replace(",","") #桁区切りを除外 3,600 -> 3600
    text = text.translate(str.maketrans(trans_dict)) #四 -> 4
    return text
import re

pattern = "((?P<num1>[\d\.]+)\s?(?P<suffix1>億|千万|百万|十万|万|千|百|十)*)" \
        + "((?P<num2>[\d\.]+)*\s?(?P<suffix2>億|千万|百万|十万|万|千|百|十)*)" \
        + "((?P<num3>[\d\.]+)*\s?(?P<suffix3>億|千万|百万|十万|万|千|百|十)*)"
re_num = re.compile(pattern)

text = "一億四千万二百"
text = preprocess(text)
m = re_num.search(text)

print(m)
#<re.Match object; span=(0, 7), match='1億4千万2百'>

[\d\.]で「1.4億」などの表記に対応可能。

(?P<num1>[\d\.]+)で各部位ごとに名前をつけることが出来、
m.group("num1")と指定することで取得できます

for a in ["num1","suffix1","num2","suffix2","num3","suffix3"]:
    print(m.group(a))

"""
1
億
4
千万
2
百
"""

正規表現にマッチしたものを手に入れたら次は以下のような処理を行います。

1億4千万2百
= 1 * 億 + 4 * 千万 + 2 * 百
= 1 * 100000000 + 4 * 10000000 + 2 * 100
= 140000200

suffix_dict = {
        "十":10,
        "百":100,
        "千":1000,
        "万":10000,
        "十万":100000,
        "百万":1000000,
        "千万":10000000,
        "億":100000000
            }

def get_suffix_num(suffix):
    if suffix_dict.get(suffix)==None:
        return 1
    return suffix_dict[suffix]    

def get_num(match):
    if not match:
        return 0
    res = 0
    for i in [1,2,3]:
        n = match.group(f"num{i}")
        if n==None:
            continue
        suffix = match.group(f"suffix{i}")
        s = get_suffix_num(suffix)
        res += int(n) * s
    return res

m = re_num.search("1億4千万2百")
print(get_num(m))

#140000200

一つの文章内に複数の数字が含まれるのを処理したい場合はfinditer メソッドを使う。

text = "個人情報 1億4千2百件 (金額は3億2千万5百円)"
text = preprocess(text)
m_list = re_num.finditer(text)
for m in m_list:
    print(m)

"""
<re.Match object; span=(5, 11), match='1億4千2百'>
<re.Match object; span=(17, 24), match='3億2千万5百'>
"""

findallではマッチ情報でなくてタプルにされてしまうのでここでは使用しない。

text = "個人情報 1億4千2百件 (金額は3億2千万5百円)"
text = preprocess(text)
m_list = re_num.findall(text)
print(m_list[0])
#('1億', '1', '億', '4千', '4', '千', '2百', '2', '百')

まとめ

import mojimoji
import re


trans_dict = {"〇":"0","一":"1","二":"2","三":"3","四":"4","五":"5","六":"6","七":"7","八":"8","九":"9"}

def preprocess(text):
    text = mojimoji.zen_to_han(text) # 1->1
    text = text.replace(",","") #桁区切りを除外 3,600 -> 3600
    text = text.translate(str.maketrans(trans_dict)) #四 -> 4
    return text

pattern = "((?P<num1>[\d\.]+)\s?(?P<suffix1>億|千万|百万|十万|万|千|百|十)*)" \
        + "((?P<num2>[\d\.]+)*\s?(?P<suffix2>億|千万|百万|十万|万|千|百|十)*)" \
        + "((?P<num3>[\d\.]+)*\s?(?P<suffix3>億|千万|百万|十万|万|千|百|十)*)"
re_num = re.compile(pattern)

suffix_dict = {
        "十": 10,
        "百": 100,
        "千": 1000,
        "万": 10000,
        "十万":100000,
        "百万":1000000,
        "千万":10000000,
        "億":  100000000
            }

def get_suffix_num(suffix):
    
    if suffix_dict.get(suffix)==None:
        return 1
    return suffix_dict[suffix]    

def get_num(match):
    if not match:
        return 0
    res = 0
    for i in [1,2,3]:
        n = match.group(f"num{i}")
        if n==None:
            continue
        suffix = match.group(f"suffix{i}")
        s = get_suffix_num(suffix)
        res += float(n) * s
    return res




text = "個人情報 一億四千二百件 (金額は3億2千万5百円)企業数は2.5万"

text = preprocess(text)
m_list = re_num.finditer(text)
for m in m_list:
    num = get_num(m)
    print(m.group(),num)

"""
1億4千2百 100004200.0
3億2千万5百 320000500.0
2.5万 25000.0
"""

今回は日本語の文章に含まれる数値の正規表現による取得についての記事がすぐにはヒットしなかったので、自分で作ってみました。

正規表現の宿命として、おそらく上手く抽出できないパターンもあるので覚悟は必要だが、簡単な情報収集タスクにかける時間の多くが正規表現の調整になるようなことが少しでも減れば幸いです。