[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の前処理

《 前処理の目的と内容 》

csvダウンロードした特許情報ファイルを編集して、分析に便利な情報を追加する前処理

  1. 〈出願件数〉
    「出願件数」の列(column)を作って、出願件数として全ての行(row)に「1」を入力

  2. 〈筆頭出願人(出願人分析用)〉
    「出願人」欄から、筆頭出願人を抽出。国籍(アルファベット2文字の国コード)が括弧書きされているとき、「出願人の国籍」の列(column)に出力。さらに抽出した「筆頭出願人」と同じデータを「筆頭出願人(名寄せ)」にコピー。「筆頭出願人(名寄せ)」は将来の名寄せ用の元データ。

  3. 〈最先の優先日(年次推移分析用)〉
    「最先優先日」と「最先優先年」の列(column)を追加。「出願日」と「優先日」の欄から最先の日を抽出して「最先優先日」として出力。さらに、抽出した「最先優先日」からその年を抜き出して「最先優先年」として出力。

  4. 〈筆頭IPC(技術分野分析用)〉
    「筆頭IPC(sub-class)」と「筆頭IPC(main-group)」の列(column)を追加。「IPC」の欄から、筆頭IPCを抽出して、サブクラス(先頭の4文字,”H01L”など)を「筆頭IPC(sub-class)」に、メイングループ(先頭の4文字,”H01L”などに続く「数字/数字」の前半部分)を「筆頭IPC(main-group)」に出力。

  5. 〈出願国(出願国分析用)〉
    「JP」「WO」「US」「EP」「CN」「KR」の列(column)を追加。「ファミリー公報」の欄から、作成した列(column)に相当する国・地域への出願があれば「1」なければ「0」を出力。その国・地域への出願の有無を表すフラグ。同じ国・地域内でのファミリー展開に応じた件数は無視して、単に有無のみを判定。

《 プログラムの構成 》

<入力>
csvダウンロードしたデータから構成したEXCELファイル
複数シートを想定して、シート名を指定して処理
<出力>
指定したシート内への列(column)の追加
<プログラム>
上記1~5は関数として定義。処理対象のWokkBook名(EXCELファイル名)とシート名を指定して必要な前処理を実行する関数を呼び出す。

# -*- coding: utf-8 -*-
"""
csvDLから作成したEXCELテーブルの前処理

2020.07.18 newly created
"""
#
import re, os, openpyxl, datetime
#
#   関数定義
#
#   出願件数(1固定)
def num_application(file_name, sheet_name, col_n_apl):
    global path
    global col_out_start
    workbook = openpyxl.load_workbook(path+"\\"+file_name)
    sheet = workbook.get_sheet_by_name(sheet_name)
    print("Processing 出願件数 for file = ", file_name,  " sheet = ", sheet_name)
    # header
    sheet.cell(row=1,column=col_out_start).value = '出願件数'
    #
    for line in range(2, sheet.max_row+1):
        sheet.cell(row=line,column=col_out_start).value = 1
    #
    col_out_start = col_out_start +1
    workbook.save(path+"\\"+file_name)
#
#   筆頭出願人
#    国籍は、出願人欄に括弧書きで国コードが付されていることが前提
#    名寄せ欄は、筆頭出願人欄と同じ。後の名寄せ処理のため。
#
def applicant(file_name, sheet_name, col_n_apl):
    global path
    global col_out_start
    regex_CC = re.compile(r'\(([A-Z][A-Z])\)')    # Regex. of Country Code
    #
    workbook = openpyxl.load_workbook(path+"\\"+file_name)
    sheet = workbook.get_sheet_by_name(sheet_name)
    print("Processing 筆頭出願人 for file = ", file_name,  " sheet = ", sheet_name)
    # header
    sheet.cell(row=1,column=col_out_start).value = '筆頭出願人'
    sheet.cell(row=1,column=col_out_start+1).value = '筆頭出願人の国籍'
    sheet.cell(row=1,column=col_out_start+2).value = '筆頭出願人(名寄せ)'
    #
    for line in range(2, sheet.max_row+1):
        applicants = sheet.cell(row=line,column=col_n_apl).value.split(';')
        if type(applicants is list):
            applicant = applicants[0]
        else:
            applicant = applicants
        CC = regex_CC.search(applicant)
        if CC is None:
            Nationality = 'N/A'
        else:
            Nationality = CC.group(1)
            applicant = applicant[0:CC.start()-1]
        sheet.cell(row=line,column=col_out_start).value = applicant
        sheet.cell(row=line,column=col_out_start+1).value = Nationality
        sheet.cell(row=line,column=col_out_start+2).value = applicant
    #
    col_out_start = col_out_start +3
    workbook.save(path+"\\"+file_name)
#
#   最先の優先日とその年の抽出
#    最先の優先日: 出願日と優先日(複数優先も想定)のうち最先の日
#
def earliest_date(file_name, sheet_name, col_d_apl, col_d_pri):
    global path
    global col_out_start
    workbook = openpyxl.load_workbook(path+"\\"+file_name)
    sheet = workbook.get_sheet_by_name(sheet_name)
    print("Processing 最先優先日 for file = ", file_name,  " sheet = ", sheet_name)
    # header
    sheet.cell(row=1,column=col_out_start).value = '最先優先日'
    sheet.cell(row=1,column=col_out_start+1).value = '最先優先年'
    #
    for line in range(2, sheet.max_row+1):
        pp_date = sheet.cell(row=line,column=col_d_apl).value   # Apllication date
        if sheet.cell(row=line,column=col_d_pri).value is not None: # Priority dates
            if type(sheet.cell(row=line,column=col_d_pri).value) is str:
                p_dates = sheet.cell(row=line,column=col_d_pri).value.split(';')
                for p_date in p_dates:
                    if datetime.datetime.strptime(p_date, '%Y/%m/%d') < pp_date:
                        pp_date = datetime.datetime.strptime(p_date, '%Y/%m/%d')
            else:
                p_date = sheet.cell(row=line,column=col_d_pri).value
                if p_date < pp_date:
                    pp_date = p_date
        sheet.cell(row=line,column=col_out_start).value = pp_date
        sheet.cell(row=line,column=col_out_start+1).value = pp_date.year
    #
    col_out_start = col_out_start +2
    workbook.save(path+"\\"+file_name)
#
#   筆頭IPCのサブセクション、メイングループまでの抽出
#
def primary_IPC(file_name, sheet_name, col_IPC):
    global path
    global col_out_start
    workbook = openpyxl.load_workbook(path+"\\"+file_name)
    sheet = workbook.get_sheet_by_name(sheet_name)
    print("Processing 筆頭IPC for file = ", file_name,  " sheet = ", sheet_name)
    # header
    sheet.cell(row=1,column=col_out_start).value = '筆頭IPC(sub-class)'
    sheet.cell(row=1,column=col_out_start+1).value = '筆頭IPC(main-group)'
    #
    for line in range(2, sheet.max_row+1):
        IPC = sheet.cell(row=line,column=col_IPC).value
        if IPC is None:
            IPC_class = 'N/A'
            IPC_group = 'N/A'
        else:
            IPC_class = IPC[0:4]
            IPC_group = IPC[0:IPC.find('/')]
        sheet.cell(row=line,column=col_out_start).value = IPC_class # 筆頭IPC(sub-class)
        sheet.cell(row=line,column=col_out_start+1).value = IPC_group # 筆頭IPC(main-group)
    #
    col_out_start = col_out_start +2
    workbook.save(path+"\\"+file_name)
#
#   出願国の抽出
#
def family_analysis(file_name, sheet_name, col_family):
    global path
    global col_out_start
    workbook = openpyxl.load_workbook(path+"\\"+file_name)
    sheet = workbook.get_sheet_by_name(sheet_name)
    print("Processing 出願国分析 for file = ", file_name,  " sheet = ", sheet_name)
    # regular expressions
    regex_WO = re.compile(r'WO|WO')
    regex_CN = re.compile(r'CN')
    regex_US = re.compile(r'US')
    regex_EP = re.compile(r'EP')
    regex_KR = re.compile(r'KR')
    regex_JP = re.compile(r'特|実')
    # header
    sheet.cell(row=1,column=col_out_start).value = 'WO'
    sheet.cell(row=1,column=col_out_start+1).value = 'CN'
    sheet.cell(row=1,column=col_out_start+2).value = 'US'
    sheet.cell(row=1,column=col_out_start+3).value = 'EP'
    sheet.cell(row=1,column=col_out_start+4).value = 'KR'
    sheet.cell(row=1,column=col_out_start+5).value = 'JP'
    #
    for line in range(2, sheet.max_row+1):
        family = sheet.cell(row=line,column=col_family).value # ファミリ出願番号(全世代)
        if family is None:
            sheet.cell(row=line,column=col_out_start).value = 0
            sheet.cell(row=line,column=col_out_start+1).value = 0
            sheet.cell(row=line,column=col_out_start+2).value = 0
            sheet.cell(row=line,column=col_out_start+3).value = 0
            sheet.cell(row=line,column=col_out_start+4).value = 0
            sheet.cell(row=line,column=col_out_start+5).value = 0
        else:
            if regex_WO.search(family) is None:
                sheet.cell(row=line,column=col_out_start).value = 0
            else:
                sheet.cell(row=line,column=col_out_start).value = 1
            if regex_CN.search(family) is None:
                sheet.cell(row=line,column=col_out_start+1).value = 0
            else:
                    sheet.cell(row=line,column=col_out_start+1).value = 1
            if regex_US.search(family) is None:
                sheet.cell(row=line,column=col_out_start+2).value = 0
            else:
                sheet.cell(row=line,column=col_out_start+2).value = 1
            if regex_EP.search(family) is None:
                sheet.cell(row=line,column=col_out_start+3).value = 0
            else:
                sheet.cell(row=line,column=col_out_start+3).value = 1
            if regex_KR.search(family) is None:
                sheet.cell(row=line,column=col_out_start+4).value = 0
            else:
                sheet.cell(row=line,column=col_out_start+4).value = 1
            if regex_JP.search(family) is None:
                sheet.cell(row=line,column=col_out_start+5).value = 0
            else:
                sheet.cell(row=line,column=col_out_start+5).value = 1
    #
    col_out_start = col_out_start +6
    workbook.save(path+"\\"+file_name)
#
# START OF PRE-PROCESSSING
#
# 入力データのカラム番号
col_n_apl = 5   # 出願人/権利者
col_n_inv = 6   # 発明者
col_d_apl = 7   # 出願日
col_d_pri = 10  # 優先日
col_IPC = 12    # IPC
col_family = 19 # ファミリー公報番号
# データ出力の開始カラム
col_out_start = 20
#
path = os.getcwd()
file_name  = "20200717_NintendoSWITCH_JP_108.xlsx"
#file_name  = "test.xlsx"
sheet_name = "db"
#
num_application(file_name, sheet_name, col_n_apl)
applicant(file_name, sheet_name, col_n_apl)
earliest_date(file_name, sheet_name, col_d_apl, col_d_pri)
primary_IPC(file_name, sheet_name, col_IPC)
family_analysis(file_name, sheet_name, col_family)
#

《 出願件数 num_application 関数の定義 》

#
#   出願件数(1固定)
def num_application(file_name, sheet_name, col_n_apl):
    global path
    global col_out_start
    workbook = openpyxl.load_workbook(path+"\\"+file_name)
    sheet = workbook.get_sheet_by_name(sheet_name)
    print("Processing 出願件数 for file = ", file_name,  " sheet = ", sheet_name)
    # header
    sheet.cell(row=1,column=col_out_start).value = '出願件数'
    #
    for line in range(2, sheet.max_row+1):
        sheet.cell(row=line,column=col_out_start).value = 1
    #
    col_out_start = col_out_start +1
    workbook.save(path+"\\"+file_name)
#

《 筆頭出願人 applicant 関数の定義 

#
#   筆頭出願人
#    国籍は、出願人欄に括弧書きで国コードが付されていることが前提
#    名寄せ欄は、筆頭出願人欄と同じ。後の名寄せ処理のため。
#
def applicant(file_name, sheet_name, col_n_apl):
    global path
    global col_out_start
    regex_CC = re.compile(r'\(([A-Z][A-Z])\)')    # Regex. of Country Code
    #
    workbook = openpyxl.load_workbook(path+"\\"+file_name)
    sheet = workbook.get_sheet_by_name(sheet_name)
    print("Processing 筆頭出願人 for file = ", file_name,  " sheet = ", sheet_name)
    # header
    sheet.cell(row=1,column=col_out_start).value = '筆頭出願人'
    sheet.cell(row=1,column=col_out_start+1).value = '筆頭出願人の国籍'
    sheet.cell(row=1,column=col_out_start+2).value = '筆頭出願人(名寄せ)'
    #
    for line in range(2, sheet.max_row+1):
        applicants = sheet.cell(row=line,column=col_n_apl).value.split(';')
        if type(applicants is list):
            applicant = applicants[0]
        else:
            applicant = applicants
        CC = regex_CC.search(applicant)
        if CC is None:
            Nationality = 'N/A'
        else:
            Nationality = CC.group(1)
            applicant = applicant[0:CC.start()-1]
        sheet.cell(row=line,column=col_out_start).value = applicant
        sheet.cell(row=line,column=col_out_start+1).value = Nationality
        sheet.cell(row=line,column=col_out_start+2).value = applicant
    #
    col_out_start = col_out_start +3
    workbook.save(path+"\\"+file_name)
#

《 最先の優先日 earliest_date 関数の定義 

#
#   最先の優先日とその年の抽出
#    最先の優先日: 出願日と優先日(複数優先も想定)のうち最先の日
#
def earliest_date(file_name, sheet_name, col_d_apl, col_d_pri):
    global path
    global col_out_start
    workbook = openpyxl.load_workbook(path+"\\"+file_name)
    sheet = workbook.get_sheet_by_name(sheet_name)
    print("Processing 最先優先日 for file = ", file_name,  " sheet = ", sheet_name)
    # header
    sheet.cell(row=1,column=col_out_start).value = '最先優先日'
    sheet.cell(row=1,column=col_out_start+1).value = '最先優先年'
    #
    for line in range(2, sheet.max_row+1):
        pp_date = sheet.cell(row=line,column=col_d_apl).value   # Apllication date
        if sheet.cell(row=line,column=col_d_pri).value is not None: # Priority dates
            if type(sheet.cell(row=line,column=col_d_pri).value) is str:
                p_dates = sheet.cell(row=line,column=col_d_pri).value.split(';')
                for p_date in p_dates:
                    if datetime.datetime.strptime(p_date, '%Y/%m/%d') < pp_date:
                        pp_date = datetime.datetime.strptime(p_date, '%Y/%m/%d')
            else:
                p_date = sheet.cell(row=line,column=col_d_pri).value
                if p_date < pp_date:
                    pp_date = p_date
        sheet.cell(row=line,column=col_out_start).value = pp_date
        sheet.cell(row=line,column=col_out_start+1).value = pp_date.year
    #
    col_out_start = col_out_start +2
    workbook.save(path+"\\"+file_name)
#

《 筆頭IPC primary_IPC 関数の定義 

#
#   筆頭IPCのサブセクション、メイングループまでの抽出
#
def primary_IPC(file_name, sheet_name, col_IPC):
    global path
    global col_out_start
    workbook = openpyxl.load_workbook(path+"\\"+file_name)
    sheet = workbook.get_sheet_by_name(sheet_name)
    print("Processing 筆頭IPC for file = ", file_name,  " sheet = ", sheet_name)
    # header
    sheet.cell(row=1,column=col_out_start).value = '筆頭IPC(sub-class)'
    sheet.cell(row=1,column=col_out_start+1).value = '筆頭IPC(main-group)'
    #
    for line in range(2, sheet.max_row+1):
        IPC = sheet.cell(row=line,column=col_IPC).value
        if IPC is None:
            IPC_class = 'N/A'
            IPC_group = 'N/A'
        else:
            IPC_class = IPC[0:4]
            IPC_group = IPC[0:IPC.find('/')]
        sheet.cell(row=line,column=col_out_start).value = IPC_class # 筆頭IPC(sub-class)
        sheet.cell(row=line,column=col_out_start+1).value = IPC_group # 筆頭IPC(main-group)
    #
    col_out_start = col_out_start +2
    workbook.save(path+"\\"+file_name)
#

《 出願国 family_analysis 関数の定義 

#
#   出願国の抽出
#
def family_analysis(file_name, sheet_name, col_family):
    global path
    global col_out_start
    workbook = openpyxl.load_workbook(path+"\\"+file_name)
    sheet = workbook.get_sheet_by_name(sheet_name)
    print("Processing 出願国分析 for file = ", file_name,  " sheet = ", sheet_name)
    # regular expressions
    regex_WO = re.compile(r'WO|WO')
    regex_CN = re.compile(r'CN')
    regex_US = re.compile(r'US')
    regex_EP = re.compile(r'EP')
    regex_KR = re.compile(r'KR')
    regex_JP = re.compile(r'特|実')
    # header
    sheet.cell(row=1,column=col_out_start).value = 'WO'
    sheet.cell(row=1,column=col_out_start+1).value = 'CN'
    sheet.cell(row=1,column=col_out_start+2).value = 'US'
    sheet.cell(row=1,column=col_out_start+3).value = 'EP'
    sheet.cell(row=1,column=col_out_start+4).value = 'KR'
    sheet.cell(row=1,column=col_out_start+5).value = 'JP'
    #
    for line in range(2, sheet.max_row+1):
        family = sheet.cell(row=line,column=col_family).value # ファミリ出願番号(全世代)
        if family is None:
            sheet.cell(row=line,column=col_out_start).value = 0
            sheet.cell(row=line,column=col_out_start+1).value = 0
            sheet.cell(row=line,column=col_out_start+2).value = 0
            sheet.cell(row=line,column=col_out_start+3).value = 0
            sheet.cell(row=line,column=col_out_start+4).value = 0
            sheet.cell(row=line,column=col_out_start+5).value = 0
        else:
            if regex_WO.search(family) is None:
                sheet.cell(row=line,column=col_out_start).value = 0
            else:
                sheet.cell(row=line,column=col_out_start).value = 1
            if regex_CN.search(family) is None:
                sheet.cell(row=line,column=col_out_start+1).value = 0
            else:
                    sheet.cell(row=line,column=col_out_start+1).value = 1
            if regex_US.search(family) is None:
                sheet.cell(row=line,column=col_out_start+2).value = 0
            else:
                sheet.cell(row=line,column=col_out_start+2).value = 1
            if regex_EP.search(family) is None:
                sheet.cell(row=line,column=col_out_start+3).value = 0
            else:
                sheet.cell(row=line,column=col_out_start+3).value = 1
            if regex_KR.search(family) is None:
                sheet.cell(row=line,column=col_out_start+4).value = 0
            else:
                sheet.cell(row=line,column=col_out_start+4).value = 1
            if regex_JP.search(family) is None:
                sheet.cell(row=line,column=col_out_start+5).value = 0
            else:
                sheet.cell(row=line,column=col_out_start+5).value = 1
    #
    col_out_start = col_out_start +6
    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”)

〖実行結果〗

[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桁の数字