[Python] 正規表現を使ったFターム分析

〘 背景 〙

日本の特許文献には、FIとFタームと呼ばれる特許分類が付与されている。FIは国際特許分類(IPC)と同じ階層構造で、日本の実情に合わせて細分化されている。Fタームは、テーマコードごとに種々の観点について付与された特許分類コードである。出願された特許文献の内容に応じて付与されるので、内容を読む代わりに付与されているFIやFタームを分析対象とすることで、特許文献の内容分析ができる。
以下の例は、事例研究「リチウムイオン電池の電極材料の特許マップ」で紹介した特許マップを作成するための前処理。

〘 課題 〙

1件の特許文献には複数の(多くの)特許分類コードが付与されている。前処理として、どのコードが付与されているかを表すフラグを立てる。分析対象の特許文献それぞれにフラグを立てる処理をしておけば、後段に分析目的に合わせた集計処理を行えば良い。
特許分類は、FIもFタームもIPCも階層構造をもつので、立てるフラグは階層関係を反映するように設計する。

〘 仕様 〙

処理対象:EXCELの特許文献リスト()
各特許文献に、FI, Fターム, IPCなどの項目が含まれていること

入力 「Fターム」カラム
例=“5H050 AA02;5H050 AA08;5H050 BA17;5H050 CA08;5H050 CA09;5H050 CB01;5H050 CB02;5H050 CB08;5H050 CB11;5H050 DA03;5H050 DA04;5H050 DA10;5H050 DA11;5H050 DA18;5H050 EA23;5H050 EA24;5H050 EA28;5H050 FA02;5H050 FA17;5H050 GA10;5H050 HA00;5H050 HA01;5H050 HA20
出力 フラグを立てる複数のカラム
特許文献のレコードに立てたフラグ(1/” “

注:フラグを立てる対象のFタームが上位階層なら、それに含まれる下位階層のFタームでもフラグが立つようにプログラミングする。但し、階層関係はプログラマーが解釈してソースコード内に正規表現で記述する。

〘 階層構造を考慮した正規表現〙

Fタームの階層構造の例は、以下のとおり。上述の「リチウムイオン電池の電極材料の特許マップ」で分析対象としたテーマコード「電池の電極および活物質」(5H050)の「正極活物質」(CA00)である。

フラグを立てたい範囲を緑の枠取りで示す。フラグを立てたい範囲を正規表現で表す(各正規マッチマッチオブジェクトを生成する)。

例1:CA03(Niを主体とするもの)の下位概念にはCA04(Coを固溶するもの)を下位に含むので、CA03 or CA04が付与されていればCA03のフラグ(CA01)を立てる。

CA01 = re.compile(‘5H050 (CA03|CA04)’) 

例2:CA19(有機化合物)の下位概念にはCA20(ポリマーまたは重合体)~CA27(ハロゲン原子を有するもの)を下位に含むので、CA19 ~ CA27が付与されていればCA19のフラグ(CA12)を立てる。

CA12 = re.compile(‘5H050 (CA19|CA2[0-7])’)

フラグを立てたいすべてのFタームについて、マッチオブジェクトを生成する。

〘 各レコード(特許文献)についてフラグを立てる 〙

searchメソッドを使って、マッチオブジェクトにマッチするパターンが、対象レコード(特許文献)のFタームカラムの文字列に含まれているかを判定し(if文)、EXCELの所定カラムに出力する。

〘 ソースコード 〙

# -*- coding: utf-8 -*-
"""
Fターム分析
 電池の電極および活物質(5H050)のLiイオン電池(BA17)にヒットする文献について、
 正極と負極の活物質材料として付与されているFタームのフラグを立てる
 入力: ダウンロード項目にFタームを含む特許文献リスト
 出力: 各文献について、付与されているFタームにフラグを立てる
 newly created                           2022.9.19 H. Kojima
"""
import openpyxl, re
path = "C:\\Users\\kojim\\Dropbox\\事例研究\\Liイオン電池の電極材料分析"
file_name  = "5H050_DB.xlsx"
workbook = openpyxl.load_workbook(path+"\\"+file_name)
# 特許文献シート
sheet_db = workbook.get_sheet_by_name('db')
col_Fterm = 17
# 正極活物質
CA01 = re.compile('5H050 (CA03|CA04)')
CA02 = re.compile('5H050 CA05')
CA03 = re.compile('5H050 CA06')
CA04 = re.compile('5H050 (CA07|CA08|CA09)')
CA05 = re.compile('5H050 CA10')
CA06 = re.compile('5H050 CA11')
CA07 = re.compile('5H050 CA12')
CA08 = re.compile('5H050 CA15')
CA09 = re.compile('5H050 CA16')
CA10 = re.compile('5H050 CA14')
CA11 = re.compile('5H050 CA17')
CA12 = re.compile('5H050 (CA19|CA2[0-7])')
CA13 = re.compile('5H050 (CA29|CA30)')
# 負極活物質
CB01 = re.compile('5H050 (CB02|CB03)')
CB02 = re.compile('5H050 CB04')
CB03 = re.compile('5H050 CB05')
CB04 = re.compile('5H050 CB08')
CB05 = re.compile('5H050 CB09')
CB06 = re.compile('5H050 CB07')
CB07 = re.compile('5H050 CB12')
CB08 = re.compile('5H050 CB13')
CB09 = re.compile('5H050 CB14')
CB10 = re.compile('5H050 CB15')
CB11 = re.compile('5H050 (CB16|CB17|CB18)')
CB12 = re.compile('5H050 (CB19|CB2[0-7])')
CB13 = re.compile('5H050 (CB29|CB30)')
#
#   Start processing
for line in range(2, sheet_db.max_row+1):
    Fterm = sheet_db.cell(row=line,column=col_Fterm).value
    if re.search(CA01, Fterm):
        sheet_db.cell(row=line,column=37).value = 1
    if re.search(CA02, Fterm):
        sheet_db.cell(row=line,column=38).value = 1
    if re.search(CA03, Fterm):
        sheet_db.cell(row=line,column=39).value = 1  
    if re.search(CA04, Fterm):
        sheet_db.cell(row=line,column=40).value = 1  
    if re.search(CA05, Fterm):
        sheet_db.cell(row=line,column=41).value = 1  
    if re.search(CA06, Fterm):
        sheet_db.cell(row=line,column=42).value = 1  
    if re.search(CA07, Fterm):
        sheet_db.cell(row=line,column=43).value = 1  
    if re.search(CA08, Fterm):
        sheet_db.cell(row=line,column=44).value = 1  
    if re.search(CA09, Fterm):
        sheet_db.cell(row=line,column=45).value = 1
    if re.search(CA10, Fterm):
        sheet_db.cell(row=line,column=46).value = 1
    if re.search(CA11, Fterm):
        sheet_db.cell(row=line,column=47).value = 1
    if re.search(CA12, Fterm):
        sheet_db.cell(row=line,column=48).value = 1
    if re.search(CA13, Fterm):
        sheet_db.cell(row=line,column=49).value = 1
    if re.search(CB01, Fterm):
        sheet_db.cell(row=line,column=51).value = 1
    if re.search(CB02, Fterm):
        sheet_db.cell(row=line,column=52).value = 1
    if re.search(CB03, Fterm):
        sheet_db.cell(row=line,column=53).value = 1 
    if re.search(CB04, Fterm):
        sheet_db.cell(row=line,column=54).value = 1 
    if re.search(CB05, Fterm):
        sheet_db.cell(row=line,column=55).value = 1 
    if re.search(CB06, Fterm):
        sheet_db.cell(row=line,column=56).value = 1 
    if re.search(CB07, Fterm):
        sheet_db.cell(row=line,column=57).value = 1 
    if re.search(CB08, Fterm):
        sheet_db.cell(row=line,column=58).value = 1 
    if re.search(CB09, Fterm):
        sheet_db.cell(row=line,column=59).value = 1 
    if re.search(CB10, Fterm):
        sheet_db.cell(row=line,column=60).value = 1
    if re.search(CB11, Fterm):
        sheet_db.cell(row=line,column=61).value = 1 
    if re.search(CB12, Fterm):
        sheet_db.cell(row=line,column=62).value = 1
    if re.search(CB13, Fterm):
        sheet_db.cell(row=line,column=63).value = 1 
workbook.save(path+"\\"+file_name)

〘 出力(フラグを出力したEXCELのカラム)〙

[Python] 基礎-5 正規表現(regular expression)

正規表現(regular expression)(末尾のまとめ表)を使った、高機能な文字列

1. 一般的な流れ

1.1 関数

  1. ”re”モジュールをインポート:import re
  2. プログラム中に関数

match関数: re.match(r’ 正規表現’, 検査対象文字列)
       戻り値 ⇒ 先頭が一致なら一致した文字列/不一致ならNONE

search関数: re.search(r’ 正規表現’, 検査対象文字列)
       戻り値 ⇒ 検査対象文字列中の正規表現に合致する文字列/不一致ならNONE

findall関数: re.findall(r’ 正規表現’, 検査対象文字列)
       戻り値 ⇒ 検査対象文字列中の正規表現に合致する全文字列のリスト

オブジェクトを構成するspan( 始点 , 終点 )match(マッチしたパターン)を取得する
始点:re.search(r’ 正規表現’, 検査対象文字列) .start()
終点:re.search(r’ 正規表現’, 検査対象文字列) .end()
span:re.search(r’ 正規表現’, 検査対象文字列) .span()
マッチしたパターン:re.search(r’ 正規表現’, 検査対象文字列) .group()
マッチしたパターンのリスト:re.search(r’ 正規表現’, 検査対象文字列) .groups()

1.2 searchメソッド:正規表現に合致する文字列を取得する

  1. ”re”モジュールをインポート:import re
  2. Regexオブジェクトを生成:re_obj = re.compile(正規表現)
  3. Regexオブジェクトにsearch(検索対象文字列)メソッドを作用させてMatchオブジェクトを返す:match_obj = re_obj.search(‘検索対象文字列’)
    (’検索対象文字列’の中で、2の正規表現に一致する部分をMatchオブジェクトとして返す)
  4. group()メソッドを使って、Matchオブジェクトからマッチした文字列を取得:match_obj.group()
注:バックスラッシュ「\」は、円マーク「¥」で表示される場合がある

市外局番は多数桁もあり、局番も1~4桁までいろいろある。さらにハイフン「-」ではなくカッコ「(局番)」が使われる場合もある。正規表現なら複雑な表現にも対応可能。

\d{2,5}: 2~5桁の数字
[-()]: 「-」「(」「)」のうちのいずれか1文字
\d{1,4}: 1~4桁の数字
[-)]: 「-」「)」のうちのいずれか1文字
\d{4}: 4桁の数字
(詳しくは、「まとめ表」を参照)

2. group()メソッド、groups()メソッド

複数のパターンマッチングを並行に行う
複数のグループを含む正規表現を定義・・・カッコ( )でグルーピング
match_obj.group(数字)で、マッチしたグループを個別に参照

groups()メソッドを使えば、マッチした複数のグループをタプル形式で取得できる
タプルから変数への複数代入を使えば、1行のコマンドで複数の変数に代入できる

3. 貪欲マッチ(greedy match)/非貪欲マッチ

貪欲マッチ(greedy match):ある正規表現にマッチする複数のパターン(文字列)があるときに、最も長いものがマッチとして扱われる(デフォルト)

非貪欲マッチ:ある正規表現にマッチする複数のパターン(文字列)があるときに、最も長いものがマッチとして扱われる(正規表現の後ろに「」を付ける)

例:
正規表現:(Ha){3,5}
検査対象:HaHaHaHaHa
Haが3回、4回、5回の三通り、さらに3回、4回なら位置も含めれば、6通りのマッチパターンがあるが、貪欲マッチでは最も長いものがマッチとして扱われる。

4. findall()メソッド

search()メソッドが、最初にマッチした文字列のmatchオブジェクトを返すのに対して、
findall()メソッドは、マッチしたすべての文字列をタプル形式で返す。

5. sub()メソッド

正規表現にマッチした文字列の置換。

regex_obj = re.compile(正規表現)
regex_obj.sub(変換先, 変換対象のテキスト)

大文字と小文字が混在したpythonをすべて大文字のPYTHONに置換

マッチした一部を再利用した置換

regex_obj = re.compile(括弧()を使った正規表現)  # group参照
regex_obj.sub(変換先(マッチした順に\1, \2, \3を使って表現), 変換対象のテキスト)

「特開xxxx-yyyyyy」を「JPxxxxyyyyyyA1」に置換。ヒットした番号部分は、置換後にも残す。

6. オプション

6.1 re.IGNORECASEオプション:大文字/小文字を無視したマッチ

re.IGNORECASE (re.Iと省略可) オプションの指定により、大文字と小文字を区別しないでマッチを探す探索ができる。

6.2 re.DOTALLオプション:ドット「.」文字を改行にもマッチ

ドット「.」は、改行を除く任意の1文字にマッチする(「まとめ表」参照)が、
re.DOTALLオプションを指定することによって、改行を含む任意の1文字にマッチさせることができる。

【請求項n】ごとに分けてリストclaimを作ろうとしたが、うまくいかない。
請求項には、改行が含まれることがよくある。墨付け括弧【請求項n】をキーワードとして分離したいが、DOTALLオプションの効果で【請求項n】もマッチしてしまい、末尾まですべてが請求項1になってしまった。

6.3 re.VERBOSEオプション:正規表現を複数行にわけてコメント

長く複雑な正規表現を複数行に分けて記述して、それぞれの行にコメントをつけて、わかりやすくする。複数行にまたがるため三連クォートを使う。

6.4 複数のオプション

縦棒「」で並列表記。re.compileの引数の数は限られているので、第2引数にORで指定するイメージ。

注:この例ではre.Iを指定する意味はないが。

まとめ表

 短縮形、記号 意味
\d0~9の数字
\D0~9の数字以外
\w文字、数字、下線(”_”)
\W{文字、数字、下線(”_”)}以外
\sスペース、タブ、改行
\S{スペース、タブ、改行}以外
^先頭 ex.: ^\d :0~9の数字から始まる文字列
$末尾 ex.: \d$ :0~9の数字で終わる文字列
.(ドット)任意の1文字(改行を除く)
\n改行
\tタブ
?直前のグループの 0~1回の出現にマッチ ex.: \d?=0-1桁の数字
*直前のグループの 0回以上の出現にマッチ ex.: \d*=0桁以上の数字
+直前のグループの 1回以上の出現にマッチ ex.: \d*=1桁以上の数字
[複数文字]複数文字の中のいずれか1文字にマッチ ex.: [a-z]=小文字の英字
[^複数文字]複数文字以外の1文字にマッチ ex.: [^a-z]=小文字の英字以外
|(縦棒)複数グループのうちの1つにマッチ ex.: [a-z]|[0-9]=小文字の英字or数字
{n}直前のグループのn回の出現にマッチ ex.: \d{4}=4桁の数字
{n,m}直前のグループのn~m回の出現にマッチ ex.: \d{4,6}=4-6桁の数字
{n,}直前のグループのn回以上の出現にマッチ ex.: \d{4,}=4桁以上の数字
{,m}直前のグループの0~m回の出現にマッチ ex.: \d{,6}=0-6桁の数字