重新梳理一遍NIPT的分析方法。
QC
直接使用fastp对原始数据进行质控,原始数据是SE50。
fastp -i sample.fastq.gz -o sample.clean.fq.gz -w 16 -j sample.json -h sample.html
比对
这一步有用bwa mem、bwa aln和bowtie2的,差异不是很大。但是,使用的参考基因组会有影响,例如使用hs37d5比ucsc.hg19稍好,因为hs37d5会预先mask掉PAR区域。比对完后要去重。
bwa mem -t 32 hs37d5.fa sample.clean.fq.gz | samtools view -bSh - | samtools sort -@ 32 - -o sample.sort.bam
gatk MarkDuplicates -I sample.sort.bam -O sample.rmdup.bam -M sample.dups.txt --REMOVE_DUPLICATES true
Bed
需要对参考基因组进行划窗,用于GC校正。
PAR区
Version | Region | Note |
---|---|---|
GRCh38 | chrY:10001-2781479/chrX:10001-2781479 | PAR1 |
GRCh38 | chrY:56887903-57217415/chrX:155701383-156030895 | PAR2 |
GRCh37 | chrY:10001-2649520/chrX:60001-2699520 | PAR1 |
GRCh37 | chrY:59034050-59363566/chrX:154931044-155260560 | PAR2 |
使用bedtools,根据广东省的团标,划窗可以是20kb、60kb、100kb。这里使用50kb。
可以使用fai索引文件来划分
bedtools makewindows -g hs37d5.fa.fai -w 50000 > hs37d5.50kb.bed
也可以使用预先建立好的bed进行划分
bedtools makewindows -b hs37d5.bed -w 50000 > hs37d5.50kb.bed
计算各个窗口的GC比例
bedtools nuc -fi hs37d5.fa -bed hs37d5.50kb.bed > hs37d5.50kb.gc.bed
去掉与PAR区有重合区域的窗口(可选)
bedtools intersect -a hs37d5.50kb.gc.bed -b PAR.bed -v > hs37d5.50kb.gc.noPAR.bed
去掉GC比例过低和过高的区域(可选),注意自己的bed中GC在第几列。
cat hs37d5.50kb.gc.noPAR.bed | awk '{if(($6>=0.2 && $6<=0.8)) print $0}' > hs37d5.50kb.gc.noPAR.filter.bed
去掉黑名单区域(可选)
bedtools intersect -a hs37d5.50kb.gc.noPAR.filter.bed -b wgEncodeDukeMapabilityRegionsExcludable.bed > hs37d5.50kb.gc.noPAR.filter.rmBlack.bed
统计
统计各个窗口的read counts,这会将counts补充到最后一列
bedtools intersect -a hs37d5.50kb.gc.noPAR.filter.rmBlack.bed -b sample.rmdup.bam -c -wa > sample.counts.txt
GC校正
使用loess回归校正read counts,代码是R
args <- commandArgs(T)
rawCounts <- args[1]
corCounts <- args[2]
# V13是counts,V5是GC,按实际修改
RC_DT <- read.table(rawCounts, sep="\t", head=FALSE)
gcCount.loess <- loess(
V13~V5,
data=RC_DT,
control=loess.control(surface="direct"),
degree=2
)
predictions <- predict(gcCount.loess, RC_DT$V5)
RC_DT$RC <- predictions
RC_DT$RCor <- ifelse(RC_DT$RC<0, 0, round(RC_DT$RC, 0))
write.table(RC_DT, corCounts, sep="\t", quote=FALSE, row.names=FALSE, col.names=FALSE)
胎儿浓度
男胎浓度
接下来需要计算胎儿浓度。易知,男胎浓度使用chrY浓度来代替即可,但由于女胎也有少量reads会比对到chrY上,因此需要一定的公式校正。这里参考defrag算法。
代码为python
import pandas as pd
# 把校正后UR代入,再使用UR均值校正一次UR
# URAdj = UR * (URmean / URGC)
def GetURDateFrame(corFile):
df = pd.read_csv(corFile, header=None, sep="\t")
# df.rename(columns={0: "chrom", 1: "start", 2: "end", 3: "name", 5: "GC", 13: "UR", 15: "URGC"}, inplace=True)
df.rename(columns={0: "chrom", 1: "start", 2: "end", 4: "GC", 12: "UR", 14: "URGC"}, inplace=True)
# df2 = df[["chrom", "start", "end", "name", "GC", "UR", "URGC"]]
df2 = df[["chrom", "start", "end", "GC", "UR", "URGC"]]
# UR均值
URmean = df2["UR"].mean()
# UR修正值
df2.insert(loc=6, column="URAdj", value=0)
nonZeroList = df2.loc[df2["URGC"] != 0].index
for i in nonZeroList:
URGC = df2.loc[i, "URGC"]
UR = df2.loc[i, "UR"]
URAdj = round(UR * (URmean / URGC))
# 还有一种校正方式
# URAdj = round(UR - (URGC - URmean))
df2.loc[i, "URAdj"] = URAdj
return df2
defrag算法计算男胎胎儿浓度,即用(待测样本的Y丰度 - 若干女胎样本的平均Y丰度)除以(若干男性成人平均Y丰度 - 若干女胎样本的平均Y丰度)
\[FF = \frac{\%Y_{XY}-\%Y_{XX}}{\%Y_{man}-\%Y_{XX}}\]在不改变实验环境与条件的前提下,$\%Y_{man}$和$\%Y_{XX}$是两个常数。
# 获得Y丰度
def percY(dataFrame):
sumY = dataFrame[dataFrame["chrom"] == "chrY"]["URAdj"].sum()
sumAll = dataFrame["URAdj"].sum()
Y_per = float(sumY) / sumAll
return Y_per
# 获得一批次的平均Y丰度
def meanPerY(dir):
Y_per_List = []
for i in os.listdir(dir):
df = GetURDateFrame(dir + "/" + i)
y_p = percY(df)
Y_per_List.append(y_p)
meanYPer = sum(Y_per_List) / len(Y_per_List)
return meanYPer
女胎浓度
和男胎不同,女胎浓度不能通过chrX的丰度获得。seqFF算法根据常染色体read counts(去掉13,18,21三个染色体)和男胎浓度来训练获得模型,再把模型用于预测胎儿浓度,模型同时适用于女胎。模型是同时使用弹性网络模型获得预测值Ye,以及分级加权模型获得预测值Yw,最终预测胎儿浓度为Ye和Yw的平均值。seqFF的脚本可以在这里下载到,已经预先训练了模型。想尝试一下怎么把模型训练出来和以及怎么获得那两个校正常数的,但是脚本中没有相关信息。
将counts处理为如下格式的矩阵
checkPoint | sample1 | sample2 | sample3 |
---|---|---|---|
chr1-50000 | 74 | 97 | 188 |
chr1-100000 | 57 | 83 | 207 |
chr1-150000 | 63 | 63 | 141 |
弹性网络的sklearn
import pandas as pd
from sklearn.linear_model import ElasticNetCV
import joblib
# 训练
df = pd.read_csv("train_male.txt", sep="\t", header=0, index_col=0)
dft = df.T
cols = [i for i in dft.columns if i != "ff"]
X = dft[cols]
y = dft["ff"]
regr = ElasticNetCV(cv=10, random_state=0, max_iter=1000)
regr.fit(X, y)
## 输出模型,可以复用
joblib.dump(regr, "output.model")
# 测试
## 载入模型
regr = joblib.load("output.model")
df_test = pd.read_csv("test_female.txt", sep="\t", header=0, index_col=0)
dft_test = df_test.T
cols_test = [i for i in dft_test.columns if i != "ff"]
X_test = dft_test[cols_test]
y_test_act = list(dft_test["ff"])
predict_test = list(regr.predict(X_test))
zipped = list(zip(y_test_act, predict_test))
for z in zipped:
print(z[0], z[1])
WRSC没看懂怎么做,摸索中。
Z值计算
Z值计算算法比较简单,参考过往此篇。