[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] 基礎-7 EXCELファイルの読み書き

1.一般的な流れ

“openpyxl”モジュールをインポート:import openpyxl
Workbookを開く:openpyxl.load_workbook(“ファイル名”)
シートオブジェクトを生成:sheet_obj = workbook.get_sheet_by_name(“シート名”)

2.セルの値の読み書き(cell(row=行番号, column=列番号).value)

〔 値の取り込み 〕
変数 = sheet_obj.cell(row=行番号, column=列番号).value

〔 値の書き込み 〕
sheet_obj.cell(row=行番号, column=列番号).value = 値

3.行/列 単位の扱い( iter_rows() / iter_cols() )

(iteration processにより)1行ごと(1列ごと)に、リストを生成する。

〔 使い方の例 〕

for row in sheet_obj.iter_rows():
for col in range(len(row)):
print(row[col])

4.フォントの指定

〔 流れ 〕
① Font関数をopenpyxlモジュールのstyleからインポート
  from openpyxl.styles import Font
② フォントの指定
  font = Font(name=’フォント名’, size=ポイント数, ・・・)
③ 指定したフォントをセルに指定
  sheet_obj.cell(row=line,column=col+1).font = font

パラメータ説明
nameフォント名/例:’游ゴシック’, ‘Arial’
sizeポイント数/例:「size=10」等整数値で指定
color色を16進6桁の文字列で指定
例:’FF0000’(赤), ’00FF00’(緑), ‘0000FF’(青)
underline下線を文字列で指定/例:’single’
bold太字/例:True, False
italic斜字/例:True, False

5.位置揃え

〔 流れ 〕
① Alignment関数をopenpyxlモジュールのstyleからインポート
  from openpyxl.styles import Alignment
② セルごとにAlignmentを指定
  sheet_obj.cell(row=line,column=col+1).alignment
  = Alignment(horizontal=’left’,
   vertical=’top’,
   wrapText=None)

〔 Alignment関数の引数とパラメータ 〕

    引数パラメータ説明
horizontal‘left’
‘center’
‘right’
左揃え
中央揃え
右揃え
vertical‘top’
‘center’
‘bottom’
上揃え
中央揃え
下揃え
textWrapTrue
None
セル内折り返しあり
指定せず

6.セルの表示形式

〔 流れ 〕
① numbersをopenpyxlモジュールのstyleからインポート
  from openpyxl.styles import numbers
② セルのフォーマットを指定
  sheet_obj.cell(row=line,column=col+1).number_format = 表示形式

    表示形式 説明
‘0.000’小数点以下3桁
#,##0′3桁ごとにカンマ区切り&小数点以下は非表示
‘yyyy/mm/dd’短い日付(西暦4桁/月/日)

7.セルの幅と高さ

〔 セル幅の指定 〕
sheet_obj.column_dimensions[’A’].width= セル幅

〔 セル高の指定 〕
sheet_obj.row_dimensions[1].height = セル高さ

[Python] 基礎-6 csvファイルの読み込み

1.一般的な流れ

(1) “csv”モジュールを使った読み込み
“csv”モジュールをインポート import csv
open関数を使ってcsvファイルオブジェクト(csv_obj)を生成
csv_obj.reader ⇒ 行単位(iteratorプロトコル対応)オブジェクトをリスト形式で生成
csv_obj.DictReader ⇒ 行単位(iteratorプロトコル対応)オブジェクトを辞書形式で生成

(2) “panda”モジュールを使った読み込み
“panda”モジュールをインポート import pandas
データ分析用のモジュール・・・いつか使ってみたい

2.”csv”モジュールを使った読み込み

open関数

使い方の例:

with open(path+’\’+csv_A, mode=’r’, encoding=’utf_8′) as csv_obj:

引数     説明
mode“r”: read only
“w”: 書き込み許可
“x”: 排他的。
“a”:ファイルがあるときは追記
encoding 文字コード
 ”utf-8″:日本語
newlineex.: None, ”, ‘\n’, ‘\r’, ‘\r\n’

reader

使い方の例:

with open(ファイル名, mode=’r’, encoding=’utf_8′) as csv_obj:
csv_reader = csv.reader(csv_obj)
header = next(csv_reader)
print(header) 👈 headerはリスト
for row in csv_reader:
print(row[0]) 👈 rowはリスト

[Python] 出願人名の名寄せ

〘 課題 〙

「名寄せ」とは、表記を一つにまとめること。出願人名は、企業、団体、学校、研究機関、個人などの名称で、主体が同じでも完全に統一されていて変更されないというわけではない。例えば、会社名の変更、合併などで名称が変更されることがある。それ以外にも、関連会社などので1つのグループとして扱った方が良い場合もある。

〘 仕様 〙

処理対象:EXCELの特許文献リスト(「”db”シート」とする)
     「csvの前処理」を使って抽出した「筆頭出願人」カラムを持つことを想定
入力:「筆頭出願人」カラム
出力:「筆頭出願人(名寄せ)」カラム
「名寄せ」シート: 「筆頭出願人」と「筆頭出願人(名寄せ)」の対応付け

〘 名寄せシートの作り方 〙

「”db”シート」から「筆頭出願人」のピボットテーブルを作成

EXCELの「ピボットテーブル」を使うのは必須ではないが、出願件数の多い「筆頭出願人」を優先して、どのような名称を名寄せするかを検討する。
これをコピーして「名寄せ」シートを作成

名寄せ前の出願人名(Aカラム)を名寄せ後の出願人名(Bカラム)に対応付ける作業
出願件数の多い出願人名を優先して行い、上位何社(何人)まで行うかは、分析の目的による。

上の「パナソニックGr.」のように、「三洋電機」との合併、「松下電器産業」からの社名変更、「パナソニックIPマネージメント株式会社」などの関連会社など、まとめて扱いたいグループごとに、名寄せ後の出願人名を統一する。

名寄せ辞書の作成も、AIを活用するなどで自動化したいが、まだまだ検討中……
現段階では、マニュアルで進めるしかない。

〘 pythonによる処理 〙

〘 プログラムのポイント 〙

① 「名寄せ」シートを参照して、「名寄せ辞書」”name_dictionary”を作成。
   キー:元の出願人名
   値:名寄せ後の名称(空欄なら値は”None”)
   (「辞書」については、 基礎-2 リスト、タプル、辞書 を参照。)

② ”db”シートの各行(各特許文献)について、「筆頭出願人」が「名寄せ辞書」のキーのどれかに一致するので、値があればその値(名寄せ後の出願人名)、値がなければ(None)「筆頭出願人名」をそのまま、「筆頭出願人(名寄せ)」のカラムに出力する。

〘 処理結果 〙

出願人名を名寄せした後で、もう一度ピボットテーブルを作ると、出願人グループごとに出願件数を集計することができる。

[Python] ダウンロードしたcsvに対する発明者分析

〖 課題 〗

ダウンロードしたcsvファイルの特許文献リストから、発明者ごとに出願年と件数を抽出することによって、出願人がリストの特許分野に何人の発明者を投入したのかを知ることができる。

〖 仕様 〗

入力:
csvファイルで、「発明者」、「出願日」、「出願日(受理)」が含まれている。
同じ特許の出願公開と特許公報は、予め1件のレコード(文献)にまとめられている
出力:
別のシート(”発明者”)に、1列目に発明者の一覧、2列目以降に出願年ごとの出願件数を出力
「出願年」は「出願日(受理)」ではなく「出願日」に基づく。分割出願は最初の親出願の出願日になる。1件の出願からn件の分割出願が行われたときには、最初の1件の出願年に1+n件の出願されたとカウントされる。

注:分割出願をカウントから除くためには改良が必要

〖 処理例 〗

入力:

事例研究「任天堂スイッチの特許戦略(2020.9更新)」で収集した任天堂スイッチの関連特許115件

発明者名は、複数のときは「;」で区切られている。

 

出力:

初めて出願した年の順に並べ替え、発明者の個人名を伏せて、件数をexcelの「条件付き書式」で色分けして示した。

中核メンバーが誰か、長く従事しているのか、新規に投入された人はその後も継続的に従事しているのかがわかる。ただし、任天堂スイッチの事例では、出願年の範囲がまだ狭く、あまり明確には顕れていない。

詳しくは、事例研究「任天堂スイッチの特許戦略(2020.9-10更新)」に、「発明者分析を追加(2020.10.9)」を追加して更新した(ご参考)。

《 プログラムの構成 》

<入力>
csvダウンロードしたデータから構成したEXCELファイル
csvデータは”db”シート

<出力>
同じEXCELファイルの”発明者”シート
pythonでは上の表を作るところまで、並べ替えや条件付き表示は手作業(Excel操作)

注:csvデータの数(特許文献数)、出願年の範囲などを固定値としてプログラム
  したがって、プログラムとしての汎用性はない。

line 6: os, openpyxlを使用。osはgetcwdでpathを取得するため、openpyxlはexcelファイルを扱うため

line 8-11: ファイル名、シート名などのパラメータ指定

line 13-15: excelファイルを開き、2つのシート(csvデータ”db”と出力用の”発明者”シート)をオブジェクト化

line 17: inventor_dict=発明者の辞書(発明者名をkey、出願年のリストを値とする辞書型データ;出願年は複数になるので単独の整数ではなくリスト型とする)
この行ではinventor_dictを空データに初期化。

line 18-29:csvデータの115件の特許文献情報を順次取得して処理するループ
注:csvデータ内のデータ(レコード数)を適応的にするには、シートオブジェクト.max_rowメソッドを使う

line 19-20:ある1件の特許について、発明者(inventors)情報と出願日(appl_date)情報を取得
inventors=発明者名が”;“区切りで連結された長い文字列

line 21-22:発明者(inventors)が空白でなければ、”;“で分割してリスト型に変換

line 23-29:その特許(line18のループで1件ずつ選ばれている特許)のすべての発明者について繰り返すループ

line 24-29:line 23のループで指している発明者が、inventor_dict(発明者の辞書)のkeyに既に存在するかどうかを調べ、
存在してなければ(line 25)、「その発明者:[出願年]」のデータをinventor_dictに追加
存在していれば(lien 26-29)、その発明者をkeyとする要素の[出願年]リストに、その特許の出願年を要素として追加する

ここまででデータ解析は完了
inventor_dictは、以下のようになる
発明者1:[出願年1, 出願年2, ・・・]
発明者2:[出願年x, 出願年y, ・・・]

line 30-42:”発明者”シートへの出力
1列目に発明者名、2列目以降に出願年ごとの出願件数をカウントして出力

〖 プログラムソース 〗

# -*- coding: utf-8 -*-
"""
発明者解析
"""
#
import os, openpyxl
#
path = os.getcwd()
file_name  = "20200825_Switch特許family.xlsx"
sheet_name = "発明者検索"
sheet_SW = "db172"     # SWITCH特許
#
# シートオブジェクトを生成
workbook = openpyxl.load_workbook(path+"\\"+file_name)
sheet_obj = workbook.get_sheet_by_name(sheet_name)
SWpat_obj = workbook.get_sheet_by_name(sheet_SW)
#
# SWITCH特許のリストを作成
SWpatents = []
for i in range(2,173):
    SWpatents.append(SWpat_obj.cell(row=2,column=i).value)
#
# SWITCH発明者のリストを作成
SWinventors = []
for i in range(29,172):
    SWinventors.append(sheet_obj.cell(row=2,column=i).value)
#
# "発明者検索"シートでの解析
for l in range(3,sheet_obj.max_row+1):
# 共同出願解析
    applicants = sheet_obj.cell(row=l,column=7).value
    if "任天堂" in applicants:
        if ";" in applicants:
            sheet_obj.cell(row=l,column=24).value = applicants.replace("任天堂株式会社","N")
        else:
            sheet_obj.cell(row=l,column=23).value = 1               # 任天堂単独出願
# SWITCH特許か否かの判定
    appl_num = sheet_obj.cell(row=l,column=2).value
    if appl_num in SWpatents:
        sheet_obj.cell(row=l,column=25).value = 1                   # SWITCH特許
# 発明者解析
    inventors = sheet_obj.cell(row=l,column=8).value
    if inventors is None:
        sheet_obj.cell(row=l,column=26).value = 0                   # 発明者数
    else:
        inventor_list = inventors.split(";")
        sheet_obj.cell(row=l,column=26).value = len(inventor_list)  # 発明者数
        num_SWinv = 0
        for inventor in inventor_list:
            for col in range(29,172):
                if inventor == SWinventors[col-29]:
                    num_SWinv +=1
                    sheet_obj.cell(row=l,column=col).value = 1      # SW発明者のフラグ処理
        sheet_obj.cell(row=l,column=27).value = num_SWinv           # SW発明者数
        sheet_obj.cell(row=l,column=28).value = len(inventor_list) - num_SWinv        
#
workbook.save(path+"\\"+file_name)

[Python] ダウンロードしたcsvファイル(zip圧縮)を解凍

に、abcd.zipに圧縮されダウンロードされたcsvファイルが複数格納されている。解凍するとcsvDLフォルダ内にabcdフォルダが解凍され、その中にabcd-xyz.csvファイルが再生される。

〖前提〗
 csvDLフォルダの作られているフォルダ(1階層上のフォルダ)に、pythonのプログラム(スクリプト)(”unzip.py”)が保存されていて、Spyderを使ってプログラムの開発と実行を行う。(開発したプログラムは、どこか共通フォルダを作って集中管理する方が効率的だが、事例ごとに細かい仕様が変わるので、ローカルコピーを作ってカスタマイズしている。)

〖使用するモジュール〗
os:ファイルやフォルダ(ディレクトリ)の管理 [step 1]
zipfile:zipファイルの解凍や圧縮 [step 2]

〖プログラム〗

[step 1]  csvDLフォルダにあるファイルとフォルダのリストを取得する。
# -*- coding: utf-8 -*-
“””
unzip csv files downloaded in “csvDL” folder
Created on 2020.07.07 by H. Kojima
“””
import os
path = os.getcwd()
files = os.listdir(path+”\\csvDL”)
for file in files:
    print(file)
 

実行結果

[step 2]  “unzip”フォルダを作成し(mkdir)、圧縮されたファイル(”.zip”の拡張値を持つ)をそのフォルダー内に解凍する。

# -*- coding: utf-8 -*-
“””
unzip csv files downloaded in “csvDL” folder
Created on 2020.07.07 by H. Kojima

“””
import os
import zipfile
#
path = os.getcwd()
files = os.listdir(path+”\\csvDL”)
os.mkdir(path+”\\csvDL\\unzip”)
for file in files:
    if file[len(file)-4:] == “.zip”:
        print(“extractiong: “, file)
        with zipfile.ZipFile(path+”\\csvDL\\”+file) as zf:
            zf.extractall(path+”\\csvDL\\unzip”)

〖実行結果〗