2024 单细胞 RNA 测序数据应用系列 1 预处理

2024 单细胞 RNA 测序数据应用系列 1 预处理

对于单细胞 RNA 测序数据我们通常会进行一个预处理过程,这个也和大多数机器学习和深度学习项目一致。这里我们介绍以 h5 文件格式存储的单细胞 RNA 测序数据的预处理。预处理主要是对表达矩阵进行归一化和去除低表达基因和细胞。

数据预处理

读取计数数据(count data)

以 “ZINB-Based Graph Embedding Autoencoder for Single-Cell RNA-Seq Interpretations” (scTAG)中读取存储单细胞 RNA 测序数据为例。

数据格式介绍

首先对 scTAG 中 h5 文件进行简要介绍。H5 文件是层次数据格式第 5 代的版本(Hierarchical Data Format,HDF5),它是用于存储科学数据的一种文件格式和库文件。

h5 文件中有两个主要结构:组“group”和数据集“dataset”。一个 h5 文件就是 “group” 和 “dataset” 二合一的容器,group 名称为key,datasets 为 value。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import numpy as np

def prepro(filename):
"""
函数用于预处理数据文件。

参数:
filename (str): 数据文件的路径名。

返回值:
tuple: 一个包含两个元素的元组:
- X (numpy.ndarray): 预处理后的表达矩阵。
- cell_label (numpy.ndarray): 细胞类型的标签数组。
"""
# 设置数据文件的路径
data_path = filename
# 读取数据文件,返回四个对象:mat(表达矩阵), obs(观测信息), var(变量信息), uns(未分类信息)。
mat, obs, var, uns = read_data(data_path, sparsify=False, skip_exprs=False)
# 检查表达矩阵是否为NumPy数组类型,如果不是则转换为密集数组。
if isinstance(mat, np.ndarray):
X = np.array(mat)
else:
X = np.array(mat.toarray())
# 从观测信息中提取细胞类型名称,并将其转换为NumPy数组
cell_name = np.array(obs["cell_type1"])
# 获取唯一的细胞类型和对应的标签编码
cell_type, cell_label = np.unique(cell_name, return_inverse=True)
# 返回表达矩阵和细胞类型标签
return X, cell_label
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import h5py
import scipy as sp
import numpy as np
import scanpy as sc
import pandas as pd

def read_data(filename, sparsify=False, skip_exprs=False):
"""
从HDF5格式的数据文件中读取数据。

参数:
filename (str): HDF5文件的路径名。
sparsify (bool): 是否将表达矩阵转换为稀疏矩阵,默认为False。
skip_exprs (bool): 是否跳过表达矩阵的加载,默认为False。

返回值:
tuple: 包含四个元素的元组:
- mat (scipy.sparse.csr_matrix 或 numpy.ndarray): 表达矩阵。
- obs (pandas.DataFrame): 观测信息。
- var (pandas.DataFrame): 变量信息。
- uns (dict): 未分类信息。
"""
# 使用上下文管理器打开HDF5文件以确保正确关闭文件
with h5py.File(filename, "r") as f:
# 将观测信息(obs)从HDF5文件转换为Pandas DataFrame,并设置索引
obs = pd.DataFrame(dict_from_group(f["obs"]), index=utils.decode(f["obs_names"][...]))
# 将变量信息(var)从HDF5文件转换为Pandas DataFrame,并设置索引
var = pd.DataFrame(dict_from_group(f["var"]), index=utils.decode(f["var_names"][...]))
# 将未分类信息(uns)从HDF5文件转换为Python字典
uns = dict_from_group(f["uns"])
# 如果skip_exprs为False,则加载表达矩阵
if not skip_exprs:
exprs_handle = f["exprs"]
# 检查表达矩阵是否存储在一个HDF5 Group中,如果是则表示它是一个稀疏矩阵
if isinstance(exprs_handle, h5py.Group):
# 构建一个稀疏的CSR矩阵
mat = sp.sparse.csr_matrix((
exprs_handle["data"][...],
exprs_handle["indices"][...],
exprs_handle["indptr"][...]), shape=exprs_handle["shape"][...])
else:
# 否则,假设是一个密集矩阵并将其加载为numpy数组
mat = exprs_handle[...].astype(np.float32)
# 如果sparsify为True,则将密集矩阵转换为稀疏矩阵
if sparsify:
mat = sp.sparse.csr_matrix(mat)
else:
# 如果skip_exprs为True,则创建一个空的稀疏矩阵作为占位符
mat = sp.sparse.csr_matrix((obs.shape[0], var.shape[0]))
# 返回表达矩阵和相关的数据框及字典
return mat, obs, var, uns
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import h5py

class dotdict(dict):
"""
创建一个字典的子类,它允许使用点符号(.)来访问字典键,就像访问对象属性一样。

方法:
__getattr__(self, key): 通过点符号访问字典中的值。
__setattr__(self, key, value): 通过点符号设置字典中的值。
__delattr__(self, key): 通过点符号删除字典中的值。
"""
__getattr__ = dict.get # 获取字典中对应的值,如果不存在则返回None
__setattr__ = dict.__setitem__ # 设置字典中的值
__delattr__ = dict.__delitem__ # 删除字典中的值

def read_clean(data):
"""
清理从HDF5文件读取的数据。

参数:
data (numpy.ndarray): 需要清理的数据数组。

返回:
数据清理后的结果,可能是单个值或解码后的字符串数组。

断言:
确保传入的数据是NumPy数组类型。
"""
assert isinstance(data, np.ndarray) # 检查输入是否为NumPy数组
# 如果数据类型是字节,则进行解码转换为字符串
if data.dtype.type is np.bytes_:
data = utils.decode(data)
# 如果数组大小为1,则提取出唯一的元素作为标量返回
if data.size == 1:
data = data.flat[0] # 将单元素数组转换为标量
return data

def dict_from_group(group):
"""
递归地将HDF5 Group对象转换为Python字典。

参数:
group (h5py.Group): HDF5 Group对象,包含数据集和其他Group。

返回:
dotdict: 包含所有子组和数据集的字典,可以使用点符号访问。
"""
assert isinstance(group, h5py.Group) # 检查输入是否为HDF5 Group
d = dotdict() # 创建一个新的dotdict实例
# 遍历HDF5 Group中的每个key
for key in group:
# 如果值是一个子Group,则递归调用dict_from_group以构建嵌套字典
if isinstance(group[key], h5py.Group):
value = dict_from_group(group[key])
else:
# 否则,假设是数据集并清理其内容
value = read_clean(group[key][...]) # 使用read_clean函数清理数据
d[key] = value # 将清理后的值存储在dotdict中
return d # 返回填充好的dotdict

预处理(Pre-processing)

原始 scRAN-seq 读取计数数据由 Python 包 SCANPY 进行预处理。首先,过滤掉任何细胞中没有计数的基因。其次,计算大小因子,并根据文库大小对读取计数进行归一化,因此细胞间的总计数相同。形式上,如果我们将细胞 \(i\) 的库大小(总读取计数数)表示为 \(s_i\),则细胞 \(i\) 的大小因子为 \(s_i/median(s)\)。最后一步是对读数进行对数变换和缩放,以便计数值遵循单位方差和零均值。预处理的读取计数矩阵为模型的的输入。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import scipy as sp 
import scanpy as sc

def normalize(adata, copy=True, highly_genes=None, filter_min_counts=True, size_factors=True, normalize_input=True, logtrans_input=True):
"""
对单细胞RNA测序数据进行标准化预处理。

参数:
adata (AnnData 或 str): 输入的AnnData对象或文件路径。
copy (bool): 是否在操作前复制输入数据,默认为True。
highly_genes (int 或 None): 选择高变基因的数量。如果为None,则不选择。
filter_min_counts (bool): 是否过滤掉表达量低的基因和细胞,默认为True。
size_factors (bool): 是否计算并应用大小因子(size factors)进行标准化,默认为True。
normalize_input (bool): 是否对输入数据进行标准化,默认为True。
logtrans_input (bool): 是否对输入数据进行对数转换,默认为True。

返回:
AnnData: 经过预处理后的AnnData对象。
"""
# 检查输入类型,如果是AnnData对象且copy为True,则复制该对象;如果是字符串,则读取文件创建AnnData对象
if isinstance(adata, sc.AnnData):
if copy:
adata = adata.copy()
elif isinstance(adata, str):
adata = sc.read(adata)
else:
raise NotImplementedError
norm_error = 'Make sure that the dataset (adata.X) contains unnormalized count data.'
assert 'n_count' not in adata.obs, norm_error
# 如果数据集较小,检查是否所有元素都是整数(即原始计数数据)
if adata.X.size < 50e6: # check if adata.X is integer only if array is small
if sp.sparse.issparse(adata.X):
assert (adata.X.astype(int) != adata.X).nnz == 0, norm_error
else:
assert np.all(adata.X.astype(int) == adata.X), norm_error
# 过滤掉表达水平低于最小计数值的基因和细胞
if filter_min_counts:
sc.pp.filter_genes(adata, min_counts=1)
sc.pp.filter_cells(adata, min_counts=1)
# 根据参数设置决定是否保存原始数据到.raw属性中
if size_factors or normalize_input or logtrans_input:
adata.raw = adata.copy()
else:
adata.raw = adata
# 计算大小因子,并根据其调整每个细胞的总表达量
if size_factors:
sc.pp.normalize_per_cell(adata)
adata.obs['size_factors'] = adata.obs.n_counts / np.median(adata.obs.n_counts)
else:
adata.obs['size_factors'] = 1.0
# 如果需要,对输入数据进行对数转换
if logtrans_input:
sc.pp.log1p(adata)
# 如果指定了highly_genes数量,则选择高变基因
if highly_genes != None:
sc.pp.highly_variable_genes(adata, min_mean=0.0125, max_mean=3, min_disp=0.5,
n_top_genes=highly_genes, subset=True)
# 如果需要,对输入数据进行标准化
if normalize_input:
sc.pp.scale(adata)
return adata

参考

[1] H5文件简介以及python对h5文件的操作

[2] .h5文件的写入和读取(HDF5)

[3] .h5图像文件(数据集)的读取并存储工具贴(二)

[4] ZINB-based Graph Embedding Autoencoder for Single-cell RNA-seq Interpretations


2024 单细胞 RNA 测序数据应用系列 1 预处理
https://blog.lfd.world/2024/12/23/2024-dan-xi-bao-rna-ce-xu-shu-ju-ying-yong-xi-lie-1-yu-chu-li/
作者
培根请加蛋
发布于
2024年12月23日
许可协议