5. 整理スクリプトを保存
全プラットフォーム
clean_chemical_data.py として保存し、出力列を確認して必要な場合だけ --pubchem を有効にします。
"""Preserve original CSV rows while adding RDKit identifiers and optional CIDs.
Sources: https://www.rdkit.org/docs/GettingStartedInPython.html
https://docs.pubchempy.org/en/latest/guide/searching.html
Source-reviewed example; not executed. No salt removal or charge normalization.
"""
import argparse
import csv
import json
import time
from pathlib import Path
from rdkit import Chem, rdBase
def main():
parser = argparse.ArgumentParser()
parser.add_argument("input", type=Path)
parser.add_argument("output", type=Path)
parser.add_argument("--pubchem", action="store_true")
args = parser.parse_args()
added = ["source_row", "canonical_isomeric_smiles", "validation_status",
"duplicate_of_row", "rdkit_version", "pubchem_cids", "lookup_status", "lookup_error"]
with args.input.open(encoding="utf-8-sig", newline="") as source:
reader = csv.DictReader(source)
fields = reader.fieldnames or []
if "smiles" not in fields or len(fields) != len(set(fields)) or set(fields).intersection(added):
parser.error("Require a unique smiles column and no collisions with output columns.")
seen = {}
lookup_cache = {}
# Exclusive creation prevents overwriting either source or existing output.
with args.output.open("x", encoding="utf-8", newline="") as target:
writer = csv.DictWriter(target, fieldnames=fields + added)
writer.writeheader()
for row_number, row in enumerate(reader, start=2):
if None in row:
parser.error("Malformed CSV row; partial output was retained for inspection.")
result = dict(row, source_row=row_number, canonical_isomeric_smiles="",
validation_status="", duplicate_of_row="", rdkit_version=rdBase.rdkitVersion,
pubchem_cids="[]", lookup_status="not_requested", lookup_error="")
smiles = (row.get("smiles") or "").strip()
molecule = Chem.MolFromSmiles(smiles) if smiles else None
if molecule is None:
result["validation_status"] = "empty" if not smiles else "invalid"
else:
canonical = Chem.MolToSmiles(molecule, canonical=True, isomericSmiles=True)
result["canonical_isomeric_smiles"] = canonical
result["validation_status"] = "valid"
if canonical in seen:
result["duplicate_of_row"] = seen[canonical]
else:
seen[canonical] = row_number
if args.pubchem:
if canonical not in lookup_cache:
import pubchempy as pcp
try:
compounds = pcp.get_compounds(canonical, "smiles")
cids = sorted({item.cid for item in compounds if item.cid is not None})
lookup_cache[canonical] = (json.dumps(cids), "matched" if cids else "not_found", "")
except (pcp.PubChemPyError, OSError, ValueError) as error:
lookup_cache[canonical] = ("[]", "error", str(error))
time.sleep(0.3)
result["pubchem_cids"], result["lookup_status"], result["lookup_error"] = lookup_cache[canonical]
writer.writerow(result)
if __name__ == "__main__":
main()
公式出典期待される結果
.venv の隣に保存し、入力行は未変更。
6. 受け入れ用サンプルを保存
全プラットフォーム
input.csv として保存します。重複、不正 SMILES、空値、塩を意図的に含めています。
id,smiles
ethanol-a,CCO
ethanol-b,OCC
invalid,not-a-smiles
empty,
salt,CC(=O)[O-].[Na+]
公式出典7. ローカル整理を実行
全プラットフォーム
Windows では ..venv\Scripts\python.exe に置換します。この決定論的確認には --pubchem を付けません。
.venv/bin/python clean_chemical_data.py input.csv cleaned.csv
公式出典期待される結果
五行を保持。有効三行、不正一行、空一行。ethanol-b は元行 2 を重複先とし、塩はドット区切りのナトリウム陽イオンと酢酸陰イオンを保持。lookup_status はすべて not_requested、入力は未変更。
8. この指示で出力を確認
全プラットフォーム
人による確認項目として使用するか、公開の小サンプルだけを任意のアシスタントに渡します。クラウド送信はプライバシー条件を変えます。
生成した cleaned.csv を確認し、元の id と smiles 行を保持して有効・不正・空・重複の行数を示してください。ethanol-a と ethanol-b が同じ分子を表すことを説明し、salt 行の両荷電フラグメントが残っているか調べます。行削除、塩の中和・除去、欠落 CID の推測をせず、提供 CSV だけを根拠にしてください。
公式出典期待される結果
説明が実 CSV と一致し構造を削除・変更しない。
9. 任意で PubChem 拡充を実行
全プラットフォーム
別の出力名を使い取得日時と lookup_status/lookup_error を調べます。複数 CID を保持し、機密データを外部送信しません。
.venv/bin/python clean_chemical_data.py input.csv enriched.csv --pubchem
公式出典期待される結果
元の行数を保持し、有効行に実際の CID 一覧または明示エラーを記録。