見出し画像

Google Colabで独自分類を用いたパテントマップを作成する


1. はじめに

知財戦略や技術開発戦略の策定において、特許情報分析は不可欠である。しかし、膨大な特許公報一件一件に目を通し、それらを手作業で分類し、マップにまとめる作業は、多大な時間と労力を要する。もし、このプロセスを自動化し、誰でも迅速かつ客観的な分析を行えるとしたら、知財業務の生産性は飛躍的に向上するであろう。
本稿では、この課題を解決するため、Google Colaboratory上で動作する2つのPythonプログラムを紹介する。前編では特許リストの自動分類を、後編ではその分類結果を用いたパテントマップの自動作成を行う。本稿で提供するコードをコピー&ペーストするだけで、専門的なプログラミング知識がなくとも、すぐに特許情報分析を始めることが可能である。

2. 本ツールが解決する課題とその意義

特許情報分析において、「分類の揺らぎ」と「可視化の手間」という障壁が存在する。

  • 分類の揺らぎ: 分析者によって技術の解釈やキーワードの選定が異なると、分類結果に一貫性がなくなり、客観的な評価が困難になる。

  • 可視化の手間: 分類後のデータをExcel等で手作業で集計し、グラフを作成するプロセスは時間がかかる上、ビジネスレポートとして通用する質の高いマップを作るには相応のスキルが求められる。

本ツールは、これらの課題を正面から解決するために設計されている。

  • プログラム①(自動分類): キーワードベースの明確なルールに基づき、特許の「要約」から課題・解決手段を自動で分類する。これにより、誰が実行しても同じ結果が得られる客観性と、数百件のリストを数秒で処理する圧倒的なスピードを実現する。

  • プログラム②(パテントマップ作成): 分類済みのデータを基に、ビジネスシーンで即活用可能な3種類のパテントマップを対話形式で自動生成する。これにより、分析者はデータ集計やグラフデザインといった煩雑な作業から解放され、マップが示すインサイトの抽出という、より本質的な業務に集中できる。

これらを通じて、特許情報分析の属人性を排し、高度なデータドリブン戦略策定へのハードルを劇的に下げることが、本ツールの最大の意義である。

3. プログラム①:特許リスト自動分類ツール

機能概要

アップロードされた特許リスト(CSV形式)に対し、ユーザーが定義したキーワードを用いて「課題分類」と「解決手段分類」の2つの列を新たに追加し、分類結果を書き込んだ新しいCSVファイルを生成する。

詳しい使い方

  1. コードの実行: まず、下記のPythonコードをGoogle Colabのセルに貼り付けて実行する。

  2. CSVファイルのアップロード: 「ファイルを選択」ボタンが表示されるため、分析したい特許リストのCSVファイルをアップロードする。

  3. 分析対象カラムの選択: アップロードしたCSVのカラム名が番号付きで表示される。指示に従い、「要約」に該当するカラムの番号を入力する。

  4. 分類ルールの定義:

    • 「課題分類」の名称(例:省エネ)と、それに対応するキーワード(例:消費電力+発熱)を"+"で区切って入力する。

    • 【推奨】 多くの分類ルールを定義する場合、あらかじめメモ帳などのテキストエディタで「分類名」と「キーワード群」のリストを作成しておき、実行時にコピー&ペーストすると、入力ミスを防ぎ、効率的に作業を進めることができる。

    • 入力を終えたい場合は、分類名の入力時に何も入力せずEnterキーを3回押す。

    • 同様に「解決手段分類」のルールも定義する。

  5. 結果の出力: 処理が完了すると、分類が付与された新しいCSVファイル(classified_から始まるファイル名)が自動的にダウンロードされる。分類されなかった特許には自動で「その他」が付与される。

コード

import pandas as pd
import io
from google.colab import files

def get_classification_rules(category_type: str) -> dict:
    print("\n" + "="*50)
    print(f"ステップ3: 「{category_type}分類」の定義")
    print("="*50)

    rules = {}
    i = 1
    
    while True:
        prompt_message = f"▶︎ 「{category_type}分類 {i}」の名称を入力してください (入力完了の場合はEnterを3回押してください): "
        category_name = input(prompt_message)

        if not category_name:
            if not rules:
                print("⚠️ 分類が1つも定義されていません。最低1つは定義してください。")
                continue
            
            confirm1 = input("   └─ 終了確認: あと2回Enterを押してください: ")
            
            if not confirm1:
                confirm2 = input("   └─ 最終確認: 最後にもう一度Enterを押してください: ")

                if not confirm2:
                    print("   -> 入力を完了します。")
                    break
            
            print("   -> 入力を続行します。")
            continue

        keywords_str = input(f"   └─ '{category_name}' に対応するキーワードを '+' で区切って入力してください: ")
        if not keywords_str:
            print("⚠️ キーワードが入力されていません。やり直してください。")
            continue

        keywords = [kw.strip() for kw in keywords_str.split('+') if kw.strip()]
        if keywords:
            rules[category_name] = keywords
            print(f"   => 登録しました: 分類名='{category_name}', キーワード={keywords}")
            i += 1
        else:
            print("⚠️ 有効なキーワードがありませんでした。")

    print(f"✅ 「{category_type}分類」の定義が完了しました。")
    return rules

def classify_patents():
    print("="*50)
    print("ステップ1: 特許リスト(CSVファイル)のアップロード")
    print("="*50)
    try:
        uploaded = files.upload()
        if not uploaded:
            print("ファイルがアップロードされませんでした。処理を中断します。")
            return
        filename = next(iter(uploaded))
        print(f"\n✅ ファイル '{filename}' をアップロードしました。")
        df = pd.read_csv(io.BytesIO(uploaded[filename]), encoding='utf-8-sig')
    except Exception as e:
        print(f"エラー: ファイルの読み込みに失敗しました。CSV形式であることを確認してください。詳細: {e}")
        return

    print("\n" + "="*50)
    print("ステップ2: 分析対象カラムの選択")
    print("="*50)
    columns = df.columns.tolist()
    print("ファイルのカラムリスト:")
    for i, col in enumerate(columns):
        print(f"  {i+1}: {col}")

    while True:
        try:
            abstract_col_num = int(input("\n▶︎ 分析対象となる「要約」に該当するカラムの番号を入力してください: "))
            if 1 <= abstract_col_num <= len(columns):
                abstract_col = columns[abstract_col_num - 1]
                print(f"✅ 設定しました: 分析対象='{abstract_col}'")
                break
            else:
                print("⚠️ 無効な番号です。リストの範囲内の番号を入力してください。")
        except ValueError:
            print("⚠️ 半角数字で番号を入力してください。")

    problem_rules = get_classification_rules("課題")
    solution_rules = get_classification_rules("解決手段")

    print("\n" + "="*50)
    print("ステップ4: 分類付与処理を実行中...")
    print("="*50)

    df['課題分類'] = ''
    df['解決手段分類'] = ''

    for index, row in df.iterrows():
        abstract_text = str(row[abstract_col]) if pd.notna(row[abstract_col]) else ""
        search_text = abstract_text

        found_problems = []
        for category, keywords in problem_rules.items():
            if any(kw in search_text for kw in keywords):
                found_problems.append(category)

        found_solutions = []
        for category, keywords in solution_rules.items():
            if any(kw in search_text for kw in keywords):
                found_solutions.append(category)

        if found_problems:
            df.at[index, '課題分類'] = ";".join(found_problems)
        if found_solutions:
            df.at[index, '解決手段分類'] = ";".join(found_solutions)
            
    df['課題分類'] = df['課題分類'].replace('', 'その他')
    df['解決手段分類'] = df['解決手段分類'].replace('', 'その他')
    print("分類されなかった項目に「その他」を付与しました。")
    print("✅ 分類付与が完了しました。")

    print("\n" + "="*50)
    print("ステップ5: 結果の表示とダウンロード")
    print("="*50)
    print("処理結果のプレビュー (先頭5行):")
    display(df[['課題分類', '解決手段分類', abstract_col]].head())

    output_filename = "classified_" + filename
    df.to_csv(output_filename, index=False, encoding='utf-8-sig')

    print(f"\n🎉 全ての処理が完了しました。'{output_filename}' をダウンロードします。")
    files.download(output_filename)

if __name__ == "__main__":
    classify_patents()

4. 生成AIを活用した効率的な分類ルールの作成

本ツールの分析精度は、入力する「分類名」と「キーワード」の質に大きく依存する。しかし、質の高い分類体系をゼロから構築するのは、専門知識を要する骨の折れる作業である。このような場合、Geminiをはじめとする生成AIが、思考のパートナーとして極めて有効である。

例えば、分析対象となる特許要約群をAIにインプットし、内容の傾向を読み解かせることが可能である。AIは、そこから「課題」や「解決手段」といった観点での分類体系の案を複数提示し、さらには各分類を特徴づけるキーワード群を抽出することができる。

このアプローチの利点は、分析者がゼロから思考を巡らせる必要がなくなる点にある。AIが生成した草案をたたき台として、自身の知見に基づいて修正・統合することで、短時間で質の高い分類ルールを完成させることが可能となる。

5. プログラム②:分類によるパテントマップ作成ツール

機能概要

プログラム①で作成した分類済み特許リストを読み込み、対話形式で分析条件を設定するだけで、以下の3種類のパテントマップを自動生成する。

  1. 権利者 × 課題ヒートマップ: 上位の主要プレイヤーがどの課題領域に注力しているかを可視化する。

  2. 課題の時系列推移バブルチャート: 各課題領域が年代ごとにどれだけ注目されてきたか、そのトレンドを把握する。

  3. 課題 × 解決手段ヒートマップ: 特定の課題に対し、どのような技術的アプローチが取られているかの組み合わせ(技術ポートフォリオ)を明らかにする。

詳しい使い方

  1. コードの実行: 提供するPythonコードをGoogle Colabのセルに貼り付けて実行する。

  2. CSVファイルのアップロード: プログラム①で生成した分類済みCSVファイルをアップロードする。

  3. 分析対象カラムの選択: カラムリストの中から「権利者」「出願日」「課題分類」「解決手段分類」に該当する番号をそれぞれ入力する。

  4. 分析条件の設定:

    • 分析期間: データに含まれる出願年の範囲が提示されるので、分析したい開始年と終了年を入力する。

    • 上位権利者: 期間内の出願件数上位10社が表示されるので、そのうち上位何社をグラフに描画するかを数値で指定する。

  5. マップの生成: 全ての入力が完了すると、Colabの画面上に3種類のパテントマップが順番に描画される。

コード

!pip install japanize-matplotlib

import pandas as pd
import io
import plotly.express as px
import seaborn as sns
import matplotlib.pyplot as plt
import japanize_matplotlib
from google.colab import files

def sort_others_last(labels):
    """リスト内の 'その他' を末尾に移動させる関数"""
    labels = list(labels)
    if 'その他' in labels:
        labels.remove('その他')
        labels.append('その他')
    return labels

def plot_heatmap(df, index_col, columns_col, title):
    """
    ヒートマップを描画する関数。
    - 縦軸を件数の降順でソート
    - 'その他'は常に最後尾に配置
    """
    index_order = df[index_col].value_counts().index.tolist()
    sorted_index = sort_others_last(index_order)
    
    sorted_columns = sort_others_last(df[columns_col].unique())
    
    crosstab_df = pd.crosstab(df[index_col], df[columns_col]).reindex(index=sorted_index, columns=sorted_columns, fill_value=0)
    
    if crosstab_df.empty:
        print(f"⚠️ グラフ「{title}」のデータが存在しません。スキップします。")
        return

    plt.figure(figsize=(14, 10))
    sns.heatmap(crosstab_df, annot=True, cmap='coolwarm', fmt='d')
    plt.title(title, fontsize=18, weight='bold')
    plt.xlabel(columns_col, fontsize=12)
    plt.ylabel(index_col, fontsize=12)
    plt.tight_layout()
    plt.show()

def plot_timeseries_bubble(df, year_col, category_col, title):
    """
    時系列バブルチャートを静的な画像として描画する関数。
    - 縦軸を件数順('その他'を除く)にソート
    - 'その他'は最下部に配置
    - バブル内に件数を表示
    """
    count_df = df.groupby([year_col, category_col]).size().reset_index(name='件数')
    if count_df.empty:
        print(f"⚠️ グラフ「{title}」のデータが存在しません。スキップします。")
        return

    category_order = df[category_col].value_counts().index.tolist()
    sorted_category_order = sort_others_last(category_order)
    
    count_df[category_col] = pd.Categorical(count_df[category_col], categories=sorted_category_order, ordered=True)
    
    plt.figure(figsize=(16, 10))
    sns.scatterplot(
        data=count_df,
        x=year_col,
        y=category_col,
        size='件数',
        hue=category_col,
        palette='pastel',
        sizes=(100, 4000),
        legend=False
    )
    
    for i in range(count_df.shape[0]):
        plt.text(x=count_df[year_col].iloc[i], 
                 y=count_df[category_col].iloc[i], 
                 s=count_df['件数'].iloc[i],
                 horizontalalignment='center',
                 verticalalignment='center',
                 color='black',
                 weight='bold',
                 size=8)

    plt.title(title, fontsize=20, weight='bold')
    plt.xlabel('出願年', fontsize=14)
    plt.ylabel('課題分類', fontsize=14)
    plt.xticks(rotation=45)
    plt.grid(True, which='both', linestyle='--', linewidth=0.5)
    plt.tight_layout()
    plt.show()

def create_patent_maps():
    """CSVをアップロードして各種パテントマップを作成するメイン関数"""
    print("="*50)
    print("ステップ1: 分類済み特許リスト(CSV)のアップロード")
    print("="*50)
    try:
        uploaded = files.upload()
        if not uploaded:
            print("ファイルがアップロードされませんでした。処理を中断します。"); return
        filename = next(iter(uploaded))
        print(f"\n✅ ファイル '{filename}' をアップロードしました。")
        df = pd.read_csv(io.BytesIO(uploaded[filename]), encoding='utf-8-sig')
    except Exception as e:
        print(f"エラー: ファイルの読み込みに失敗しました。詳細: {e}"); return

    print("\n" + "="*50)
    print("ステップ2: 分析対象カラムの選択")
    print("="*50)
    columns = df.columns.tolist()
    print("ファイルのカラムリスト:")
    for i, col in enumerate(columns): print(f"  {i+1}: {col}")

    while True:
        try:
            assignee_col = columns[int(input("\n▶︎ 「権利者」に該当するカラム番号: ")) - 1]
            date_col = columns[int(input("▶︎ 「出願日」に該当するカラム番号: ")) - 1]
            problem_col = columns[int(input("▶︎ 「課題分類」に該当するカラム番号: ")) - 1]
            solution_col = columns[int(input("▶︎ 「解決手段分類」に該当するカラム番号: ")) - 1]
            print(f"✅ 設定しました:\n  権利者='{assignee_col}'\n  出願日='{date_col}'\n  課題分類='{problem_col}'\n  解決手段分類='{solution_col}'")
            break
        except (ValueError, IndexError):
            print("⚠️ 半角数字でリストの範囲内の番号を入力してください。")

    print("\n" + "="*50)
    print("ステップ3: 分析条件の設定")
    print("="*50)

    df['出願年'] = pd.to_datetime(df[date_col], errors='coerce').dt.year
    df.dropna(subset=['出願年'], inplace=True)
    df['出願年'] = df['出願年'].astype(int)
    
    min_year, max_year = df['出願年'].min(), df['出願年'].max()
    print(f"データには {min_year}年 から {max_year}年 までの出願情報が含まれています。")
    while True:
        try:
            start_year = int(input(f"▶︎ 分析の開始年を入力してください (例: {min_year}): "))
            end_year = int(input(f"▶︎ 分析の終了年を入力してください (例: {max_year}): "))
            if start_year <= end_year: break
            else: print("⚠️ 終了年は開始年以上の年にしてください。")
        except ValueError:
            print("⚠️ 半角数字で年を入力してください。")
    
    df = df[(df['出願年'] >= start_year) & (df['出願年'] <= end_year)]

    df[assignee_col] = df[assignee_col].astype(str).str.split(';')
    df_exploded_assignee = df.explode(assignee_col)
    
    assignee_counts = df_exploded_assignee[assignee_col].value_counts()
    print("\n期間内の出願件数上位の権利者:")
    print(assignee_counts.head(10))
    
    while True:
        try:
            top_n = int(input("\n▶︎ 上位何件の権利者を描画しますか?: "))
            if top_n > 0: break
            else: print("⚠️ 1以上の整数を入力してください。")
        except ValueError:
            print("⚠️ 半角数字で入力してください。")

    top_assignees = assignee_counts.head(top_n).index.tolist()
    df_filtered = df_exploded_assignee[df_exploded_assignee[assignee_col].isin(top_assignees)]

    df_problem = df_filtered.copy()
    df_problem[problem_col] = df_problem[problem_col].astype(str).str.split(';')
    df_problem = df_problem.explode(problem_col)
    
    df_problem_solution = df_problem.copy()
    df_problem_solution[solution_col] = df_problem_solution[solution_col].astype(str).str.split(';')
    df_problem_solution = df_problem_solution.explode(solution_col)
    
    print("\n" + "="*50)
    print("ステップ4: パテントマップを生成します...")
    print("="*50)
    
    plot_heatmap(df_problem, assignee_col, problem_col, f'{start_year}-{end_year}年 上位{top_n}権利者別 課題領域ポジショニング')
    plot_timeseries_bubble(df_problem, '出願年', problem_col, f'{start_year}-{end_year}年 課題領域別 出願件数時系列推移')
    plot_heatmap(df_problem_solution, problem_col, solution_col, f'{start_year}-{end_year}年 技術ポートフォリオ (課題×解決手段)')
    
    print("\n🎉 全てのパテントマップの生成が完了しました。")

if __name__ == "__main__":
    create_patent_maps()

6. 実施例

あいも変わらず固体電池の母集団で作成したパテントマップを下に挙げる。

権利者 × 課題ヒートマップ
課題の時系列推移バブルチャート
課題 × 解決手段ヒートマップ

7. おわりに

本稿で紹介した2つのプログラムは、特許情報分析における定型的な作業を徹底的に自動化・効率化するために開発したものである。これにより、これまで数日を要していたかもしれない分析が、数分で完了する可能性を秘めている。
もちろん、最終的な戦略判断には、これらのマップを基にした専門家による深い洞察が不可欠である。しかし、本ツールはその思考の出発点となる客観的な材料を、誰にでも平等に、かつ迅速に提供する。
本ツールが、データに基づいた知財戦略を検討する上での一助となれば幸いである。コードは自由に改変して、さらに自社のニーズに合った形に進化させることも可能である。本稿が、多くの知財担当者や技術戦略担当者の役に立つことを願ってやまない。

いいなと思ったら応援しよう!

しばやま よろしければ応援お願いします。いただいたチップはクリエイターとしての研究開発の活動費に使わせていただきます!