单细胞转录组流程三:超详细!Scanpy打通单细胞常规流程

有人可能会说:单细胞分析使用Seurat,monocle等R包会更加方便。但是实际分析中,测试情况是当细胞量大于5万时。一般小型服务器内存很容易不足,这时候请不要过多尝试使用Seurat来进行分析,monocle2更是。而基于python的单细胞转录分析包scanpy,能很好的解决内存不足的问题,亲测整合80万细胞量时,整个预处理流程在6小时左右能够完成。
scanpy相关python 包安装(安装好python3之后,终端运行),一定要注意的是,这里的python最好不要是3.9版本往上的,否则涉及多单细胞数据整合加载bbknn包会报错,numba将无法与当前环境适配。

pip install -i https://pypi.doubanio.com/simple/ scanpy==1.6.1 
##注意要加上-i参数,给pip加上一个豆瓣或者清华(https://pypi.tuna.tsinghua.edu.cn/simple)的镜像,否则下载起来你即使加了````--default-timeout=1000```也无济于事
#我个人建议在conda中新建一个环境,执行:
conda create -n scrna_test python=3.7.0 numba=0.55.2 pandas=1.1.5 llvmlite=0.38.1 numpy=1.21.6 bbknn=1.5.1
#然后在scanpy官网上下载scanpy-1.9.1-py3-none-any.whl(https://pypi.org/project/scanpy/#files)
wget https://files.pythonhosted.org/packages/51/87/a55c7992cba9b189de70eae37e9f1e2abe6fdaf3f087d30356f28698948e/scanpy-1.9.1-py3-none-any.whl
#下载好后
pip install scanpy-1.9.1-py3-none-any.whl 
#下载scanpy非常的困难,因为他对numba和 llvmlite有版本要求
#如果你在安装scanpy没有安装好pandas, numpy,numba,  llvmlite,事情会变得非常复杂,报错一个接一个。
#conda env create -n env_name pakage1=version package2=version... 非常好用,最好是通过文献看一下单细胞的开发环境,然后把他们复制过来。
#然后也可以看一下自己的conda环境下pip list都是哪些版本,也可以进行移植。

1. 运行python3,导入相关包及设置一些必要路径:

import scanpy as sc
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
sc.settings.verbosity = 3 # verbosity即冗余 。设置日志等级: errors (0), warnings (1), info (2), hints (3),取值表示测试日志结果显示的详细程度,数字越大越详细。
sc.logging.print_versions() # 输出版本号
sc.settings.set_figure_params(dpi=80) # set_figure_params 设置图片的分辨率/大小以及其他样式
import os #在服务器运行,习惯性会设置一个输出路径,用于保存pdf图片
os.getcwd()  #查看当前路径
os.chdir('./filtered_gene_bc_matrices/scanpy') #修改路径
os.getcwd()
results_file = 'pbmc3k.h5ad' #声明什么用于储存分析结果

2. 读取并查看数据:

scanpy可以读取.h5 .h5ad 以及10X标准化(features.tsv,barcodes.tsv, matrix.mtx)格式数据

data = sc.read_10x_h5("./pbmc3K.h5",genome=None, gex_only=True)
print(data)
AnnData object with n_obs × n_vars = 2700 × 32738
    var: 'gene_ids'
"""
#cell数700 基因数32738的矩阵
#也可以是data = sc.read_10x_matrix('path',var_names = 'gene_symbols', cache = True)
#使用gene_symbols作为AnnData的特征名称
#cache=True , cache为True表示写入缓存(cache) 便于快速读写
#如果是h5ad格式,可以直接read('/.h5ad')
#data.X 存储count matrix,数据类型为稀疏矩阵scipy.sparse.csr.csr_matrix 
#data.obs 存储关于 obervations(cells) 的 metadata,数据类型为 pandas dataframe 
#data.var 存储关于 variables(genes) 的 metadata,数据类型为  pandas dataframe 
#AnnData.uns 存储后续附加的其他非结构化信息 
#data.obs_names 和 data.var_names是 index 
#细胞名和基因名可分别通过 data.obs_names 和 data.var_names 查看。 AnnData 对象可以像 dataframe 一样进行切片操作,例如,data_subset = data[:, list_of_gene_names]
"""
AnnData数据结构

AnnData各部分数据类型

功能 数据类型
data.X 矩阵数据 numpy,scipy sparse,matrix
data.obs 观察值数据 pandas dataframe
data.var 特征和高变基因数据 pandas dataframe
data.uns 非结构化数据 dict

3. 索引去重:

data.var_names_make_unique()
# # 如果在 sc.read_10x_mtx 中使用了var_names='gene_ids',这一步就是不必要的

4. 质量控制:

质量控制3个指标:测到的转录本总数(total_counts)、测到的基因总数(total_cells)、来源于线粒体基因的转录本所占比例。

4.1基本过滤

sc.pp.filter_cells进行细胞的过滤,该函数保留至少有 min_genes 个基因(某个基因表达非0可判断存在该基因)的细胞,或者保留至多有 max_genes 个基因的细胞;
sc.pp.filter_genes进行基因的过滤,该函数用于保留在至少 min_cells 个细胞中出现的基因,或者保留在至多 max_cells 个细胞中出现的基因;

sc.pp.filter_cells(data,min_genes=200)
sc.pp.filter_genes(data,min_cells=3)
data
"""
AnnData object with n_obs × n_vars = 2700 × 13714
    obs: 'n_genes'
    var: 'gene_ids', 'n_cells'
"""
4.2 计算质量控制指标:

*选择阈值去除高表达量的细胞,阈值很大程度上取决于对自己项目的了解程度,因为不同器官组织提取的单细胞,线粒体基因平均水平不一样。

## startswith()方法用于检查字符串是否是以指定子字符串开头, 如果是则返回 True, 否则返回 False
# mitochondrial genes
data.var['mt'] = data.var_names.str.startswitch('MT-')  
# hemoglobin genes.  血红蛋白基因
data.var['hb'] = data.var_names.str.contains('^HB[^P]')
# ribosomal genes
data.var['ribo'] = data.var_names.str.startswitch('RPS','RPL')
"""
AL627309.1       False
                 ...  
SRSF10-1         False
Name: mt, Length: 13714, dtype: bool
"""

sc.pp.calculate_qc_metrics(data, qc_vars=['mt',‘hb’,'ribo'], percent_top=None, log1p=False, inplace=True)
print(data)
#data:Anndata;
#qc_vars:用于标识要控制的特征(基因),布尔型元素,用于作为mask使用;
#percent_top:计算与常出现基因的比,percent_top=[50] 计算与第 50 个最常出现基因的比例,None则不计算;
#inplace:决定是否将计算指标添加到var和obs中;
#log1p:设置为False可以跳过转换到log1p空间的过程;log1p即log(1+number),用于压缩数据并确保结果是一个正数;
"""
注释中多了很多QC计算得到的信息
AnnData object with n_obs × n_vars = 2700 × 13714
    obs: 'n_genes', 'nFeature_RNA', nCount_RNA', 'total_counts_mt', 'pct_counts_mt'
    var: 'gene_ids', 'n_cells', 'mt', 'n_cells_by_counts', 'mean_counts', 'pct_dropout_by_counts', 'total_counts'
"""
# 绘制高表达的前20个基因
sc.pl.highest_expr_genes(data, n_top=20, save='_pbmc3k.png')
n_top=20
"""
使用violinplot度量以下质量:
n_genes_bt_counts:每个细胞中,有表达的基因的个数;
total_counts:每个细胞的基因总计数(总表达量,umi数);
pct_counts_mt:每个细胞中,线粒体基因表达量占该细胞所有基因表达量的百分比
pct_counts_hb:每个细胞中,血红蛋白基因表达量占该细胞所有基因表达量的百分比
pct_counts_ribo:每个细胞中,核糖体RNA基因表达量占该细胞所有基因表达量的百分比
"""
sc.pl.violin(data, ['n_genes_by_counts', 'total_counts', 'pct_counts_mt','pct_counts_ribo', 'pct_counts_hb'],
             jitter=0.4,  multi_panel = True)
1664981487608.png
# filter for percent mito
data = data[data.obs['pct_counts_mt'] < 20, :]
# filter for percent ribo > 0.05
data = data[data.obs['pct_count_ribo'] > 5,:]
由于线粒体和 MALAT1 基因的表达水平被认为主要是技术性的,因此除了计算每个细胞中线粒体基因,血红蛋白基因和核糖体基因所占的比例外,明智的做法是还要将线粒体基因,血红蛋白基因,MALAT1基因从数据集中直接删除,然后再进行任何进一步的分析
#delete var_names.str.startswitch['MT-'] 
#var_names.str.startswitch('MALAT1') var_names.str.contains('^HB[^(P)]')
mito_genes = data.var_names.str.startswith('MT-')
malat1 = data.var_names.str.startswith('MALAT1')
hb_genes = data.var_names.str.contains('^HB[^(P)]')
remove = np.add(mito_genes, malat1)
remove = np.add(remove, hb_genes)
keep = np.invert(remove)
data = data[:,keep]

根据基因数量再进行过滤,对data进行切片(类似于Seurat中的subset)

data = data[data.obs['n_genes_by_counts'] < 2500, :]
data = data[data.obs['n_genes_by_counts'] > 500,:]
"""
View of AnnData object with n_obs × n_vars = 2638 × 13714
    obs: 'n_genes', 'nFeature_RNA', nCount_RNA', 'total_counts_mt', 'pct_counts_mt'
    var: 'gene_ids', 'n_cells', 'mt', 'n_cells_by_counts', 'mean_counts', 'pct_dropout_by_counts', 'total_counts'
"""

我们先把矩阵提取出来看一下表达量的值

mat = pd.DataFrame(data=test.X.todense(),index=test.obs_names,columns=test.var_names)
mat

注意原始值都是整数


image.png

5. 数据标准化:

Normalize each cell by total counts over all genes, so that every cell has the same total count after normalization.
总计数标准化 ,以便细胞之间的基因表达量具有可比性

sc.pp.normalize_total(data, target_sum=1e4)
## normalize with total UMI count per cell

函数normalize_total可以对每个细胞进行标准化,以便每个细胞在标准化后沿着基因方向求和具有相同的总数 ````
target_sum:

data.X
array([[ 3.,  3.,  3.,  6.,  6.],
       [ 1.,  1.,  1.,  2.,  2.],
       [ 1., 22.,  1.,  2.,  2.]], dtype=float32)
# 设置 target_sum=1 标准化后
X_norm
array([[0.14, 0.14, 0.14, 0.29, 0.29],
       [0.14, 0.14, 0.14, 0.29, 0.29],
       [0.04, 0.79, 0.04, 0.07, 0.07]], dtype=float32)

看一下做完标准化的结果


image.png

没取标准化之前:

View of AnnData object with n_obs × n_vars = 15072 × 21655
    obs: 'n_genes', 'n_genes_by_counts', 'total_counts', 'total_counts_mt', 'pct_counts_mt', 'total_counts_hb', 'pct_counts_hb', 'total_counts_ribo', 'pct_counts_ribo', 'doublet_score', 'predicted_doublet'
    var: 'n_cells', 'mt', 'hb', 'ribo', 'n_cells_by_counts', 'mean_counts', 'pct_dropout_by_counts', 'total_counts'
    uns: 'scrublet'

标准化之后:

AnnData object with n_obs × n_vars = 15072 × 21655 
obs: 'n_genes', 'n_genes_by_counts', 'total_counts', 'total_counts_mt', 'pct_counts_mt', 'total_counts_hb', 'pct_counts_hb', 'total_counts_ribo', 'pct_counts_ribo', 'doublet_score', 'predicted_doublet' 
var: 'n_cells', 'mt', 'hb', 'ribo', 'n_cells_by_counts', 'mean_counts', 'pct_dropout_by_counts', 'total_counts' 
uns: 'scrublet'

标准化前后只是改变了data.X 没有增加obs或者var的内容。

#将数据压缩到log1p的空间
sc.pp.log1p(data)

log1p 也就是log(X+1) 也就是In(X+1)


image.png

image.png

5. 高变基因选取:

高变异基因就是highly variable features(HVGs),就是在细胞与细胞间进行比较,选择表达量差别最大的基因,Seurat使用FindVariableFeatures函数鉴定高变基因,这些基因在不同细胞之间的表达量差异很大(在一些细胞中高表达,在另一些细胞中低表达)。默认情况下,会返回2,000个高变基因用于下游的分析。
利用FindVariableFeatures函数,会计算一个mean-variance结果,也就是给出表达量均值和方差的关系并且得到top variable features,这一步的目的是鉴定出细胞与细胞之间表达量相差很大的基因,用于后续鉴定细胞类型。
标记基因 (marker gene),是一种已知功能或已知序列的基因,能够起着特异性标记的作用。
标记基因通常是高变基因中很小的子集;

#识别高度可变基因,并进行过滤:
sc.pp.highly_variable_genes(data,min_mean = 0.0125,max_mean=3,min_disp=0.5)
sc.pl.highly_variable_genes(data)
#保存原始数据后再把data变成只有高变基因
data.raw = data
#过滤
##注意切片data[obs:var]
data = data[:,data.var['highly_variable']]
image.png

6. 归一化(将数据缩放到单位方差):

>>>将数据data.X缩放到单位方差和零均值,对于缩放后的数据,在值为10处截断:
""" sc.pp.regress_out(data, keys) keys:要回归的data.obs中的数据列 """
# 回归 adata.obs 中的列 (columns) 
# 回归每个细胞的总计数和表达的线粒体基因的百分比的影响。
sc.pp.regress_out(data, ['total_counts', 'pct_counts_mt'])
# 将每个基因缩放到单位方差。
sc.pp.scale(data,max_value = 10)
#保存数据
data.write(results_file)
需要注意的是,在做完归一化后,data.X的数据格式从scipy.sparse.csr_matrix转换为numpy.ndarray

如果你这时候想看矩阵情况,需要进行转换

s = scipy.sparse.csr_matrix(data.X)
mat = pd.DataFrame(s.todense(),index = data.obs_names,colums = data.var_names)
image.png

7. 主成分分析:

通过运行主成分分析(PCA)来降低数据的维数,该分析揭示了变化的主轴并对数据进行去噪。

#svd_solver:指定奇异值分解SVD的方法,有4个可以选择的值:{‘auto’, ‘full’, ‘arpack’, ‘randomized’,}
#如果设为’arpack’,则使用scipy.sparse.linalg.svds计算SVD分解。这种方法严格要求 0 < n_components < min(样本数,特征数)。
sc.tl.pca(data,svd_solver = 'arpack')
sc.pl.pca(data, color = 'CST3')

PCA将高维数据data.X聚类到2维空间后得到的只是一些平面下的散点,但我们可以根据每个散点(细胞)中包含基因CST3的数量为散点标记颜色。


计算单个pc对数据总方差的贡献,这可以提供给我们应该考虑多少个 PC 以计算细胞的邻域关系的信息(resolution选择多少),例如用于后续的聚类函数 sc.tl.louvain()

sc.pl.pca_variance_ratio(data,log = True)

8.降维(对neighborhood graph进行embedding)

sc.pp.neighbors(data,n_neighbors=10,n_pcs=25)
"""
computing neighbors
    using 'X_pca' with n_pcs = 40
    finished: added to `.uns['neighbors']`
    `.obsp['distances']`, distances for each pair of neighbors
    `.obsp['connectivities']`, weighted adjacency matrix (0:00:05)
"""
sc.tl.umap(data)

9. 聚类

scanpy提供leiden和louvain两种图聚类算法,图聚类在开始之前要先找到邻居。
首先计算neighborhood graph,我们使用数据矩阵的PCA表示来计算细胞的邻域图。可以在这里简单地使用默认值。为了可视化聚类的结果我们做一下umap降维,看看分群结果。

sc.tl.leiden(data,resolution = 0.3)
#用umap可视化聚类结果
sc.pl.umap(data,color = 'leiden', frameon=False, title='',
           legend_loc='right margin', legend_fontsize=12,
           legend_fontweight='normal', legend_fontoutline=6,
           palette=None, cmap=None)
#platelet是指给clutesr指定一组颜色。例如color=['red','blue'....]
#cmap是指颜色选择
#legend_fontweight 是指字体粗细
#legend_loc='on data'
#legend_fontoutline:图例字体轮廓的线宽,单位为pt。使用withStroke的路径效果绘制白色轮廓。
#. 当frameon=True的时候, 图示会被绘制在一个patch实体上;
# 否则, 如果frameon=False, 则图示会被直接绘制在图片上。
#这里, 讨论是否将图示绘制在一个patch实体上的意义在于,
#当把它绘制在一个patch实体上时, 
#我们才可以使用facecolor, edgecolor, framealpha, fancybox等参数来设置图示的背景(不是图片的背景)的颜色, 边框颜色, 透明度, 以及形状, 而当frameon=False的时候这些参数就会失效

10. Marker基因查找

通过文献或cellmarker查找marker 基因


PBMC common marker genes
#先设置分辨率以及长宽
sc.settings.set_figure_params(dpi=50, dpi_save=600, figsize=(5,5))
marker_names = ['IL7R','CCR7',
                'CD14','LYZ',
               'IL7R','S100A4',
               'MS4A1',
               'CD8A',
               'FCGR3A','MS4A7',
               'GNLY','NKG7',
               'FCER1A','CST3',
               'PPBP']
sc.pl.umap(data,color = marker_names, ncols=2)
1665051072562.png

11. 通过差异表达基因寻找Marker基因

让我们计算每个cluster中高度差异基因的排名,最简单和最快的方法是t-test,当然还有wilcoxon。

sc.settings.set_figure_params(dpi=80,dpi_save=600,figsize=(10,10))
sc.tl.rank_genes_groups(data,'leiden',method='t-test')
#绘图指定每个cluster前多少个基因,每个cluster之间是否共享
sc.pl.rank_genes_groups(data,n_genes=25,sharey=False,fontsize=15)

#sharely : 控制是否应共享每个panel的y轴,sharey =False表示每个panel都有自己的y轴范围。
image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,812评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,626评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,144评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,052评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,925评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,035评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,461评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,150评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,413评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,501评论 2 307
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,277评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,159评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,528评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,868评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,143评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,407评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,615评论 2 335

推荐阅读更多精彩内容