本章目标:把大语言模型(LLM)变成你的日常生产力工具, 尤其是用它加速 SAS → Python 的迁移。 这是当前行业最真实、最有效的 AI 落地场景。
15.1 先看清 AI 在临床编程里能做什么、不能做什么
行业共识(PharmaSUG 2026 ET-223 的原话是 "AI could generate programming, but programmers who know the language efficiently handle AI"):
| 能做好 | 做不好 / 有风险 |
|---|---|
| ✅ 解释一段陌生的 SAS / Python 代码 | ❌ 保证逻辑完全等价(必须人工核对) |
| ✅ 语法翻译(DATA 步 → pandas) | ❌ 理解你的 SAP 里的业务规则 |
| ✅ 生成样板代码(程序头、循环、批处理) | ❌ 边界条件(>= 还是 >) |
| ✅ 写单元测试与文档 | ❌ 知道哪个"看起来对"的结果其实是错的 |
| ✅ 排查报错(把 Traceback 丢给它) | ❌ 在 GxP 环境下直接交付未验证的代码 |
| ✅ 生成假数据用于开发测试 | ❌ 处理受控术语/编码(MedDRA/WHODrug) |
🔥 一句话原则: AI 负责"写得快",你负责"写得对"。 临床编程的最终责任永远在人身上。任何 AI 生成的代码都必须走完整的验证流程。
15.2 高效提示词:三个真正有用的模板
模板 1:SAS → Python 逐句翻译(最高频)
你是一位精通 SAS 和 Python 的临床统计程序员,熟悉 CDISC SDTM/ADaM 标准。
请把下面的 SAS 代码转换成等价的 Python(pandas)代码。
要求:
1. 逐句对应,用注释标明每一行 Python 对应哪一行 SAS。
2. 使用向量化写法,不要用 for 循环遍历行。
3. 变量名保持全大写(CDISC 惯例)。
4. 特别说明以下容易出错的地方(如果有):
- 缺失值处理(SAS 的 . 与 pandas 的 NaN/NaT 差异)
- 舍入规则(SAS 是四舍五入,Python 是银行家舍入)
- 排序与分组顺序(SAS 的 CLASS 顺序 vs pandas 的 Categorical)
- 字符空串与缺失的区别
5. 最后给出一段"结果核对建议":我应该检查哪些数字来确认转换正确。
SAS 代码:
```sas
<粘贴你的 SAS 代码>
数据上下文:<变量清单 / 数据集行数 / 关键取值>
### 模板 2:报错诊断(最省时间)
我在用 pandas 处理临床数据时遇到报错。请诊断原因并给出修复方案。
【环境】Python 3.x + pandas 2.x 【我的代码】
<你的代码>
【报错信息】
<Traceback 全文>
【数据结构】
请: 1. 用一句话说明根本原因。 2. 给出修复后的代码,并说明改了什么。 3. 告诉我以后如何避免同类问题。
### 模板 3:代码审查(QC 视角)
请以"临床统计编程 QC 人员"的视角审查下面这段 Python 代码, 找出可能导致结果与方法学不一致的问题。
重点检查: 1. 分母是否正确(分析人群、受试者数 vs 事件数) 2. 缺失值是否被意外排除或意外计入 3. 舍入规则是否与 SAS 一致 4. 分组/排序顺序是否可复现 5. 连接(merge)是否可能产生重复或丢失记录 6. 是否有静默错误(不报错但结果错)
【代码】<粘贴> 【对应的 SAP 要求】<粘贴相关段落>
> 💡 **模板 3 的价值极高**:它相当于给你配了一个
> "永远不疲倦的 QC 伙伴"。虽然不能替代正式 QC,
> 但能在你提交前抓掉大部分低级错误。
---
## 15.3 实战演练:把一段真实 SAS 迁移过去
### 输入:一段典型的 SAS 安全性汇总
```sas
/* 计算各组受试者中,发生 SAE 的比例 */
proc sort data=adsl; by usubjid; run;
proc sort data=adae; by usubjid; run;
data sae;
merge adsl (in=a) adae (in=b);
by usubjid;
if a;
if aeser = 'Y' then SAEFL = 'Y';
run;
proc sql;
create table sae_sum as
select trt01p,
count(distinct usubjid) as N_SAE
from sae
where SAEFL = 'Y'
group by trt01p;
quit;
proc sql;
create table denom as
select trt01p,
count(distinct usubjid) as N_TOT
from adsl
where saffl = 'Y'
group by trt01p;
quit;
data final;
merge sae_sum denom;
by trt01p;
PCT = N_SAE / N_TOT * 100;
format PCT 5.1;
run;
输出:AI 迁移结果(附核对点)
import numpy as np
import pandas as pd
# --- 对应 proc sort + merge adsl(in=a) adae; by usubjid; if a; ---
# 注意:SAS 的 merge 在这个场景下会"按 USUBJID 展开",
# a=1 但无 AE 的受试者仍会被保留(AE 变量为空)
merged = pd.merge(adsl, adae, on="USUBJID", how="left", suffixes=("", "_AE"))
# --- 对应 if aeser = 'Y' then SAEFL = 'Y'; ---
# 注意 1:pandas 里字符缺失是 NaN,不是 '',用 == 'Y' 判断即可
# 注意 2:SAS 的 if 不给 SAEFL 赋缺失时它保持为空白;pandas 需要显式给 'N' 或 NaN
merged["SAEFL"] = np.where(merged["AESER"] == "Y", "Y", np.nan)
# --- 对应第一个 proc sql(分子)---
# ★ 关键点:SAS 用 count(distinct usubjid),所以要去重
# 而且这里的分母应该是"安全性人群",不是"全部 adsl"(原 SAS 用了 adsl 全集)
sae_n = (merged.query("SAEFL == 'Y'")
.groupby("TRT01P")["USUBJID"].nunique()
.rename("N_SAE"))
# --- 对应第二个 proc sql(分母)---
denom = (adsl.query("SAFFL == 'Y'")
.groupby("TRT01P")["USUBJID"].nunique()
.rename("N_TOT"))
# --- 对应 data final ---
final = (pd.concat([sae_n, denom], axis=1)
.fillna({"N_SAE": 0})
.reset_index())
final["PCT"] = (final["N_SAE"] / final["N_TOT"] * 100).round(1)
print(final.to_string(index=False))
必须核对的点(AI 未必全部指出)
| 核对项 | 为什么重要 | 怎么查 |
|---|---|---|
how="left" 对不对 |
SAS 的 if a = 保留 adsl 全部 = left join |
比较行数与 USUBJID 数 |
| 分子分母的口径 | 原 SAS 分母用 adsl 全集;但 SAFFL='Y' 才有意义 |
这里原 SAS 可能有 bug! 需确认 |
count(distinct) |
pandas 用 nunique() 而不是 size() |
同一受试者多条 SAE 时数字会不同 |
| SAE 的数量 | 试点数据里 AESER='Y' 只有 3 条记录 |
(adae["AESER"]=="Y").sum() |
| 输出顺序 | 治疗组顺序必须与 SAP 一致 | 用 Categorical |
🔥 注意上面第 2 行:AI 转换时可能帮你发现原 SAS 代码的 bug。 例子中的原代码用
adsl全集算分母,但变量叫N_TOT且加了where saffl='Y'—— 两处口径不一致。这正是"知道语言的人才能有效使用 AI"的含义。
15.4 用 LLM 加速的其他高价值场景
场景 1:批量生成程序骨架与文档
"""用 LLM 从统计需求生成程序头注释与骨架代码"""
PROMPT = """你是临床统计程序员。请为下面的 TLF 生成一段 SAS/Python 程序头注释。
输出要求:只输出注释块,包含字段:
项目/研究、程序名、程序用途、输入数据集、输出、创建人、创建日期、修订历史。
TLF 信息:
{tlf_info}
"""
def gen_program_header(client, tlf_info: str) -> str:
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": PROMPT.format(tlf_info=tlf_info)}],
temperature=0.2,
)
return resp.choices[0].message.content
场景 2:生成测试用假数据
FAKE_DATA_PROMPT = """生成一段 Python 代码,造一个模拟的 SDTM AE 数据集用于开发测试。
要求:
- 200 个受试者,USUBJID 格式为 '01-{site:03d}-{subj:04d}'
- 每个受试者 0~8 条 AE
- 含变量:USUBJID, AESEQ, AETERM, AEDECOD, AEBODSYS, AESEV, AESER, AESTDTC, AEENDTC
- AESEV 只能是 MILD/MODERATE/SEVERE,分布约为 65%/30%/5%
- AESTDTC 在 2018-01-01 到 2020-12-31 之间
- 用 numpy 的 default_rng(seed) 保证可复现
- 输出纯代码,不要解释
"""
⚠️ 重要合规提醒:绝对不要把真实的患者数据、 未公开的研究数据、公司内部代码放进公开的 LLM 服务。 使用前必须确认: 1. 公司/客户是否允许使用外部 LLM 服务; 2. 数据是否已充分去标识化; 3. 是否有本地部署(如 Ollama + 开源模型)可用。 在临床环境里,这通常比技术问题更重要。
场景 3:给代码加注释与文档
请为下面的函数补充 NumPy 风格的 docstring,
并逐行添加中文注释说明它在临床数据处理中的作用。
注意:注释要解释"为什么这么做",而不只是"这行做了什么"。
15.5 关于"AI 自动迁移 SAS→R/Python"的行业现状
PharmaSUG 2026 有几篇论文直接讨论这个:
| 论文 | 内容 | 链接 |
|---|---|---|
| AI-305 Merck | Improving AI SAS-to-R Code Migration via an Intermediate Design Document Layer:用"中间设计文档"作为 AI 迁移的桥梁,提升迁移准确性 | |
| AI-332 Merck | A Human-in-the-Loop AI-Assisted Framework for ADaM Standardization:人在环中的 AI 辅助 ADaM 标准化框架 | |
| SS-261 TUM | Closing the Loop: Validating AI-Generated SDTM Mappings using CDISC CORE and Synthetic Data | |
| ET-223 Servier | Python as a Cost-Effective Solution for Clinical Statistical Programming(完整 Python TFL 工作流) |
从这些论文能提炼出的行业共识:
- 纯 AI 迁移不可靠,需要"中间表示"(设计文档/规格)来约束。
- 人在环中(Human-in-the-Loop)是当前最现实的架构。
- 验证要靠自动化工具(如 CDISC CORE)而不是靠人眼。
- "懂语言的程序员"是 AI 时代更值钱而不是更不值钱。
💡 PharmaSUG 2026 AI-305 的"中间设计文档层"思路值得直接借鉴: 不要直接把 SAS 丢给 AI 要 Python,而是先让 AI 把 SAS 的意图 写成一份结构化的设计说明(输入、人群、派生规则、统计方法、输出格式), 你审核这份说明无误后,再让 AI 按说明写 Python。 多一步,但准确性提升明显,而且这份说明本身就是很好的 QC 依据。
15.6 工具清单
| 用途 | 工具 | 说明 |
|---|---|---|
| 对话式编程 | Claude / ChatGPT / 国内大模型 | 主力 |
| IDE 内置补全 | GitHub Copilot、Cursor、Trae、Qoder | 你已在用多款 AI 编辑器 |
| 本地/离线模型 | Ollama + Qwen / DeepSeek | 数据不出内网 |
| 代码解释 | 直接把代码贴给 LLM | 读别人代码效率翻倍 |
| 报错诊断 | 把 Traceback 贴给 LLM | 省掉大量搜索时间 |
| 结构化迁移 | 先让 LLM 写设计文档,再写代码 | 见 15.5 |
15.7 动手练习
-
迁移练习:用 15.2 的模板 1,把一段你自己的 SAS 程序 (20-40 行)交给 LLM 迁移,然后按 15.3 的核对清单逐项验证。 记录 AI 出错的地方——这比成功案例更有价值。
-
报错诊断:故意写一段有错的 pandas 代码(比如不带括号的
&条件、 忘记na=False),把报错交给 LLM,看你能否从它的回答里学到新东西。 -
QC 审查:把你写的一段 TFL 代码用模板 3 让 LLM 审查, 看它能否发现"分母口径"或"受试者去重"的问题。
-
假数据:用 15.4 场景 2 的提示词生成一份假的 AE 数据集, 用于测试你的 AE 汇总表脚本。
-
(合规思考) 列出你所在环境使用 LLM 的三条红线, 以及为规避这些红线你打算采取的措施。
上一章 ← 第 14 章 · 统计分析与可视化 下一章 → 第 16 章 · Agent 开发入门:从 API 调用到临床 QC Agent