一、需求
将h5ad文件中的数据转化为可在浏览器中展示的散点图、热力图、盒须图、小提琴图,后端使用python进行开发。
二、难点
- h5ad大文件读取速度慢
- 百万级别散点前端展示性能差,甚至会导致崩溃
三、探索
h5ad读取速度慢
首先是读取h5ad文件使用的包,分别尝试了h5py、scanpy、anndata三个较为常用的包,scanpy和anndata读取一个3G左右大小的h5ad文件需要大概30s,h5py则1s不到的时间即可完成读取,主要的差异在于h5py没有构造稀疏矩阵(用来展示热力图),如果自行使用csc_matrix()方法进行处理的话,所需要的时间则和另外两个包几乎没有区别,因此推测主要的瓶颈在于对稀疏矩阵的处理上。
那么下一步就可以考虑不读取原始h5ad文件的方式,而是将所需要的数据提前预处理好存储成json文件,每次仅读取json文件即可。
存储为json
由此而来新的问题则是如何进行json的存储和读取,第一次尝试是将所需要的数据存储在一个json文件中,但是存储后的json文件大小是原始h5ad文件大小的130%左右(仅保留所需要的相关数据),并且由于json格式的读取速度不如h5ad文件格式快,直接读取全数据的方法不可行。
那么就选择了拆分成多个存储不同内容的json及jsonl文件,散点图需要的坐标和分类数据分别存储为两个json文件,其他图表所需要的稀疏矩阵则单独存储为一个jsonl文件,最后的结果是一个1GB大小h5ad文件的散点图需要的坐标及分类数据仅需要4MB即可,散点图的问题解决。而存储稀疏矩阵(为进一步优化文件大小该稀疏矩阵排除了0值,记录的是有值的索引值及表达量)的jsonl仍是原h5ad文件大小的120%,继续考虑稀疏矩阵jsonl文件的读取优化。
/**
* 稀疏矩阵jsonl参考格式如下
* 存储内容是每个差异基因的表达量索引及值
**/
{"WNT16":{"index":[50564,51310,53022,69374],"value":[72.7,429.18,343.88,279.17]}}
{"ABCC8":{"index":[37710,40979,67813],"value":[273.6,476.64,195.66]}}
不全量读取文件
一个1GB的h5ad文件将其中的稀疏矩阵存储为jsonl后大小约为1.25GB,如果读取的话依然无法满足性能优化的需求,因此考虑是否可以使用seek()方法仅读取部分文件,因为每次用来展示为热力图的只是部分差异基因的数据,不需要全量数据进行展示。
而部分读取,那就需要针对每个差异基因的数据的指针位置进行存储,通过tell()方法找到指针起始位置,再使用len()计算出终止位置,最终单独存储为一个json。
/**
* 指针位置示例
**/
{"TSPAN6": [0, 6697], "DPM1": [6699, 187677], "SCYL3": [187679, 224870], "C1orf112": [224872, 231096], "FGR": [231098, 238901], "CFH": [238903, 245321]}
最终的读取则是先从指针位置文件中找到所需要的差异基因的起始与终止位置,根据起始与终止位置去稀疏矩阵的jsonl文件中部分读取出所需的数据即可,便可以将读取速度控制在毫秒级。
前端可视化框架选择
文件读取速度的问题得以解决,而前端主要的性能瓶颈在于散点图的量级上,目前数据最多有200万个点坐标需要进行渲染展示。
首先在框架的选择上优先选择了echarts,因为echarts的官方示例里有支持百万级数据量的散点图并且echarts的中文文档较为健全,但在实际的开发中发现两个较为严重的问题:
- echarts的百万级散点图仅在单分类场景下能够流畅运行,大量级多分类会崩溃
- echarts开启大数据优化配置后,select事件无法获取框选的点数据(使用引射线法可手动解决,详见上一篇文档)
最终因为第一点不满足需求放弃echarts,转而使用plotly框架,在散点图的使用上plotly提供了两种模式,大数据情况下建议使用webgl模式(mode: scattergl)进行渲染,性能明显优于基础模式,并且一些科研类目的前端可视化项目也大多使用plotly进行开发。
最终证明plotly可满足现有需要,能够支持目前现有数据集最高的200w散点图渲染。
目前遗留问题
经测试百万级散点图使用plotly的拖动组件(pan)时依然会导致崩溃,因此目前暂时禁用百万级数据的拖动功能,目前基于plotly暂无解决方案,使用d3.js可以解决但开发成本较大,并且区域放大(zoom)功能也可以满足用户需求,因此暂不考虑该方案。
后续优化方案
1、可以考虑进一步使用gzip压缩json文件。
更新:gzip尝试后发现压缩用时太久,可行性较低。
2、可以对数据进行抽样操作。