临床 Python 进阶路线图
⌕ /
路线图 › 第一阶段 · Python 基础

第 01 章 · 环境搭建与思维转换

本章目标:把 Python 环境跑起来(15 分钟),并完成最关键的一步—— 理解 Python 与你熟悉的 SAS 在执行模型上的根本差异。


1.1 环境搭建

为什么 Python 需要"环境"而 SAS 不需要

这是新手最困惑、也最容易踩坑的地方。差别来自分发模型:

SAS Python
软件本体 商业软件,一次安装,全公司统一版本 开源,任何人都能发布"包"
第三方库 主要是 SAS 官方 + 少量宏 有 50 万+ 个第三方包,版本互相依赖
冲突问题 基本不存在 普遍存在(A 包要 numpy 1.x,B 包要 2.x)
解决方案 无 虚拟环境(venv / conda)

一句话结论:虚拟环境就是给每个项目配一个"SAS 版本 + 授权库"的独立沙箱, 避免项目之间互相污染。每个项目一个环境,这是硬规矩。

推荐方案:官方 Python + venv(本项目采用)

如果你还没装 Python:

  1. 到 https://www.python.org/downloads/ 下载 Python 3.12 或 3.13 (临床场景建议不要用刚发布的奇数版本,等生态跟上)。
  2. 安装时务必勾选 Add Python to PATH。
  3. 安装完成后在终端验证:
Shell
python --version
# 期望输出:Python 3.12.x 或 3.13.x

关于 Anaconda:Anaconda 自带 pandas/numpy/Jupyter,开箱即用, PharmaSUG 的论文里也常推荐它。但它体积大(3–5 GB)、会污染全局环境。 本教程用更轻的 venv 方案;如果你的公司统一用 conda,把下面的 venv 换成 conda create 即可,后续内容完全一致。

三步建立本项目环境

Shell
# 1) 进入项目目录
cd clinical-python-roadmap

# 2) 创建虚拟环境(会在当前目录生成 .venv 文件夹)
python -m venv .venv

# 3) 激活环境
#    Windows PowerShell:
.venv\Scripts\Activate.ps1
#    Windows CMD:
.venv\Scripts\activate.bat
#    macOS / Linux:
source .venv/bin/activate

# 激活成功后,命令行提示符前会出现 (.venv)

⚠️ Windows PowerShell 常见报错: 无法加载文件 ... Activate.ps1,因为在此系统上禁止运行脚本 解决:以管理员身份运行 PowerShell,执行 Set-ExecutionPolicy -Scope CurrentUser RemoteSigned,然后重试。

安装依赖

Shell
# 确保已激活虚拟环境,然后:
pip install -r requirements.txt

# 验证
python -c "import pandas, numpy, pyreadstat; print('OK', pandas.__version__)"

requirements.txt 内容(本项目):

文本
pandas>=2.2       # 数据操作核心,等价于 DATA 步 + PROC SQL + PROC MEANS
numpy>=1.26       # 数值计算与向量化
pyreadstat>=1.2   # 读取 SAS 的 .xpt / .sas7bdat(关键!不用装 SAS)
matplotlib>=3.8   # 绘图
openpyxl>=3.1     # 读写 Excel
requests>=2.31    # HTTP 请求(Agent 章节要用)
pytest>=8.0       # 单元测试

编辑器:VS Code(推荐)

临床程序员从 SAS Enterprise Guide / Display Manager 迁移过去,VS Code 的对应关系是:

SAS 里的东西 VS Code 里的对应物
资源管理器 左侧 Explorer 面板
提交代码 (F8 / Run) 右上角 ▶ 按钮,或 Shift+Enter 执行当前单元格
日志窗口 底部 TERMINAL / JUPYTER 面板
语法高亮 内置,安装 Python 扩展后更强
断点调试 行号左侧点击打断点,F5 启动调试
程序编辑器 .py 文件,或用 Jupyter Notebook(.ipynb,交互式,类似 EG)

必装扩展:Python(Microsoft)、Jupyter(Microsoft)、Pylance、Ruff。

💡 建议:学习阶段用 Jupyter Notebook,因为它像 SAS 的 EG—— 一段一段执行、结果即时可见、变量常驻内存; 但最终交付的脚本一定要是 .py,因为那才是可版本管理、可自动化的形态。


1.2 第一个程序:不装 SAS 也能读 SAS 数据

在你敲完 pip install 之后,立刻做一件事——用 Python 打开一个真实的 临床数据集。这个即时反馈非常重要。

Python
# file: hello_clinical.py
import pyreadstat

# 读取 CDISC 试点项目的 SDTM DM 域(受试者人口学)
# data/samples/ 里已备好转换后的 CSV;如果下载了 XPT,也可以直接读 .xpt
df, meta = pyreadstat.read_xport("data/raw/dm.xpt")

print(f"行数 = {df.shape[0]},列数 = {df.shape[1]}")
print(f"前 5 个变量:{list(df.columns[:5])}")
print(df[["USUBJID", "AGE", "SEX", "RACE"]].head())
print(f"\n变量标签:AGE -> {meta.column_names_to_labels['AGE']}")

如果你已经运行了 python scripts/download_data.py --core,输出会是:

文本
行数 = 306,列数 = 25
前 5 个变量:['STUDYID', 'DOMAIN', 'USUBJID', 'SUBJID', 'RFSTDTC', 'RFENDTC', 'RFXSTRT']
       USUBJID   AGE SEX   RACE
0  01-701-1015  63.0   F  WHITE
1  01-701-1023  64.0   M  WHITE
2  01-701-1028  71.0   M  WHITE
...
变量标签:AGE -> Age

注意几个和 SAS 不同的点:

  • df.shape 返回 (行数, 列数),等价于 PROC CONTENTS 里的观测数与变量数。
  • 变量名在 pandas 里是列名的字符串;需要用 df["AGE"] 这种写法取,而不是 AGE。
  • 变量标签还在(meta.column_names_to_labels),这是 pyreadstat 帮你从 XPT 里带出来的。

1.3 思维转换:SAS 与 Python 的五个根本差异

这一段比任何语法都重要。不完成这层转换,你会写出"用 Python 语法写的 SAS"。

差异 1:语句 vs 表达式——谁在干活?

SAS
/* SAS:过程作用在数据集上,数据集是被动的 */
proc means data=adsl n mean std;
    var AGE;
    class TRT01P;
run;
Python
# Python:数据自己会做事(方法调用),主语变了
adsl.groupby("TRT01P")["AGE"].agg(["count", "mean", "std"])
SAS Python
语法形状 动词 数据; 选项; 数据.动作(参数)
主体 PROC 是主体,数据集是宾语 数据是主体,方法是动作
思维 "我要对 a 做 MEANS" "让 a 自己按组算均值"

记忆口诀:SAS 是"过程驱动",Python 是"对象驱动"。

差异 2:全局环境 vs 局部作用域

SAS 里你几乎永远在操作一个全局的 WORK 库; data 语句的输出数据集默认留在 WORK 里,下个步骤就能用。

SAS
data adsl2; set adsl; where SAFFL='Y'; run;
proc means data=adsl2; run;   /* adsl2 还在 WORK 里 */

Python 里没有自动留存的中间数据集——你必须在变量里显式接住结果:

Python
adsl2 = adsl[adsl["SAFFL"] == "Y"]      # 必须赋值给变量
adsl2.groupby("TRT01P").size()          # 然后用这个变量

🧠 这是最容易踩的坑:adsl.dropna() 写完发现数据没变—— 因为它返回新对象,不修改原对象。必须写 adsl = adsl.dropna() 或 adsl.dropna(inplace=True)。

差异 3:缺失值不是"空白",是多套机制

场景 SAS Python
数值缺失 .(特殊数值) np.nan(浮点 NaN)或 pd.NA
字符缺失 ''(空串,SAS 里空串 = 缺失) None 或 np.nan(空串 '' 不等于缺失!)
判断 if age = . pd.isna(age) 或 age != age
比较特性 SAS 自动把 . 视为最小 np.nan 参与任何比较都是 False
Python
import numpy as np
np.nan == np.nan       # False !这在 SAS 里是不可思议的
np.nan < 10            # False
np.isnan(np.nan)       # True —— 正确的判断方式

关键教训:Python 里永远不要用 == 判断缺失,用 pd.isna()。 另外,SAS 里 if name = '' then 能捕获缺失字符,Python 里空串是合法的非缺失值, 必须清洗时显式处理 "" 和 NaN 两种情况。详见第 10 章。

差异 4:逐行 vs 向量化——性能的量级差异

SAS
/* SAS 的 DATA 步天然逐行:这是它的优势,简单直观 */
data want;
    set have;
    if age >= 65 then agegrp = '>=65';
    else agegrp = '<65';
run;
Python
# 写法 A:逐行循环("用 Python 写 SAS")——慢,且不推荐
for i in range(len(df)):
    df.loc[i, "AGEGRP"] = ">=65" if df.loc[i, "AGE"] >= 65 else "<65"

# 写法 B:向量化(Python 的正道)——快百倍,代码更短
df["AGEGRP"] = np.where(df["AGE"] >= 65, ">=65", "<65")

# 写法 C:多条件(对应 SAS 的 select/when 或嵌套 if)
df["AGEGRP"] = np.select(
    [df["AGE"] < 65, df["AGE"] <= 80],
    ["<65", "65-80"],
    default=">80",
)

在 306 行数据上你感觉不到差别;在 58,700 行的 ADLBC 上, 循环写法要几秒,向量化是毫秒级。第 06 章整章都在训练这个思维。

差异 5:日志自动滚动 vs 异常立即中断

SAS Python
出错表现 日志里出现 ERROR/WARNING,程序继续跑完(或按选项停) 抛异常,程序在该行中断
排查方式 肉眼看日志 读 Traceback(从下往上读!)
主动输出 %put print() / logging
需要"继续跑" 默认行为 必须 try/except 显式处理
Python
# SAS 思维:想检查一行就 put,然后继续
# Python 思维:直接让程序崩,读 Traceback 定位
try:
    df = pd.read_sas("data/raw/not_exist.xpt", format="xport")
except FileNotFoundError:
    print("文件不存在,请先运行 download_data.py")

读 Traceback 的方法(这是新手最需要练的技能):

文本
Traceback (most recent call last):
  File "case02.py", line 47, in <module>     ← 最后一行才是真正出错的位置
    tbl = df.groupby("TRT01P")["AGEGR1"].value_counts()
  File ".../pandas/core/groupby.py", line 1234, in ...
KeyError: 'AGEGRP1'                          ← 最后一行是错误原因:列名拼错了

读法:从最下面一行往上读。最后一行告诉你"什么错", 倒数第二段告诉你"错在自己代码的哪一行"。中间那些是库的内部调用,先忽略。


1.4 SAS 与 Python 的"环境对照速查"

概念 SAS Python
库 / 目录 LIBNAME adsl 'C:\data'; 普通文件夹路径 / pathlib.Path
数据集 adsl.sas7bdat DataFrame 对象(内存)+ CSV/Parquet/xpt(磁盘)
一次分析单元 一个 .sas 程序 一个 .py 脚本 / 一个 .ipynb 笔记本
跨程序共享 永久数据集 / 宏变量 模块 import / 函数返回值 / 中间文件
版本管理 通常无(或手写 _v2.sas) Git(第 17 章)
复用代码 %INCLUDE / %MACRO import 自己的 .py 模块
环境隔离 基本不需要 虚拟环境(必须)
官方文档 support.sas.com docs.python.org + pandas.pydata.org

1.5 动手练习

  1. 完成环境搭建,python -c "import pandas; print(pandas.__version__)" 能输出版本号。
  2. 运行 python scripts/download_data.py --core 下载数据(或在 data/samples/ 里用 CSV)。
  3. 运行 1.2 节的 hello_clinical.py,确认输出 306 行。
  4. (思维转换练习) 把下面这段 SAS 用 Python 写出来,先不管优雅与否: sas data adsl2; set adsl; if AGE >= 65 then AGEGRP = '>=65'; else AGEGRP = '<65'; if SAFFL = 'Y'; run; 提示:你需要一个 np.where(或列表推导式)和一个布尔索引。

1.6 常见问题

现象 原因 解决
python 不是内部或外部命令 安装时未勾选 Add to PATH 重装并勾选,或手动加 PATH
ModuleNotFoundError: No module named 'pandas' 忘了激活虚拟环境 / 装到了别的环境 看命令行前面有没有 (.venv)
Activate.ps1 无法加载 PowerShell 执行策略 Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
中文输出乱码 Windows 控制台编码 在脚本首行加 # -*- coding: utf-8 -*-;或 PYTHONUTF8=1
pip install 极慢 默认源在国外 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
读取 XPT 报 does not exist 路径写法不对(尤其 Git Bash 的 /tmp) 用 Windows 绝对路径 r"G:\...\dm.xpt",或 pathlib.Path

上一章 ← 第 00 章 · 学习路线图 下一章 → 第 02 章 · 基础语法速通