.bed 在生信里其实有 两种完全不同的格式,最容易混淆:
-
UCSC BED(基因组坐标文件,纯文本,tab 分割,扩展名
.bed)
只是“区域列表”,和 GWAS 无关,我们脚本里用的就是这种。 -
PLINK 二进制 BED( trio:
.bed..bim..fam)
真正的基因型矩阵,不可读文本,需要 PLINK 软件操作。
下面给你 30 秒把两种格式彻底分清,并演示 “UCSC BED” 怎么生成/查看/转换。
一、UCSC BED(文本,脚本里用的)
格式(最少 3 列,可扩展)
chrom chromStart chromEnd [name] [score] [strand] …
示例
1 123456 123457 rs123456 0 +
列说明
- chrom:chr1、chr2 … 或 1、2 … 都可以
- chromStart:0-based 左端
- chromEnd:1-based 右端(所以 SNP 位置 start = bp-1,end = bp)
- 后面任意加列,不影响 bedtools
二、PLINK 二进制 BED(非文本)
- 由
plink --make-bed生成, trio 文件:
.bed(二进制基因型)
.bim(SNP 信息,文本)
.fam(样本信息,文本) - 不能直接
less或cat看,必须plink --bfile ...操作。
三、我们脚本里的 .bed 是哪一种?
脚本第 2 步生成的 gwas_coord.bed 就是 UCSC BED,纯文本,随便看:
head __add_rsID_tmp/gwas_coord.bed
1 123456 123457
1 234567 234568
...
目的:让 bedtools 把“坐标”与 dbSNP 索引做 overlap,从而拿到 rsID。
四、常用操作速查
- 文本查看
less -S file.bed
- 把 GWAS 的 CHR/BP 直接变 3 列 BED(0-based)
awk 'BEGIN{OFS=FS="\t"} {print $1, $2-1, $2, $3, $4}' your.tsv > tmp.bed
# 列:CHR start end A1 A2
- 用 bedtools 取交集(脚本核心命令)
bedtools intersect -a tmp.bed -b dbsnp151.grch37.rsid.bed -wa -wb > overlap.txt
- 把 BED 变回 VCF/TSV
awk '{print $1"\t"($2+1)"\t"$4"\t"$5}' overlap.txt
- 如果你手上有 PLINK 二进制 trio(
.bed..bim..fam)
想“看”SNP 列表,别去碰.bed,直接读.bim:
cut -f2 file.bim | head # 第二列就是 rsID
五、一句话总结
- 文本能
less的.bed→ UCSC BED,坐标而已,随便改。 - 不能
less的.bed→ PLINK 二进制,必须plink --bfile操作。
我们脚本里只用 UCSC BED,生成、查看、改都就是普通文本文件,没有任何特殊用法。
例如
#!/usr/bin/env bash
set -euo pipefail
#-------------- 参数 --------------
GWAS_IN=${1:? "用法: $0 <your_GWAS.tsv>"}
GWAS_OUT="${GWAS_IN%.*}.rsID.tsv"
#-------------- 路径 --------------
DIR=$(pwd)
TMP="$DIR/__add_rsID_tmp"
mkdir -p "$TMP"
DBSNP_URL="https://zenodo.org/record/3775883/files/dbsnp151.grch37.rsid.bed.gz"
DBSNP="$TMP/dbsnp151.grch37.rsid.bed"
#-------------- 依赖检查 --------------
command -v bedtools >/dev/null 2>&1 || {
echo ">>> 安装 bedtools ..."
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
sudo apt-get update && sudo apt-get install -y bedtools
elif [[ "$OSTYPE" == "darwin"* ]]; then
brew install bedtools
else
echo "请手动安装 bedtools 后再运行"; exit 1
fi
}
command -v python3 >/dev/null 2>&1 || { echo "请先安装 python3"; exit 1; }
#-------------- 1. 下载 dbSNP 索引 --------------
if [[ ! -f "$DBSNP" ]]; then
echo ">>> 下载 dbSNP151 GRCh37 索引 ..."
wget -qO- "$DBSNP_URL" | gunzip -c > "$DBSNP"
fi
#-------------- 2. 提取 CHR/BP/A1/A2 并去重 --------------
echo ">>> 提取坐标 ..."
python3 - <<PY
import pandas as pd, sys, os
f = "$GWAS_IN"
df = pd.read_csv(f, sep='\t')
need = ['CHR','BP','A1','A2']
miss = [x for x in need if x not in df.columns]
if miss: sys.exit(f"缺失列: {miss}")
(df[need].drop_duplicates()
.assign(START=lambda x: x.BP, END=lambda x: x.BP)
.to_csv("$TMP/gwas_coord.bed", sep='\t', index=False, header=False))
PY
#-------------- 3. 链对齐(防止翻转) --------------
echo ">>> 链对齐 ..."
# 用 bedtools 把 REF/ALT 与 dbSNP 完全一致的对上
bedtools intersect -a "$TMP/gwas_coord.bed" \
-b "$DBSNP" -wa -wb \
| awk '($3==$8 && $4==$9){print $1"\t"$2"\t"$3"\t"$4"\t"$7}' \
> "$TMP/chr2rsID.txt"
#-------------- 4. 合并回 GWAS 表 --------------
echo ">>> 写入 rsID ..."
python3 - <<PY
import pandas as pd
gwas = pd.read_csv("$GWAS_IN", sep='\t')
conv = pd.read_csv("$TMP/chr2rsID.txt", sep='\t',
names=['CHR','BP','A1','A2','rsID'])
out = (gwas.merge(conv, on=['CHR','BP','A1','A2'], how='left')
.rename(columns={'rsID':'SNP'}))
# 把没匹配到的 SNP 列留空,方便用户自行过滤
out.to_csv("$GWAS_OUT", sep='\t', index=False)
print(f"完成!输出 => {GWAS_OUT}")
print(f"匹配率: {out['SNP'].notna().mean():.1%}")
PY
#-------------- 5. 清临时 --------------
rm -rf "$TMP"
echo ">>> All done!"
5250

被折叠的 条评论
为什么被折叠?



