scanpy官方教程2022||07-anndata对象:Getting started with anndata

学习资料来源:

本篇教程来自Adam in 2021 and Alex in 2017.的博客内容

在本教程中,我们将介绍中心对象AnnData(“Annotated Data”)的基本属性

AnnData是专门为类似矩阵的数据设计的。这意味着我们有观测,每一个观测都可以表示为-维向量,其中每个维度对应一个变量或特征。这个矩阵的行和列都是特殊的,因为它们都有索引。

例如,在scRNA-seq数据中,每一行对应一个带有barcode的细胞,每一列对应一个带有基因id的基因。此外,对于每个细胞和每个基因,我们可能有额外的元数据,比如(1)每个细胞的供体信息,或(2)每个基因的替代基因符号。最后,我们可能有其他非结构化元数据,比如用于绘图的调色板。不需要深入了解每一个基于python的数据结构,我们认为直到今天还没有其他的替代方案存在:

  • 处理稀疏
  • 处理非结构化数据
  • 处理观察级和特性级元数据
  • 是友好的

首先,加载包

anndata的版本为0.8.0

import numpy as np
import pandas as pd
import anndata as ad
from scipy.sparse import csr_matrix
print(ad.__version__)

0.8.0

初始化AnnData对象

让我们从构建一个基本的AnnData对象开始,该对象带有一些稀疏的counts信息,可能表示基因表达counts。

counts = csr_matrix(np.random.poisson(1, size=(100, 2000)), dtype=np.float32)
adata = ad.AnnData(counts)
adata

AnnData object with n_obs × n_vars = 100 × 2000

我们可以看到,AnnData提供了一个数据汇总统计的表示,我们传递的初始数据可以通过使用数据的稀疏矩阵来访问adata.X

adata.X

<100x2000 sparse matrix of type '<class 'numpy.float32'>'
        with 126555 stored elements in Compressed Sparse Row format>

现在,我们使用.obs_names和.var_names为obs和var轴提供索引。

adata.obs_names = [f"Cell_{i:d}" for i in range(adata.n_obs)]
adata.var_names = [f"Gene_{i:d}" for i in range(adata.n_vars)]
print(adata.obs_names[:10])

Index(['Cell_0', 'Cell_1', 'Cell_2', 'Cell_3', 'Cell_4', 'Cell_5', 'Cell_6',
       'Cell_7', 'Cell_8', 'Cell_9'],
      dtype='object')

提取子集

这些索引值可用于AnnData的子集,AnnData提供了AnnData对象的视图。我们可以想象,这对将AnnData子集用于特定的细胞类型或感兴趣的基因模块非常有用。设置AnnData子集的规则与Pandas DataFrame非常相似。您可以在obs/var_names、布尔masks或cell index integers使用值。

adata[["Cell_1", "Cell_10"], ["Gene_5", "Gene_1900"]]

View of AnnData object with n_obs × n_vars = 2 × 2

添加metadata信息

Observation/Variable level

所以我们有了对象的核心,现在我们想在观察和变量级别添加元数据。这对AnnData来说非常简单,都是数据。adata.obs和adata.var是Pandas DataFrames

ct = np.random.choice(["B", "T", "Monocyte"], size=(adata.n_obs,))
# Categoricals are preferred for efficiency
adata.obs["cell_type"] = pd.Categorical(ct)  
adata.obs

        cell_type
Cell_0          T
Cell_1   Monocyte
Cell_2   Monocyte
Cell_3          T
Cell_4          B
...           ...
Cell_95  Monocyte
Cell_96  Monocyte
Cell_97         T
Cell_98         B
Cell_99         T

[100 rows x 1 columns]

我们现在还可以看到,AnnData表示已经更新

adata

AnnData object with n_obs × n_vars = 100 × 2000
    obs: 'cell_type'

使用metadata提取子集

我们还可以使用这些随机生成的细胞类型对AnnData进行子集化:

bdata = adata[adata.obs.cell_type == "B"]
bdata

View of AnnData object with n_obs × n_vars = 42 × 2000
    obs: 'cell_type'

观察/变量级矩阵

我们也可能在任何级别上都有元数据,它有许多维度,比如数据的UMAP embedding。对于这种类型的元数据,AnnData有.obsm/.varm属性。我们使用 keys来识别插入的不同矩阵。.obsm/.varm的限制是.obsm矩阵的长度必须等于.n_obs个数,.varm矩阵的长度必须等于.n_vars个数。它们各自有不同的维数。

让我们从一个随机生成的矩阵开始,我们可以将其解释为我们想要存储的数据的UMAP嵌入,以及一些随机的基因级metadata:

adata.obsm["X_umap"] = np.random.normal(0, 1, size=(adata.n_obs, 2))
adata.varm["gene_stuff"] = np.random.normal(0, 1, size=(adata.n_vars, 5))
adata.obsm

看到AnnData再次更新:

adata

AnnData object with n_obs × n_vars = 100 × 2000
    obs: 'cell_type'
    obsm: 'X_umap'
    varm: 'gene_stuff'

关于.obsm/.varm的Notes:

  • “类似数组”的metadata可以来自Pandas DataFrame、 scipy sparse matrix或numpy dense array
  • 当使用 Scanpy 时,它们的值(列)不容易绘制,而是从.obs很容易绘制,例如 UMAP 图

非结构化metadata

AnnData有.uns,它允许任何非结构化元数据。这可以是任何东西,比如包含一些在数据分析中有用的一般信息的列表或字典。

adata.uns["random"] = [1, 2, 3]
adata.uns

Layers

最后,原始核心数据可能有不同的形式,可能是normalized的,也可能不是。这些数据可以存储在AnnData的不同层中。例如,让我们对原始数据进行log转换,并将其存储在一个layer中

adata.layers["log_transformed"] = np.log1p(adata.X)
adata

AnnData object with n_obs × n_vars = 100 × 2000
    obs: 'cell_type'
    uns: 'random'
    obsm: 'X_umap'
    varm: 'gene_stuff'
    layers: 'log_transformed'

转换成DataFrames

我们还可以要求AnnData从其中一个层返回一个DataFrame

adata.to_df(layer="log_transformed")

结果图:我们看到.obs_names/.var_names用于创建这个Pandas对象

image-20220913235016039.png

保存结果

AnnData自带基于hdf5的持久文件格式:h5ad。如果包含少量类别的字符串列还不是类别,AnnData将自动转换为类别categoricals。

outdir = '/Pub/Users/project/scanpy/Anndata/'
adata.write(outdir + 'my_results.h5ad', compression="gzip")

总结介绍

AnnData已经成为Python中单细胞分析的标准,这是有充分理由的——它使用起来很简单,并且通过基于键的存储便利了更可复制的分析。它甚至变得更容易转换为流行的基于r的单细胞分析格式。

现在继续往下阅读,以更好地理解“views”, 磁盘上备份和其他细节。

Views and copies

为了好玩,我们来看看另一个元数据用例。想象一下,这些观察结果来自一项多年研究的仪器,该研究的样本来自不同地点的不同受试者。我们通常会以某种格式获取信息,然后将其存储在DataFrame中:

obs_meta = pd.DataFrame({
        'time_yr': np.random.choice([0, 2, 4, 8], adata.n_obs),
        'subject_id': np.random.choice(['subject 1', 'subject 2', 'subject 4', 'subject 8'], adata.n_obs),
        'instrument_type': np.random.choice(['type a', 'type b'], adata.n_obs),
        'site': np.random.choice(['site x', 'site y'], adata.n_obs),
    },
    index=adata.obs.index,    # these are the same IDs of observations as above!
)

结果图:

image-20220914215343196.png

这就是我们将读出数据与元数据连接起来的方式。当然,下面对X的调用的第一个参数也可以只是一个数据帧。

现在我们又有了一个单独的数据容器来跟踪所有内容:

adata = ad.AnnData(adata.X, obs=obs_meta, var=adata.var)

访问两个变量的前5行:

# This is a view! 
adata[:5, ['Gene_1', 'Gene_3']]

# If we want an AnnData that holds the data in memory, let’s call .copy()
adata_subset = adata[:5, ['Gene_1', 'Gene_3']].copy()

对于view,我们可以查看一列的前三个元素

print(adata[:3, 'Gene_1'].X.toarray().tolist())
adata[:3, 'Gene_1'].X = [0, 0, 0]
print(adata[:3, 'Gene_1'].X.toarray().tolist())

如果您试图访问AnnData的view的部分,内容将自动复制,并生成一个数据存储对象。

adata_subset = adata[:3, ['Gene_1', 'Gene_2']]
adata_subset
adata_subset.obs['foo'] = range(3)
adata_subset

现在adata_subset存储实际数据,不再只是对数据的引用。

显然,您可以使用所有的pandas对序列或布尔索引进行切片。

adata[adata.obs.time_yr.isin([2, 4])].obs.head()

结果图如下:

image-20220914220336356.png

大数据部分读取

如果一个.h5ad非常大,你可以使用备份模式将其部分读入内存:

adata = ad.read('my_results.h5ad', backed='r')
adata.isbacked

如果你这样做,你需要记住AnnData对象有一个打开的连接到用于读取的文件:

adata.filename

因为我们是在只读模式下使用它,所以不会损坏任何东西。为了继续本教程,我们仍然需要显式地关闭它:

adata.file.close()

和往常一样,应该使用with语句来避免打开的文件悬空(即将推出的特性)。

操作磁盘上的对象是可能的,但对于稀疏数据来说是实验性的。因此,我们在本教程中不讨论它。

下次见~

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

推荐阅读更多精彩内容