リサーチ・情報源

CSV で参考文献リストを管理し、DOI の重複や欠落を確認する

小規模な研究用参考文献リストを CSV で管理し、比較用に DOI text を正規化して、重複 DOI と欠落 DOI を確認する review file を作成します。workflow は元の CSV を保持し、DOI が存在するというだけで有効だとは判断しません。

目次を表示

この翻訳はAIで作成しました。コード、単位、数値は原文と併せて確認してください。各言語のネイティブ話者による校閲は、まだ完了していません。 English

対象読者スプレッドシートや CSV で参考文献リストを管理し、重複 DOI と DOI 欠落を簡単に確認したい研究者向けのガイドです。

準備するもの
  • Python 3.12 と、そのバージョンを起動できるターミナルコマンド。
  • UTF-8 CSV ファイルを保存できるテキストエディタまたは表計算ソフト。
  • スクリプトが outputs の下に新しいフォルダを作成できる作業フォルダ。
  • 必要なのは Python 標準ライブラリのみです: csv, pathlib.

01DOI チェックで分かることと分からないことを決める

この workflow は2つの実務的な問題を確認します。reference に DOI value がない場合と、simple text normalization 後に2 rows が同じ DOI を持つ場合です。DOI registry には接続しないため、DOI が実在すること、正しく解決されること、引用した article に属することまでは証明しません。

script は https://doi.org/ や doi: などの一般的な textual prefixes を除去し、whitespace を trim して、残った DOI text を比較用に lowercase に変換します。original DOI text は normalized value と並べて output に保持します。

02合成の参考文献リストを作成する

以下の reference list は合成データであり、この記事のために作成したものです。DOI-like strings は demonstration data であり、実在または解決可能な publications として提示するものではありません。file を references.csv として保存してください。

csv
ref_id,title,year,doi
R001,Synthetic bracket study,2024,10.0000/demo.alpha
R002,Synthetic vibration study,2025,10.0000/demo.beta
R003,Synthetic bracket study copy,2024,https://doi.org/10.0000/demo.alpha
R004,Synthetic fatigue note,2023,
R005,Synthetic surrogate study,2026,doi:10.0000/DEMO.GAMMA
R006,Synthetic optimization study,2026,10.0000/demo.delta

reference rows は6件あります。5件には DOI text があり、R004 には DOI がありません。R001 と R003 は異なる textual forms を使用していますが、同じ value に normalize されます。R005 は capitalization と doi: prefix も比較用に normalize できることを示します。

03想定される issues を手作業で確認する

ref_idOriginal DOINormalized DOI想定 issue
R00110.0000/demo.alpha10.0000/demo.alphaDUPLICATE_DOI
R00210.0000/demo.beta10.0000/demo.betaNone
R003https://doi.org/10.0000/demo.alpha10.0000/demo.alphaDUPLICATE_DOI
R004MISSING_DOI
R005doi:10.0000/DEMO.GAMMA10.0000/demo.gammaNone
R00610.0000/demo.delta10.0000/demo.deltaNone

想定件数は references 6件、DOI text を持つ rows 5件、missing DOI の row 1件、2 rows を含む duplicate DOI group 1件です。normalization 後、空白でない DOI entries 5件には distinct DOI values が4つあります。

04DOI text を正規化して review files を作成する

次の script を reference_list_check.py として保存してください。reference list 全体の normalized copy と、review が必要な rows だけを含む別の issues.csv を書き出します。元の references.csv は読み取り専用です。

python
import csv
from pathlib import Path

SOURCE = Path("references.csv")
OUTPUT_DIR = Path("outputs") / "reference_list_result"
NORMALIZED = OUTPUT_DIR / "normalized_references.csv"
ISSUES = OUTPUT_DIR / "issues.csv"
REQUIRED = {"ref_id", "title", "year", "doi"}


def normalize_doi(value: str) -> str:
    text = value.strip()
    lowered = text.lower()
    prefixes = (
        "https://doi.org/",
        "http://doi.org/",
        "doi:",
    )
    for prefix in prefixes:
        if lowered.startswith(prefix):
            text = text[len(prefix):].strip()
            break
    return text.lower()


def main() -> None:
    if not SOURCE.is_file():
        raise FileNotFoundError(f"Source CSV not found: {SOURCE}")
    if OUTPUT_DIR.exists():
        raise FileExistsError(f"Output folder already exists: {OUTPUT_DIR}")

    rows = []
    with SOURCE.open("r", encoding="utf-8-sig", newline="") as stream:
        reader = csv.DictReader(stream)
        if reader.fieldnames is None or not REQUIRED.issubset(reader.fieldnames):
            raise ValueError("CSV is missing a required column.")

        seen_ids = set()
        for row in reader:
            ref_id = row["ref_id"].strip()
            if not ref_id:
                raise ValueError("ref_id must not be blank.")
            if ref_id in seen_ids:
                raise ValueError(f"Duplicate ref_id: {ref_id}")
            seen_ids.add(ref_id)

            rows.append({
                "ref_id": ref_id,
                "title": row["title"],
                "year": row["year"],
                "doi": row["doi"],
                "normalized_doi": normalize_doi(row["doi"]),
            })

    by_doi = {}
    for row in rows:
        doi = row["normalized_doi"]
        if doi:
            by_doi.setdefault(doi, []).append(row["ref_id"])

    duplicate_dois = {
        doi: ids for doi, ids in by_doi.items() if len(ids) > 1
    }

    issue_rows = []
    for row in rows:
        doi = row["normalized_doi"]
        if not doi:
            issue_rows.append({
                "ref_id": row["ref_id"],
                "issue": "MISSING_DOI",
                "normalized_doi": "",
                "related_ref_ids": "",
            })
        elif doi in duplicate_dois:
            issue_rows.append({
                "ref_id": row["ref_id"],
                "issue": "DUPLICATE_DOI",
                "normalized_doi": doi,
                "related_ref_ids": ";".join(duplicate_dois[doi]),
            })

    OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT_DIR.mkdir()

    normalized_fields = ["ref_id", "title", "year", "doi", "normalized_doi"]
    with NORMALIZED.open("x", encoding="utf-8", newline="") as stream:
        writer = csv.DictWriter(stream, fieldnames=normalized_fields)
        writer.writeheader()
        writer.writerows(rows)

    issue_fields = ["ref_id", "issue", "normalized_doi", "related_ref_ids"]
    with ISSUES.open("x", encoding="utf-8", newline="") as stream:
        writer = csv.DictWriter(stream, fieldnames=issue_fields)
        writer.writeheader()
        writer.writerows(issue_rows)

    missing_count = sum(row["issue"] == "MISSING_DOI" for row in issue_rows)
    print(f"References checked: {len(rows)}.")
    print(f"Missing DOI rows: {missing_count}.")
    print(f"Duplicate DOI groups: {len(duplicate_dois)}.")
    print(f"Issue rows: {len(issue_rows)}.")
    print(f"Output folder: {OUTPUT_DIR.as_posix()}")


if __name__ == "__main__":
    main()

05想定される review report と比較する

issues.csv には3つの data rows が含まれるはずです。重複した normalized DOI の R001 と R003、そして missing DOI の R004 です。

csv
ref_id,issue,normalized_doi,related_ref_ids
R001,DUPLICATE_DOI,10.0000/demo.alpha,R001;R003
R003,DUPLICATE_DOI,10.0000/demo.alpha,R001;R003
R004,MISSING_DOI,,

以下の想定 console output は、合成 list と script から手作業で導出したものです。実際に取得した execution log ではありません。

text
References checked: 6.
Missing DOI rows: 1.
Duplicate DOI groups: 1.
Issue rows: 3.
Output folder: outputs/reference_list_result

06自動削除せず、flagged rows を確認する

  • R001 と R003 が同じ DOI text に normalize されることを確認します。
  • R004 は DOI が missing でも normalized reference list に残ることを確認します。
  • duplicate-DOI rows を merge するか決める前に、paper title, authors, year, publication details を確認します。
  • missing DOI rows では、DOI を追加する前にその publication が実際に DOI を持つか確認します。
  • OUTPUT_DIR を変更せずに script を再実行します。以前の review を置き換えるのではなく FileExistsError で停止するはずです。
問題確認すること
同じ paper が異なる DOI formatting で2回入力されている比較前に一般的な DOI URL と doi: prefixes を normalize します。
Blank DOI が duplicate として扱われるmissing DOI detection と duplicate DOI grouping を分けてください。
異なる papers の titles が似ているtitle similarity だけを records が duplicates である証拠として使用しないでください。
DOI に見える string が存在する存在するだけでは validity の証明になりません。重要な references は trusted metadata source で確認してください。
既存 result が上書きされる以前の review evidence を保持するため、新しい output folder を使用します。

07DOI ベースの重複除去の制限を理解する

DOI metadata が存在する場合、DOI matching は有用ですが、missing DOI だからといって reference が invalid または incomplete という意味ではありません。一部の publications や document types には DOI がない場合があり、older records では別の identifiers や manual bibliographic checks が必要になることがあります。

この script が行うのは text normalization だけです。DOI links の resolve、Crossref や別の registration agency への query、titles や authors の検証、retractions の検出、異なる DOI values が work の関連 versions を示すかどうかの判断は行いません。

実際の research database では authors, title, year, journal, volume, pages, DOI, another local reference ID などの fields を保持してください。automatic duplicate detection は bibliographic records を削除する指示ではなく、review aid として扱います。

実行・検証の記録

2026-09-20 · 手作業で確認した例 · 対象: Python 3.12 · 標準ライブラリ: csv, pathlib · 未実行

  • 合成 reference rows 6件、DOI を含む rows 5件、missing DOI row 1件を手作業で数えました。
  • R001 と R003 を同じ value 10.0000/demo.alpha に手作業で normalize しました。
  • R005 を doi:10.0000/DEMO.GAMMA から 10.0000/demo.gamma に手作業で normalize しました。
  • 空白でない DOI rows 5件の中に distinct normalized DOI values が4つあると計算しました。
  • 想定 issue report を duplicate-DOI rows 2件 + missing-DOI row 1件として導出しました。
  • duplicate ref_id checks, DOI normalization, missing と duplicate の分離処理, output collision protection, 元 CSV の保持について script を確認しました。
検証範囲の限界
  • この回答の作成者はコードを実行しておらず、CSV output files は作成していません。
  • 合成 DOI-like strings は registration または resolution を確認しておらず、実在 publications として提示していません。
  • DOI validity, bibliographic metadata accuracy, title similarity, retractions, related article versions はテストしていません。
  • 公式ドキュメントの URL は既知のドキュメント所在地に基づいて記載していますが、ライブでは確認していません。

サイト全体の執筆・検証方針

参考資料

説明と例は独自に作成しました。関連する動作や概念は、以下の公式資料で確認できます。