CpG 位点是基因组上的一种特殊序列,它在 DNA 的结构和功能中起着重要作用。其组成和甲基化影响着基因组上各区域的调控,是表观遗传学领域中重要的研究目标。

本文中的脚本主要用于提取基因组中 CpG 位点的位置,以方便一些着重于 CpG 位点的下游分析进行。此外在部分场景下,我们可能希望排除 CpG 位点进行分析(因为其独特的性质例如高突变率等),因此该脚本也可产生 AT&CG(nonCpG) 位点信息文件。

需要准备的文件和条件有:

  • 基因组序列文件
  • Biopython 库(pip install biopython

可能需要准备的前置条件:

  • 已被放置于 $PATH 环境变量中的 bedtools

脚本内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
from Bio import SeqIO
import os
import argparse
import gzip
import subprocess

def get_args():
parser = argparse.ArgumentParser(description="Use to extract AT/CG(nonCpG) & CpG sites.")

input_group = parser.add_argument_group("Input")
input_group.add_argument("-g", "--genome", required=True, help="The path of genome sequence file.")
input_group.add_argument("-c", "--chr", required=True, help="The target chromosome to extract.")

option_group = parser.add_argument_group("Optional parameters")
option_group.add_argument("--onlycpg", action="store_true", help="Only extract CpG sites.")
option_group.add_argument("--merge", action="store_true", help="Use bedtools to merge bed files.")
option_group.add_argument("--gzip", action="store_true", help="Use gzip to compress output.")
option_group.add_argument("--nosoftmask", action="store_true", help="Remove soft-masked where repeats are in lower-case text.")

output_group = parser.add_argument_group("Output")
output_group.add_argument("-n", "--name", help="The name of output file.")
output_group.add_argument("-p", "--path", help="The path of output file.", default='./')

args = parser.parse_args()
if not args.name:
args.name = os.path.splitext(os.path.basename(args.genome))[0]

return args

def process_command(command):
result = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0:
print(f"Error when executing command: {command}\n{result.stderr.decode()}")

def main():
args = get_args()

with gzip.open(args.genome, "rt") if args.genome.endswith(".gz") else open(args.genome, "r") as genome_file:
chr_num = "chr" + args.chr.replace("chr", "")
cpg_bed_name = os.path.join(args.path, f"{args.name}_{chr_num}_CpGsites.bed.tmp")
atcg_bed_name = os.path.join(args.path, f"{args.name}_{chr_num}_ATnonCpGsites.bed.tmp")

with open(cpg_bed_name, "w") as cpg_bed, open(atcg_bed_name, "w") as atcg_bed:
for record in SeqIO.parse(genome_file, "fasta"):
if record.id != args.chr:
continue
if not args.nosoftmask:
seq = str(record.seq).upper()
else:
seq = str(record.seq)

for i, nuc in enumerate(seq):
if nuc == "N":
continue
if nuc in ["A", "T"] and not args.onlycpg:
atcg_bed.write(f"{chr_num}\t{i}\t{i+1}\n")
elif nuc in ["C", "G"]:
if (nuc == "C" and i + 1 < len(seq) and seq[i + 1] == "G") or (nuc == "G" and i > 0 and seq[i - 1] == "C"):
cpg_bed.write(f"{chr_num}\t{i}\t{i+1}\n")
elif not args.onlycpg:
atcg_bed.write(f"{chr_num}\t{i}\t{i+1}\n")

for bed_file in [cpg_bed_name, atcg_bed_name] if not args.onlycpg else [cpg_bed_name]:
outfile = bed_file.replace(".tmp", "")
if args.merge:
command = f"bedtools merge -i {bed_file} > {outfile} && rm {bed_file}"
else:
command = f"mv {bed_file} {outfile}"
process_command(command)
if args.gzip:
process_command(f"gzip -f {outfile}")

if args.onlycpg:
os.remove(atcg_bed_name)

if __name__ == "__main__":
main()

将上述内容保存在一个名为 get_sites.py 的文件内,或者点击此处下载。

运行示例(以 hg38 人类基因组为例,提取其在 21 号染色体上的 CpG 位点位置信息):

1
2
$ python get_sites.py -g hg38.fa -c chr21 --merge --gzip -n GRCh38
# python get_sites.py -g [sequence file] -c [chr] --merge --gzip -n [outname]

最后将会得到名为 GRCh38_chr21_ATnonCpGsites.bed.gzGRCh38_chr21_CpGsites.bed.gz 的文件,以下是各参数说明:

  • -g 指定参考基因组序列文件,.gz 压缩格式可以被自动识别。
  • -c 指定要提取位点信息的染色体,需和序列文件中的序列 id 一致。
  • --merge 在提取完位点信息后,调用 bedtools 对输出文件进行合并,将每行 1bp 的 bed 文件合并为区间。该选项要求 bedtools 可以直接调用,否则会出现错误。bedtools 的安装可以直接通过 conda install -c bioconda bedtools 实现。
  • --gzip 在提取完位点信息后(如果指定了 --merge 则在合并后),对输出文件进行压缩。
  • -n 指定输出文件的前缀。如果命令中未给定,则默认为序列文件的前缀。
  • -p 指定输出文件的路径。如果命令中未给定,则默认为运行脚本时所处的路径。

此外,如果在运行脚本时指定了 --onlycpg,则仅产生 CpG 位点位置文件。如果在运行脚本时指定了 --nosoftmask,则跳过软掩蔽区域

因为该脚本针对特定染色体进行,因此可以批量运行脚本以同时处理多条染色体,示例:

1
2
3
4
$ for i in {1..23};
do
python get_sites.py -g hg38.fa -c chr"$i" --merge --gzip -n GRCh38 &
done

请保证计算机或服务器有足够的 CPU 和内存进行批量运行,此处更建议使用 parallel 来进行并行数量控制。如果在服务器上有作业调度系统,也可以合理利用,例如 slurm 的作业数组等,以下是一个 slurm 作业数组运行的示例:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash

#SBATCH --job-name=getsites
#SBATCH --nodelist=yournode
#SBATCH --ntasks=1
#SBATCH --array=1-23%10
#SBATCH --mail-user=youremail
#SBATCH --mail-type=END

python get_sites.py -g hg38.fa -c chr"$SLURM_ARRAY_TASK_ID" --merge --gzip -n GRCh38

产生结果示例:

1
$ zcat GRCh38_chr21_CpGsites.bed.gz | head -n 5
1
2
3
4
5
chr21   5010008 5010010
chr21 5010053 5010055
chr21 5010215 5010217
chr21 5010331 5010333
chr21 5010335 5010337

后记

虽说这半年已经写了非常多的 script,但是翻来翻去感觉都属于针对性过强的类型,除了我大概率也没有人能用得上。因此最后只能选定本文中这个短小精悍(没啥难度)的脚本作为分享(水文),或许能帮到有需要的人。

此外也还有一个和本文类似的脚本,主要用来提取全基因组上所有四倍简并位点的位置信息。之后如果有需要的话会补充在这篇文章里面。