上一篇我们学习了 Python 基础,包括变量、数据类型、索引、切片、列表、字典、元组和集合。这一篇继续往前走,主要讲 Python 代码中最常用的三类结构:条件语句、循环语句、函数与 lambda 函数。这一篇不系统展开 pandas 数据框操作,也不正式进入 AnnData。我们只用一些接近单细胞分析的例子,帮助大家理解 Python 代码的运行逻辑。
一、为什么单细胞分析要学条件、循环和函数?
在单细胞分析中,很多代码其实都离不开这三类逻辑。
例如,判断一个细胞是否通过 QC:
if pct_counts_mt < 5: print(”Pass”)else: print(”High mitochondrial”)
例如,批量检查多个 marker gene:
for gene in marker_genes: print(gene)
例如,把一段重复使用的 QC 逻辑封装成函数:
def check_mt(pct_mt): if pct_mt < 5: return ”Pass” else: return ”High mitochondrial”
所以这一篇的目标不是单纯学习语法,而是理解:
Python 如何根据条件做判断,如何批量重复执行代码,以及如何把重复代码封装成函数。
第一部分:条件语句
二、最基础的 if 判断
if 的意思是:
如果条件成立,就执行下面的代码。
基础结构:
例如,判断一个细胞的线粒体比例是否低于 5%:
pct_counts_mt = 0.89if pct_counts_mt < 5: print(”这个细胞线粒体比例较低”)
输出:
这里的条件是:
如果条件成立,就会执行缩进里面的代码。
三、if...else:二选一判断
很多时候,我们不仅要判断“满足条件时做什么”,还要判断“不满足条件时做什么”。
这时可以用 if...else。
pct_counts_mt = 5.14if pct_counts_mt < 5: print(”Pass”)else: print(”High mitochondrial”)
输出:
这里的逻辑是:
如果 pct_counts_mt < 5,输出 Pass。否则,输出 High mitochondrial。
四、if...elif...else:多条件判断
如果要分成多个等级,可以用 if...elif...else。
例如根据线粒体比例给细胞做一个简单分级:
pct_counts_mt = 5.14if pct_counts_mt < 3: print(”Good”)elif pct_counts_mt < 5: print(”Acceptable”)else: print(”High mitochondrial”)
输出:
这段代码会从上到下依次判断:
先判断 pct_counts_mt < 3如果不满足,再判断 pct_counts_mt < 5如果还不满足,就进入 else
五、逻辑运算:and、or、not
在真实分析中,一个判断条件往往不够。
例如一个细胞要通过 QC,可能需要同时满足:
这时就要用逻辑运算。
1. and:两个条件都满足
n_genes_by_counts = 6732pct_counts_mt = 0.89if n_genes_by_counts > 500 and pct_counts_mt < 5: print(”Pass QC”)else: print(”Fail QC”)
输出:
and 的意思是:
两个条件都成立,整体才成立。
2. or:满足一个条件即可
如果我们想标记潜在低质量细胞:
可以写成:
pct_counts_mt = 5.14total_counts = 8344if pct_counts_mt > 5 or total_counts < 5000: print(”Potential low-quality cell”)else: print(”Normal cell”)
输出:
Potential low-quality cell
or 的意思是:
只要有一个条件成立,整体就成立。
3. not:取反
not 表示取反。
例如:
in_tissue = 0if not in_tissue: print(”这个 spot 不在组织区域内”)
输出:
在 Python 中,0 经常可以理解为 False,1 可以理解为 True。
所以:
结果是 True。
六、嵌套 if:先判断大条件,再判断小条件
有些判断是分层的。
例如空间转录组分析时,可以先判断 spot 是否在组织区域内,再判断 QC 是否通过。
in_tissue = 1pct_counts_mt = 5.14if in_tissue == 1: print(”这个 spot 在组织区域内,继续判断 QC”) if pct_counts_mt < 5: print(”通过 QC”) else: print(”线粒体比例偏高”)else: print(”这个 spot 不在组织区域内,建议去除”)
输出:
这个 spot 在组织区域内,继续判断 QC线粒体比例偏高
这种结构叫嵌套判断。
七、条件语句在单细胞分析中的常见场景
条件语句在单细胞分析中常见于:
判断细胞是否通过 QC判断基因是否存在判断样本是否需要跳过判断输出目录是否存在判断某个参数是否为空
例如,检查 marker gene 是否存在:
gene = ”LYZ”if gene in adata.var_names: print(”可以绘制这个基因”)else: print(”这个基因不在数据中”)
这类判断后面在 Scanpy 分析中会非常常见。
第二部分:循环语句
八、为什么需要循环?
如果要打印 5 次:
最笨的方法是:
print(”hello single-cell”)print(”hello single-cell”)print(”hello single-cell”)print(”hello single-cell”)print(”hello single-cell”)
如果要重复 100 次,就不能这样写了。
这时就需要循环。
九、while 循环
while 的意思是:
只要条件成立,就一直执行。
基础结构:
例如:
i = 0while i < 5: print(”hello single-cell”) i += 1
输出:
hello single-cellhello single-cellhello single-cellhello single-cellhello single-cell
这里最关键的是:
它表示每循环一次,i 增加 1。
如果忘记更新 i,就可能进入死循环。
十、用 while 理解“逐行检查”的思想
假设有一组细胞的线粒体比例:
pct_mt_list = [0.89, 0.93, 5.14, 0.80, 0.90]
我们想统计有多少个细胞线粒体比例小于 5:
i = 0pass_count = 0while i < len(pct_mt_list): pct_mt = pct_mt_list[i] if pct_mt < 5: pass_count += 1 i += 1print(pass_count)
输出:
这段代码的意思是:
从第 0 个元素开始检查。如果线粒体比例小于 5,就计数加 1。检查完所有元素后,输出通过数量。
虽然实际分析中我们更多用 pandas 或 Scanpy 的向量化写法,但这个例子可以帮助理解循环的底层逻辑。
十一、for 循环:数据分析中更常用
在数据分析中,for 循环比 while 更常用。
基础结构:
例如:
marker_genes = [”MS4A1”, ”CD3D”, ”LYZ”]for gene in marker_genes: print(gene)
输出:
每次循环,gene 会依次变成:
十二、for 循环批量处理 marker gene
在单细胞分析中,常常要批量查看多个 marker gene。
marker_genes = [”MS4A1”, ”CD3D”, ”LYZ”, ”COL1A1”]for gene in marker_genes: print(f”准备绘制:{gene}”)
输出:
准备绘制:MS4A1准备绘制:CD3D准备绘制:LYZ准备绘制:COL1A1
后面正式使用 Scanpy 时,可以改成:
for gene in marker_genes: sc.pl.umap(adata, color=gene)
这就相当于批量画多个基因的 UMAP 表达图。
十三、continue:跳过本轮循环
continue 的意思是:
跳过本轮循环,直接进入下一轮。
例如,某些 marker gene 不在数据中时,可以跳过。
marker_genes = [”MS4A1”, ”CD3D”, ”LYZ”, ”NOT_EXIST_GENE”]var_names = [”MS4A1”, ”CD3D”, ”LYZ”]for gene in marker_genes: if gene not in var_names: print(f”{gene} 不存在,跳过”) continue print(f”{gene} 存在,可以绘图”)
输出:
MS4A1 存在,可以绘图CD3D 存在,可以绘图LYZ 存在,可以绘图NOT_EXIST_GENE 不存在,跳过
十四、break:提前结束循环
break 的意思是:
直接结束整个循环。
例如,我们只想找到第一个存在的 marker gene:
marker_genes = [”NOT_EXIST_1”, ”NOT_EXIST_2”, ”LYZ”, ”CD3D”]var_names = [”MS4A1”, ”CD3D”, ”LYZ”]first_found = Nonefor gene in marker_genes: if gene in var_names: first_found = gene breakprint(first_found)
输出:
找到 LYZ 后,循环就结束了,不再继续检查后面的 CD3D。
十五、嵌套循环:多个细胞类型和多个 marker
有时候我们有多个细胞类型,每个细胞类型又有多个 marker gene。
marker_dict = { ”T cell”: [”CD3D”, ”CD3E”, ”TRAC”], ”B cell”: [”MS4A1”, ”CD79A”, ”CD79B”], ”Macrophage”: [”LYZ”, ”C1QA”, ”C1QB”]}
可以用嵌套循环:
for celltype, genes in marker_dict.items(): print(f”正在查看 {celltype} markers”) for gene in genes: print(gene)
输出:
正在查看 T cell markersCD3DCD3ETRAC正在查看 B cell markersMS4A1CD79ACD79B正在查看 Macrophage markersLYZC1QAC1QB
这就是循环嵌套:
外层循环控制细胞类型内层循环控制 marker gene
第三部分:函数
十六、为什么需要函数?
如果一段代码需要反复使用,就可以写成函数。
例如判断线粒体比例:
if pct_counts_mt < 5: result = ”Pass”else: result = ”High mitochondrial”
如果每次都复制这段代码,会很麻烦。
我们可以把它封装成函数:
def check_mt(pct_counts_mt): if pct_counts_mt < 5: return ”Pass” else: return ”High mitochondrial”
以后只需要调用:
输出:
十七、函数的基本结构
函数结构如下:
def 函数名(参数): 函数内部代码 return 返回结果
例如:
def say_hello(): print(”hello single-cell”)
调用函数:
输出:
十八、带参数和返回值的函数
def check_mt(pct_counts_mt): if pct_counts_mt < 5: return ”Pass” else: return ”High mitochondrial”
调用:
输出:
输出:
这里:
pct_counts_mt 是参数return 后面的内容是返回值
十九、函数可以有多个参数
例如同时根据基因数和线粒体比例判断 QC:
def check_qc(n_genes_by_counts, pct_counts_mt): if n_genes_by_counts < 500: return ”Low genes” elif pct_counts_mt > 5: return ”High mitochondrial” else: return ”Pass”
调用:
输出:
输出:
二十、给函数设置默认参数
函数可以设置默认参数。
def check_qc(n_genes_by_counts, pct_counts_mt, min_genes=500, max_mt=5): if n_genes_by_counts < min_genes: return ”Low genes” elif pct_counts_mt > max_mt: return ”High mitochondrial” else: return ”Pass”
默认情况下:
min_genes = 500max_mt = 5
调用:
如果想修改阈值:
check_qc(6732, 0.89, min_genes=1000, max_mt=10)
函数设置默认参数后,更适合后面重复使用。
二十一、函数在单细胞分析中的用途
函数在单细胞分析中常用于:
封装 QC 规则封装绘图代码封装读取样本代码封装 marker gene 检查封装重复使用的分析流程
例如:
def get_valid_genes(genes, var_names): valid_genes = [] for gene in genes: if gene in var_names: valid_genes.append(gene) else: print(f”{gene} 不存在,跳过”) return valid_genes
调用:
marker_genes = [”MS4A1”, ”CD3D”, ”LYZ”, ”NOT_EXIST”]var_names = [”MS4A1”, ”CD3D”, ”LYZ”]valid_genes = get_valid_genes(marker_genes, var_names)print(valid_genes)
输出:
第四部分:lambda 函数
二十二、什么是 lambda 函数?
lambda 可以理解成一种简短函数。
普通函数写法:
def add_one(x): return x + 1
lambda 写法:
add_one = lambda x: x + 1
调用:
输出:
二十三、lambda 更适合简单的一行逻辑
例如判断线粒体比例是否高:
mt_status = lambda x: ”High mitochondrial” if x > 5 else ”Pass”
调用:
输出:
调用:
输出:
二十四、lambda 常和 apply 配合使用
在下一篇 pandas 数据框中,我们会系统介绍 apply()。
这里先看一个简单例子:
import pandas as pddf = pd.DataFrame({ ”pct_counts_mt”: [0.89, 0.93, 5.14, 0.80]})df[”mt_status”] = df[”pct_counts_mt”].apply( lambda x: ”High mitochondrial” if x > 5 else ”Pass”)df
结果:
pct_counts_mt mt_status0 0.89 Pass1 0.93 Pass2 5.14 High mitochondrial3 0.80 Pass
这就是 lambda 在数据分析中的常见用法。
第五部分:常见错误
1. if 后面忘记冒号
错误:
if pct_counts_mt < 5 print(”Pass”)
正确:
if pct_counts_mt < 5: print(”Pass”)
2. 缩进错误
错误:
if pct_counts_mt < 5:print(”Pass”)
正确:
if pct_counts_mt < 5: print(”Pass”)
Python 用缩进表示代码层级,所以缩进非常重要。
3. while 循环忘记更新计数器
错误:
i = 0while i < 5: print(i)
这会死循环。
正确:
i = 0while i < 5: print(i) i += 1
4. continue 前没有处理计数器
在 while 循环中使用 continue 时尤其要小心。
错误写法可能导致死循环:
i = 0while i < 10: if i == 3: continue print(i) i += 1
正确写法:
i = 0while i < 10: if i == 3: i += 1 continue print(i) i += 1
5. 函数忘记 return
错误:
def check_mt(x): if x > 5: ”High mitochondrial” else: ”Pass”
正确:
def check_mt(x): if x > 5: return ”High mitochondrial” else: return ”Pass”
没有 return,函数就不会真正把结果返回出来。
本篇小结
这一篇主要介绍了 Python 的进阶语法:
1. 条件语句:if / elif / else2. 逻辑运算:and / or / not3. 循环语句:while / for4. 循环控制:break / continue5. 函数:def / return6. lambda 函数
最重要的几句话:
if 用来做判断。for 用来批量处理。while 用来在条件成立时重复执行。break 用来提前结束循环。continue 用来跳过本轮循环。函数用来封装重复代码。lambda 适合写简单的一行函数。
下一篇我们不急着进入 AnnData,而是先补上 Python 数据分析中最重要的工具:
pandas 数据框操作。
掌握 pandas 之后,再学习 AnnData 会更容易理解。