单细胞加权基因共表达网络分析scWGCNA-01

scWGCNA是一个生物信息学工作流程,是R包WGCNA的附加组件,用于在单细胞或单核RNA-seq数据集中进行加权基因共表达网络分析。WGCNA最初是为分析大量基因表达数据集而构建的,由于scRNA-seq数据的固有稀疏性,vanilla WGCNA在单细胞数据上的性能受到限制。为了解决这个问题,scWGCNA在运行WGCNA分析之前将转录表达谱相似的细胞聚集成pseudo-bulk元细胞。

scWGCNA代码比较简短,对WGCNA的原有算法的一种改进,以适应单细胞稀疏矩阵(很多表达为0);

该包只包含两个函数metacells_by_groupsconstruct_metacells,construct_metacells()函数将 Seurat 对象,基于knn算法,将相邻细胞构建平均的“元细胞”(averaged metacells)。

github网站:https://github.com/smorabit/scWGCNA
在线教程:https://smorabit.github.io/tutorials/9_scWGCNA_tutorial

作者在2021年nature genetics发表的文章Single-nucleus chromatin accessibility and transcriptomic characterization of Alzheimer's disease中应用了此算法。

1. 先决条件

运行 scWGCNA,需要有一个 Seurat 格式的单细胞转录组数据集,且已经进行聚类和降维。

2. scWGCNA安装

# 要运行scWGCNA,需要安装其他一些R包
install.packages('WGCNA') 
install.packages('igraph') 
install.packages('devtools') 
# install Seurat, check their website for the most up-to-date instructions 
install.packages('Seurat')

devtools::install_github('smorabit/scWGCNA')

library(scWGCNA)

3. scWGCNA实例教程

官方教程介绍从操作Seurat 对象到构建metacells逐步展开分析。 如果你已经有一个已聚类的数据集,你可以跳过接下来的前两部分,从“构建metacells”部分开始分析。

3.1 聚类和降维

首先,从已发表的人类大脑健康和疾病的 snRNA-seq 研究中收集了几个数据集,总计超过 50 万个单核转录组细胞。这些数据集都满足scWGCNA 的先决条件,并且可以通过许多不同的方式进行聚类/降维。由于处理来自多个来源的数据,因此使用online iNMF来构建不受批次或数据来源影响的降维,以反映潜在的细胞异质性。

代码中总共包含9个文献数据集,数据非常不容易下载,先放弃实操。

step1: 将9个seurat对象merge在一起,生成seurat_obj对象,进行Normalize归一化和scale处理;
step2: 提取9个数据集的基因表达矩阵,生成expression_list,构建Liger对象,Liger是一种去批次,多数据集整合方法,跟SCTransform-CCA,Harmony方法类似。对Liger对象进行如下操作:1)normalize,scale等预处理;2)执行online iNMF;3) quantile归一化。将liger_obj保存为9_datasets_online_liger.rds。
step3:将iNMF坐标转移至seurat_obj,我们之前的seurat_obj降维方式通常只有pca,umap,tsne,现在将增加iNMF降维,另外,用liger_obj的umap坐标替换掉seurat_obj的坐标。将修改后的seurat_obj保存为9_datasets_online_seurat.rds。

library(Seurat) 
library(Matrix) 
library(tidyverse) 
library(rliger) 
data_dir <- "data/" 

# load individual seurat objects: 
seurat_trem2 <- readRDS(file=paste0(data_dir, 'seurat_objects/trem2_nmed_unprocessed.rds')) 
seurat_allen <- readRDS(file=paste0(data_dir, 'seurat_objects/allen_brain_map_2020_unprocessed.rds')) 
seurat_jakel <- readRDS(file=paste0(data_dir, 'seurat_objects/jakel_2019_unprocessed.rds')) 
seurat_grubman <- readRDS(file=paste0(data_dir, 'seurat_objects/grubman_2019_unprocessed.rds')) 
seurat_schirmer <- readRDS(file=paste0(data_dir, 'seurat_objects/schirmer_2019_unprocessed.rds')) 
seurat_velmeshev <- readRDS(file=paste0(data_dir, 'seurat_objects/velmeshev_2019_unprocessed.rds')) 
seurat_swarup_splitseq <- readRDS(file=paste0(data_dir, 'seurat_objects/swarup_AD_splitseq_2019_unprocessed.rds')) 
seurat_swarup <- readRDS(file=paste0(data_dir, 'seurat_objects/swarup_AD_2019_unprocessed.rds')) 
seurat_tsai <- readRDS(file=paste0(data_dir, 'seurat_objects/tsai_AD_2019_unprocessed.rds')) 
# make one big merged Seurat object: 
seurat_obj <- merge(c(seurat_trem2, seurat_allen, seurat_jakel, seurat_grubman, seurat_schirmer, seurat_velmeshev, seurat_swarup_splitseq, seurat_swarup, seurat_tsai)) 
# Normalize and scale data: 
seurat_obj <- NormalizeData(seurat_obj) 
seurat_obj <- ScaleData(seurat_obj) 
# make a list of expression matrices: 
expression_list <- list( 
  'trem2' = GetAssayData(seurat_trem2, slot='counts'), 
  'allen'= GetAssayData(seurat_allen, slot='counts'), 
  'jakel'= GetAssayData(seurat_jakel, slot='counts'), 
  'grubman'= GetAssayData(seurat_grubman, slot='counts'), 
  'schirmer'= GetAssayData(seurat_schirmer, slot='counts'), 
  'velmeshev'= GetAssayData(seurat_velmeshev, slot='counts'), 
  'swarup_splitseq'= GetAssayData(seurat_swarup_splitseq, slot='counts'), 
  'swarup_AD'= GetAssayData(seurat_swarup, slot='counts'), 
  'mathys'= GetAssayData(seurat_tsai, slot='counts') 
) 
# append dataset names to barcodes: 
for(dataset in names(expression_list)){ 
  colnames(expression_list[[dataset]]) <- paste0(dataset, '_', colnames(expression_list[[dataset]])) 
} 
# combined metadata: 
meta_columns <- c('orig.ident', 'nCount_RNA', 'nFeature_RNA', 'Age', 'Sex', 'Condition', 'Condition_specific', 'Batch', 'SampleID', 'DonorID', 'Original_cluster', 'Dataset', 'Tissue', 'Technology' ) 
seurat_meta <- rbind( 
  seurat_trem2@meta.data[,meta_columns], 
  seurat_allen@meta.data[,meta_columns], 
  seurat_jakel@meta.data[,meta_columns], 
  seurat_grubman@meta.data[,meta_columns], 
  seurat_schirmer@meta.data[,meta_columns], 
  seurat_velmeshev@meta.data[,meta_columns], 
  seurat_swarup_splitseq@meta.data[,meta_columns], 
  seurat_swarup@meta.data[,meta_columns], 
  seurat_tsai@meta.data[,meta_columns] 
) 
rownames(seurat_meta) <- paste0(seurat_meta$Dataset, '_', rownames(seurat_meta)) 
# remove individual seurat objects to save space: 
rm(seurat_trem2, seurat_allen, seurat_jakel, seurat_grubman, seurat_schirmer, seurat_velmeshev, seurat_swarup, seurat_swarup_splitseq, seurat_tsai); gc(); 
# create liger object: 
liger_obj <- createLiger(expression_list) 
# pre-processing 
liger_obj <- normalize(liger_obj) 
liger_obj <- selectGenes(liger_obj,) 
liger_obj <- scaleNotCenter(liger_obj) 
# perform online iNMF 
liger_obj = online_iNMF(liger_obj, k=50, max.epochs=5) 
# quantile normalization 
liger_obj  = quantile_norm(liger_obj) 
liger_obj  = runUMAP(liger_obj) 
saveRDS(liger_obj, file=paste0(data_dir, '9_datasets_online_liger.rds')) 
# transfer iNMF matrix to seurat obj: 
seurat_obj@reductions$iNMF <- CreateDimReducObject( 
    loadings=t(liger_obj@W), 
    embeddings=liger_obj@H.norm[colnames(seurat_obj),], 
    key="iNMF_", 
    assay="RNA" 
) 
# add UMAP: 
seurat_obj@reductions$UMAP <- CreateDimReducObject( 
  embeddings = liger_obj@tsne.coords[colnames(seurat_obj),], 
  key='umap', 
  assay='RNA' 
) 
# add clusters from liger 
seurat_obj@meta.data$liger_clusters <- liger_obj@clusters 
saveRDS(seurat_obj, file=paste0(data_dir, '9_datasets_online_seurat.rds'))

问题1:liger_obj进行runUMAP降维,但是在后面的代码中,出现liger_obj@tsne.coords,有点看不懂,是不是误写?
seurat_obj@reductions$UMAP <- CreateDimReducObject( 
  embeddings = liger_obj@umap.coords[colnames(seurat_obj),],  
  key='umap', 
  assay='RNA' 
)

找同行确认了下,这应该是作者误写。


image.png
问题2:seurat_meta整合9个样本的meta.data信息(orig.ident,nCount_RNA等),但是代码中没有将seurat_meta赋值给liger_obj@meta.data或seurat_obj@meta.data, 也没有保存?
liger_obj@meta.data <- seurat_meta
seurat_obj@meta.data <- seurat_meta

下图是用online iNMF整合方法得到的UMAP聚类图

image
3.2 获取感兴趣的特定细胞类型子集

为了识别给定细胞类型内的共表达模块,作者仅使用数据的一个子集重复上述分析。在这里,我们对感兴趣的少突胶质细胞谱系细胞进行这种分析,包括少突胶质祖细胞和成熟的少突胶质细胞。
step1:从seurat_obj提取细胞类型为ODC,OPC的子集seurat_odc;同上述操作,对seurat_odc数据集进行iNMF整合,降维,将liger_obj保存为ODC_9_datasets_online_liger.rds。
step2:将iNMF坐标转移至seurat_odc,命名为ctiNMF,这将新增ctiNMF降维方式(类似于pca),seurat_odc对象scale处理后,基于ctiNMF进行umap降维,将生成的seurat_odc保存为9_datasets_ODC_seurat.rds。

# subset by neuronal clusters
seurat_odc <- subset(seurat_obj, celltype %in% c('ODC', 'OPC'))

# iNMF for just ODCs:
expr_matrix <- GetAssayData(seurat_odc, slot='counts')

expression_list <- list(
  'zhou' = expr_matrix[,colnames(seurat_odc)[seurat_odc$Dataset == 'zhou']],
  'allen'= expr_matrix[,colnames(seurat_odc)[seurat_odc$Dataset == 'allen']],
  'jakel'= expr_matrix[,colnames(seurat_odc)[seurat_odc$Dataset == 'jakel']],
  'grubman'= expr_matrix[,colnames(seurat_odc)[seurat_odc$Dataset == 'grubman']],
  'schirmer'= expr_matrix[,colnames(seurat_odc)[seurat_odc$Dataset == 'schirmer']],
  'velmeshev'= expr_matrix[,colnames(seurat_odc)[seurat_odc$Dataset == 'velmeshev']],
  'swarup_splitseq'= expr_matrix[,colnames(seurat_odc)[seurat_odc$Dataset == 'swarup_splitseq']],
  'swarup_AD'= expr_matrix[,colnames(seurat_odc)[seurat_odc$Dataset == 'swarup_AD_2020']],
  'mathys'= expr_matrix[,colnames(seurat_odc)[seurat_odc$Dataset == 'mathys']]
)

seurat_meta <- rbind(
  subset(seurat_odc@meta.data, Dataset=='zhou'),
  subset(seurat_odc@meta.data, Dataset=='allen'),
  subset(seurat_odc@meta.data, Dataset=='jakel'),
  subset(seurat_odc@meta.data, Dataset=='grubman'),
  subset(seurat_odc@meta.data, Dataset=='schirmer'),
  subset(seurat_odc@meta.data, Dataset=='velmeshev'),
  subset(seurat_odc@meta.data, Dataset=='swarup_splitseq'),
  subset(seurat_odc@meta.data, Dataset=='swarup_AD_2020'),
  subset(seurat_odc@meta.data, Dataset=='mathys')
)

# create liger object:
liger_obj <- createLiger(expression_list)

liger_obj <- normalize(liger_obj)
pdf("liger_variable_genes.pdf", width=8, height=8)
liger_obj <- selectGenes(
  liger_obj,
  var.thresh =c(0.05, 0.15, 0.025, 0.025, 0.075, 0.075, 0.05, 0.20, 0.025),
  do.plot=T
)
dev.off()
liger_obj@var.genes %>% length
liger_obj <- scaleNotCenter(liger_obj)

# perform online iNMF
liger_obj = online_iNMF(liger_obj, k=20, max.epochs=5)

# quantile normalization
liger_obj  = quantile_norm(liger_obj)
liger_obj  = runUMAP(liger_obj)

saveRDS(liger_obj, file='ODC_9_datasets_online_liger.rds')

# transfer iNMF matrix to seurat obj:
seurat_odc@reductions$ctiNMF <- CreateDimReducObject(
    loadings=t(liger_obj@W),
    embeddings=liger_obj@H.norm[colnames(seurat_odc),],
    key="ctiNMF_",
    assay="RNA"
  )
VariableFeatures(seurat_odc) <- liger_obj@var.genes

# scale expression data:
seurat_odc <- ScaleData(seurat_odc, features=VariableFeatures(seurat_odc))

# UMAP + clustering:
seurat_odc <- RunUMAP(seurat_odc, reduction='ctiNMF', dims=1:20)

# save the results
saveRDS(seurat_odc, file ='9_datasets_ODC_seurat.rds')

# optional: format for use in Scanpy
library(SeuratDisk)
SaveH5Seurat(cur_save, filename = "9_datasets_ODC_seurat.h5Seurat", overwrite=TRUE)
Convert("9_datasets_ODC_seurat.h5Seurat", dest='h5ad', overwrite=TRUE)

下面可以看到少突胶质细胞谱系 细胞的UMAP聚类图,作者根据已知标记基因将其分为 4 个主要cluster。


image
3.3 构建metacells

现在我们准备构建metacells。为了节省内存,我们只使用高可变基因来构建metacells并执行下游 WGCNA分析。 可以使用 HVG基因,也可以用所有基因,下游结果将基因数目的差异而有所不同。因此,你可以重新运行此步骤以尝试不同的参数。

step1:这个数据集有几种不同的疾病处理条件,但是,我们只想聚合相同条件下的细胞,即odc_group和Condition属性一致的细胞群。 因此,我们在 Seurat 对象中创建了一个新的metadata列metacell_group ,它将 ODC 亚群(odc_group)与处理条件(Condition)组合在一起。

step2:接下来,我们将来自同一 ODC 亚群和相同疾病处理组的细胞群构建metacells。construct_metacells()函数有一个关键参数是 k,有多少个细胞合并成一个metacells。 这里我们使用了一个较大数值 100,这仅仅是因为我们这里的数据集非常大。 你可以根据数据集中的细胞数来调整 k 值大小。我只是通过 for 循环单线程运行它,当然,您可以更快速,例如,如果您要将每个调用construct_metacells函数的程序投入到HPC计算节点,实现并行运算。该construct_metacells() 函数输出一个新的Seurat 对象,包含聚合后的基因表达谱。

step3:将每个metacell_group构建好的metacells(Seurat 对象)进行merge,scale缩放,Harmony整合,umap聚类。

# load scWGCNA package
library(scWGCNA)

# construct metacells by ODC group, condition
seurat_odc$metacell_group <- paste0(
  as.character(seurat_odc$odc_group), '_',
  as.character(seurat_odc$Condition)
)

genes.keep <- VariableFeatures(seurat_odc)

# loop through each group and construct metacells
seurat_list <- list()
for(group in unique(seurat_odc$metacell_group)){
  print(group)
  cur_seurat <- subset(seurat_odc, metacell_group == group)
  cur_seurat <- cur_seurat[genes.keep,]
  cur_metacell_seurat <- scWGCNA::construct_metacells(
    cur_seurat, name=group,
    k=100, reduction='umap',
    assay='RNA', slot='data'
  )
  cur_metacell_seurat$Condition <- as.character(unique(cur_seurat$Condition))
  cur_metacell_seurat$odc_group <- as.character(unique(cur_seurat$odc_group))
  seurat_list[[group]] <- cur_metacell_seurat
}

# merge all of the metacells objects
metacell_seurat <- merge(seurat_list[[1]], seurat_list[2:length(seurat_list)])
saveRDS(metacell_seurat, file='data/metacell_seurat.rds')

Optionally, we can run a dimensionality reduction on the metacell Seurat object to check if some cellular heterogeneity has been retained. The differences in the transcriptional profiles of the different disease groups are quite prominent in the UMAP space, so I am harmonizing the data on the basis of condition merely for visualization purposes.

metacell_subset <- ScaleData(metacell_subset, features = rownames(metacell_subset))
metacell_subset <- RunPCA(metacell_subset, features=rownames(metacell_subset))
metacell_subset <- RunHarmony(metacell_subset, group.by='Condition', dims=1:15)
metacell_subset <- RunUMAP(metacell_subset, reduction='harmony', dims=1:15)

pdf(paste0(fig_dir, "metacell_umap_group.pdf"), width=6, height=6)
DimPlot(metacell_subset, group.by='odc_group', reduction='umap', label=TRUE) + umap_theme
dev.off()

由下图可以看出,通过construct_metacells处理后的seurat对象在umap图中仍有很好地分离。

image.png

前面代码最关键的一步是construct_metacells,将cur_seurat转成cur_metacell_seurat ,我们看下源码,这步做了什么处理。

construct_metacells <- function(seurat_obj, name='agg', k=50, reduction='umap', assay='RNA', slot='data'){
  reduced_coordinates <- as.data.frame(seurat_obj@reductions[[reduction]]@cell.embeddings)
  nn_map <- FNN::knn.index(reduced_coordinates, k = (k - 1))
  row.names(nn_map) <- row.names(reduced_coordinates)
  nn_map <- cbind(nn_map, seq_len(nrow(nn_map)))
  good_choices <- seq_len(nrow(nn_map))
  choice <- sample(seq_len(length(good_choices)), size = 1,
      replace = FALSE)
  chosen <- good_choices[choice]
  good_choices <- good_choices[good_choices != good_choices[choice]]
  it <- 0
  k2 <- k * 2
  get_shared <- function(other, this_choice) {
      k2 - length(union(cell_sample[other, ], this_choice))
  }
  while (length(good_choices) > 0 & it < 5000) {
      it <- it + 1
      choice <- sample(seq_len(length(good_choices)), size = 1,
          replace = FALSE)
      new_chosen <- c(chosen, good_choices[choice])
      good_choices <- good_choices[good_choices != good_choices[choice]]
      cell_sample <- nn_map[new_chosen, ]
      others <- seq_len(nrow(cell_sample) - 1)
      this_choice <- cell_sample[nrow(cell_sample), ]
      shared <- sapply(others, get_shared, this_choice = this_choice)

      if (max(shared) < 0.9 * k) {
          chosen <- new_chosen
      }
  }

  cell_sample <- nn_map[chosen, ]
  combs <- combn(nrow(cell_sample), 2)
  shared <- apply(combs, 2, function(x) {
      k2 - length(unique(as.vector(cell_sample[x, ])))
  })

  message(paste0("Overlap QC metrics:\nCells per bin: ",
      k, "\nMaximum shared cells bin-bin: ", max(shared),
      "\nMean shared cells bin-bin: ", mean(shared), "\nMedian shared cells bin-bin: ",
      median(shared)))
  if (mean(shared)/k > 0.1)
      warning("On average, more than 10% of cells are shared between paired bins.")

  exprs_old <- GetAssayData(seurat_obj, assay=assay, slot=slot)

  mask <- sapply(seq_len(nrow(cell_sample)), function(x) seq_len(ncol(exprs_old)) %in%
      cell_sample[x, , drop = FALSE])
  mask <- Matrix::Matrix(mask)
  new_exprs <- (exprs_old %*% mask) / k
  colnames(new_exprs) <- paste0(name, '_', 1:ncol(new_exprs))
  rownames(cell_sample) <- paste0(name, '_', 1:ncol(new_exprs))
  colnames(cell_sample) <- paste0('knn_', 1:ncol(cell_sample))

  # make seurat obj:
  seurat_aggr <- CreateSeuratObject(
    counts = new_exprs
  )
  seurat_aggr
}
代码解析:

1.假设seurat_obj对象有5000个细胞,10000个基因,即5000*10000的基因表达矩阵。提取seurat_obj对象的umap坐标信息(umap1,umap2),生成reduced_coordinates(5000*2矩阵)。

2.利用FNN快速最近邻搜索算法,为每个细胞寻找99个最近邻细胞,nn_map为5000*99矩阵;第1-99列均为最临近细胞的次序号;



添加第100列,为当前细胞自身的次序号;nn_map为5000*100矩阵;99个邻近细胞加上自身细胞,共100个。



3.随机从5000个细胞中抽取一个细胞,如编号为2000的细胞N2000,然后从剩下的4999个细胞中再随机抽取一个编号N100细胞,计算N2000和N100的最临近细胞群数目(200个细胞编号),对着200个细胞编号,取其并集,如满足max(shared) < 0.9 * k条件,也就是说,如果这两细胞越相似,他们的共享邻居应该越多,把N2000和N100细胞合并成chosen组,反之亦然。接着从剩下的细胞群抽取,与上一步的chosen 组比较,看相似程度,迭代5000次,把相似的细胞编号放入chosen组。
4.通过上一步的找邻居策略,chosen组为2000个,5000个总细胞群,有2000个最相似的最近邻居群, cell_sample为2000*100矩阵。

5.2000个细胞两两比较,查看共有邻居数,统计max(shared),median(shared),mean(shared)。
6.提取5000*10000的基因表达矩阵exprs_old ,(exprs_old %*% mask) / k的计算是2000个细胞邻近细胞群的平均表达量,转换成2000*10000,即5000个细胞由2000个细胞所代表,数据集也实现了降维。

construct_metacells算法小结:

1.seurat_obj对象有5000个细胞,10000个基因,是5000*10000的基因表达矩阵;设置参数k=100,通过KNN算法查找100个最临近邻居群(包括自身细胞),如A细胞,找99个邻居,加上自己,共100个。
2.指定一套筛选相似细胞策略,将细胞的邻居群相互比较,保留相似的细胞,5000个细胞保留了最相似的2000个细胞,用这2000个细胞代表整体细胞。
3.之前是5000*10000的基因表达矩阵,新的基因表达矩阵是:2000个细胞的邻居群(100个细胞)的平均基因表达量,如A细胞的gene1表达量为100个邻居群在gene1的表达平均值,因此新矩阵为2000*10000,剔除了噪音数据,也实现了数据降维。这应该也是处理单细胞表达谱的一种数据处理策略。

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

推荐阅读更多精彩内容