在前面的笔记中:扎克伯格背刺基于R语言的Seurat单细胞生态 我提到了一个比较大数据量(接近100万个细胞啦)的单细胞转录组项目,是 929,686 cells derived from 156 fresh clinical samples obtained from 41 HGSOC patients 。
这样的话,它是 31815个基因 ,是 929690 个细胞,所以数值会很恐怖,是29578087350,但是因为是稀疏矩阵,所以这个单细胞表达量矩阵里面绝大部分都是0值,真正有数值的地方是2178171554,差不多是7.36%,这个是单细胞转录组的特性:drop-out。如下所示:
> 2178171554/29578087350
[1] 0.07364139
既然数值的地方是2178171554,那么在R编程语言里面读取sparse_matrix.mtx文件会 报错:
Error in scan(file, nmax = 1, what = what, quiet = TRUE, ...) :
scan() expected 'an integer', got '2178171554'
Calls: readMM -> scan1 -> scan
Execution halted
==> inputs/sparse_matrix.mtx <==
%%MatrixMarket matrix coordinate real general
%
31815 929690 2178171554
因为上面的数值 2178171554 确实超出了 R 中 int
类型的范围。在 32 位系统中,R 的 int
类型通常是一个 32 位整数,其范围是 -2^31 到 2^31-1,即 -2,147,483,648 到 2,147,483,647。
但是经费是有限的,不可能说是无限制添加内存条,所以我提出来了:在R编程环节有所限制未必不是好事。我找人工智能大模型帮我写了一个代码把上面的单细胞表达量矩阵拆分成为两个:
import scanpy as sc
import numpy as np
import scipy.io as sio
# 读取单细胞数据
all_data = sc.read_h5ad("./fdff1375-aafc-48ef-b5b1-b1ff8466d92c.h5ad")
# 随机拆分成两个部分
np.random.seed(42) # 设置随机种子以确保可重复性
indices = np.random.permutation(all_data.shape[0])
split_index = indices[int(0.5 * len(indices))] # 找到中间索引进行拆分
# 创建两个子集
subset1 = all_data[indices[:split_index], :]
subset2 = all_data[indices[split_index:], :]
# 为每个子集导出数据
def export_subset(subset, subset_name):
# 导出细胞信息
subset.obs.to_csv(f"{subset_name}_cellinfo.csv")
# 导出基因信息
subset.var.to_csv(f"{subset_name}_geneinfo.csv")
# 导出表达矩阵
raw_data = subset.raw.X
# 转置矩阵,因为通常细胞是行,基因是列
sparse_matrix = raw_data.T
sio.mmwrite(f"{subset_name}_sparse_matrix.mtx", sparse_matrix)
# 导出第一个子集的数据
export_subset(subset1, 'subset1')
# 导出第二个子集的数据
export_subset(subset2, 'subset2')
关键的Python代码是使用了numpy
库来随机拆分一个矩阵(或数组)成两个子集。下面是代码的逐行解释:
np.random.seed(42)
:这行代码设置了随机数生成器的种子为42。设置随机种子可以确保每次运行代码时生成的随机数序列是相同的,这有助于结果的可重复性。indices = np.random.permutation(all_data.shape[0])
:这行代码生成了一个随机排列的索引数组。all_data.shape[0]
给出了all_data
矩阵的行数,np.random.permutation
函数对这个数字进行随机排列,生成一个从0到all_data
行数减1的随机排列数组。split_index = indices[int(0.5 * len(indices))]
:这行代码计算了拆分索引,即在随机排列的索引数组中间位置的值。int(0.5 * len(indices))
计算了数组长度的一半(向下取整),然后使用这个值从indices
数组中取出一个索引,这个索引将用来将all_data
矩阵拆分成两个大致相等的部分。subset1 = all_data[indices[:split_index], :]
:这行代码创建了第一个子集subset1
。它使用切片indices[:split_index]
来选择all_data
矩阵的前半部分行(根据随机排列的索引),:
表示选择所有的列。因此,subset1
包含了all_data
矩阵中随机选择的前半部分行。subset2 = all_data[indices[split_index:], :]
:这行代码创建了第二个子集subset2
。它使用切片indices[split_index:]
来选择all_data
矩阵从split_index
开始的后半部分行,同样:
表示选择所有的列。因此,subset2
包含了all_data
矩阵中随机选择的后半部分行。
总的来说,这段代码将all_data
矩阵随机拆分成两个子集subset1
和subset2
,每个子集包含all_data
的一半行,且行的选择是基于随机排列的索引。这种方法常用于机器学习中的数据集拆分,例如将数据集拆分成训练集和测试集。
之前没有拆分的时候得到的单细胞表达量矩阵会肉眼看起来就很多:
ls -lh inputs/
total 55G
-rw-rw-r-- 1 t180559 t180559 537M 11月 9 23:44 cellinfo.csv
-rw-rw-r-- 1 t180559 t180559 2.1M 11月 9 23:44 geneinfo.csv
-rw-rw-r-- 1 t180559 t180559 54G 11月 10 00:52 sparse_matrix.mtx
现在拆分成为了两个:
$ ls -lh subset*
-rw-rw-r-- 1 t180559 t180559 332M 11月 10 18:00 subset1_cellinfo.csv
-rw-rw-r-- 1 t180559 t180559 2.1M 11月 10 18:00 subset1_geneinfo.csv
-rw-rw-r-- 1 t180559 t180559 34G 11月 10 18:30 subset1_sparse_matrix.mtx
-rw-rw-r-- 1 t180559 t180559 205M 11月 10 18:31 subset2_cellinfo.csv
-rw-rw-r-- 1 t180559 t180559 2.1M 11月 10 18:31 subset2_geneinfo.csv
-rw-rw-r-- 1 t180559 t180559 21G 11月 10 18:48 subset2_sparse_matrix.mtx
==> subset1_sparse_matrix.mtx <==
%%MatrixMarket matrix coordinate real general
%
31815 575647 1348277546
==> subset2_sparse_matrix.mtx <==
%%MatrixMarket matrix coordinate real general
%
31815 354043 829894008
可以看到,之前的929690细胞是同一个单细胞转录组数据集,然后被拆分成为了 575647 和 354043个细胞数量的两个项目,不知道为什么不是平均分配。
拆分成为了两个项目就可以各自独立的跑单细胞转录组数据分析的降维聚类分群流程啦,而且也可以很容易给出来生物学名字,如下所示:
但是呢,这个是因为是跑了两次单细胞转录组流程,所以后面的分析比如如果是要把里面的内皮细胞子集提取出来做细分的时候,就可以把两个对象里面的内皮细胞合并,然后继续走单细胞转录组流程。也就是说, 除了第一层次降维聚类分群是需要这个项目里面的全部的单细胞之外,后面的分析都可以细化到具体的细胞亚群里面去。
我们作为使用者,有很多曲线救国的方法。其实就像大家在单细胞转录组数据处理过程也很遇到很多很多内存限制,因为不可能大家的计算机资源是无限的,但并不是每个内存限制都是靠加内存接近的。
内存限制是进行大规模生物信息学分析时的一个主要挑战,尤其是在处理大规模单细胞数据集时。以下是一些应对内存限制的策略和方法:
抽样分析:
对于细胞通讯分析,可以通过随机抽样的方式减少细胞数量,从而减少内存消耗。
对于拟时序等分析,可以只针对具有生物学联系的特定亚群进行分析,而不是对所有细胞进行分析。
对于可以独立处理的分析(如单细胞打分),可以将数据集拆分成多个子集,分别处理后再合并结果。
只加载数据的一个子集进行分析,而不是一次性加载整个数据集。
正是因为有所限制才让大家理解数据分析的本质
上面是从生物学角度去理解单细胞转录组数据分析的本质,如果是从计算机角度看,需要做的应该是:
内存管理:
优化代码以减少内存使用,例如使用更高效的数据结构和算法。
利用专门的数据库和计算平台来处理大规模数据。
采用分布式计算框架,如Apache Spark,来处理大规模数据集。
利用云计算服务提供商提供的高性能计算资源。
使用数据压缩技术减少数据占用的存储空间。
采用增量加载的方式逐步加载数据,而不是一次性加载全部数据。
利用多核处理器的并行处理能力来加速计算。
使用更高效的数据存储格式,如稀疏矩阵格式,以减少内存占用。
在分析过程中定期清理不再需要的变量和对象,释放内存。
针对特定分析使用专业的软件和工具,这些工具通常经过优化,能够更有效地处理大数据。