我有一个文件(~50,000行)文本.txt如下所示,其中包含来自五个个体(AB,BB,CA,DD,GG)的一些基因信息。文件中的 \t 是一个制表符分隔符。文件中还有很多没有用的信息,我想清理一下。所以我需要的是用'transcript=' id提取物种名称,如果他们有的话,还要提取'DD:"和'GG:"部分。
$head文本.txt
GeneAtAB:xrbyk | jdnif | otherTexttBB:abdf | jdhfkc | otherDifferentTexttCA:bdmf | nfjvks | transcript=aaabb.1tDD:hudnf.1 type=cdstGG:jdubf.1 type=cds
GeneBtBB:dfsfg | dfsfvdf | otherDifferenttCA:zdcfsdf | xfgdfgs | transcript=sdfs.1tDD:sdfsw.1 type=cdstGG:fghfg.1 type=cds
GeneCtAB:dsfsdf | xdvv | otherText1tBB:xdsd | sdfsdf | otherDifferentText2tDD:hudnf.1 type=cdstGG:jdubf.1 type=cds
GeneDtAB:dfsdf | Asda | transcript=asdasd.2tCA:bdmf | nfjvks | transcript=aaabb.1tDD:hudnf.1 type=cdstGG:jdubf.1 type=cds
我希望输出是
GeneAtCA:transcript=aaabb.1tDD:hudnf.1tGG:jdubf.1
GeneBtCA:transcript=sdfs.1tDD:sdfsw.1tGG:fghfg.1
GeneCtDD:hudnf.1tGG:jdubf.1
GeneDtAB:transcript=asdasd.2tCA:transcript=aaabb.1tDD:hudnf.1tGG:jdubf.1
我已经搜索和思考了整整一个下午,只有用第一列基因名称按物种撕开这个文件的想法,然后分别修改每个文件,最后根据基因名称将文件合并在一起。但如您所见,文件的每一行不必具有相同数量的字段,因此我不能简单地使用awk
来打印某个列。我不知道我怎么能按物种撕毁它们。
我试图模仿这个如何使用sed/grep提取两个单词之间的文本?,但没有成功。我还阅读了一些关于Python如何拆分文本的信息(因为我开始学习这种语言),但仍然无法弄清楚。有人可以帮忙吗?多谢!
更新输入数据的澄清: 在上面显示的例子中,每个个体的基因信息用制表符(\t)分隔,这意味着个人名称加冒号(例如AB:)后面的所有文本都属于个体(例如AB的"xrbyk | jdnif | otherText")。是否将个体保留在最终输出中取决于该个体是否有"transcript="的信息,DD 和 GG 除外。这就是为什么在最终输出中,第一行以CA开头,而不是以AB开头。
这个解决方案有点长,但应该很容易使用:
#!/usr/bin/env python3
# main.py
import csv
import fileinput
import re
def filter_fields(row):
output = []
for field_number, field in enumerate(row, 1):
if field_number == 1:
output.append(field)
elif "DD:" in field or "GG:" in field:
output.append(field.split()[0])
elif "transcript=" in field:
# Remove stuff from after the colon to the last space
output.append(re.sub(r":.* ", ":", field))
return "t".join(output)
reader = csv.reader(fileinput.input(), delimiter="t")
for row in reader:
print(filter_fields(row))
如何运行它:
# Output to the screen
python3 main.py text.txt
# Output to a file
python3 main.py text.txt > out.txt
# Use as a filter
cat text.txt | python3 main.py
笔记
- 在此解决方案中,每行文本被分成一行字段。
- 函数
filter_fields
将占用每一行,决定要保留的字段并重新格式化。然后,它返回这些字段,以制表符分隔。 re.sub(...)
电话说:删除冒号后的所有内容,直到最后一个空格。
该任务很简单,可以通过定义指定感兴趣信息的正则表达式在一行代码中解决。
要测试输入数据,请将while <DATA>
替换为while <>
,并将文件名作为脚本的参数。
use strict;
use warnings;
use feature 'say';
my $re = qr/(Gene.|[A-Z]{2}:(?=[^:]*transcript=)|transcript=S+|GG:S+|DD:S+)/;
say join("t",/$re/g) while <DATA>;
exit 0;
__DATA__
GeneA AB:xrbyk | jdnif | otherText BB:abdf | jdhfkc | otherDifferentText CA:bdmf | nfjvks | transcript=aaabb.1 DD:hudnf.1 type=cds GG:jdubf.1 type=cds
GeneB BB:dfsfg | dfsfvdf | otherDifferent CA:zdcfsdf | xfgdfgs | transcript=sdfs.1 DD:sdfsw.1 type=cds GG:fghfg.1 type=cds
GeneC AB:dsfsdf | xdvv | otherText1 BB:xdsd | sdfsdf | otherDifferentText2 DD:hudnf.1 type=cds GG:jdubf.1 type=cds
GeneD AB:dfsdf | Asda | transcript=asdasd.2 CA:bdmf | nfjvks | transcript=aaabb.1 DD:hudnf.1 type=cds GG:jdubf.1 type=cds
输出
GeneA CA: transcript=aaabb.1 DD:hudnf.1 GG:jdubf.1
GeneB CA: transcript=sdfs.1 DD:sdfsw.1 GG:fghfg.1
GeneC DD:hudnf.1 GG:jdubf.1
GeneD AB: transcript=asdasd.2 CA: transcript=aaabb.1 DD:hudnf.1 GG:jdubf.1
只需更改少量代码,即可实现所需的确切输出
use strict;
use warnings;
use feature 'say';
my $re = qr/(Gene.|[A-Z]{2}:transcript=S+|GG:S+|DD:S+)/;
while( <DATA> ) {
s/[^:]*(?=transcript=)//g;
say join("t",/$re/g);
}
exit 0;
__DATA__
GeneA AB:xrbyk | jdnif | otherText BB:abdf | jdhfkc | otherDifferentText CA:bdmf | nfjvks | transcript=aaabb.1 DD:hudnf.1 type=cds GG:jdubf.1 type=cds
GeneB BB:dfsfg | dfsfvdf | otherDifferent CA:zdcfsdf | xfgdfgs | transcript=sdfs.1 DD:sdfsw.1 type=cds GG:fghfg.1 type=cds
GeneC AB:dsfsdf | xdvv | otherText1 BB:xdsd | sdfsdf | otherDifferentText2 DD:hudnf.1 type=cds GG:jdubf.1 type=cds
GeneD AB:dfsdf | Asda | transcript=asdasd.2 CA:bdmf | nfjvks | transcript=aaabb.1 DD:hudnf.1 type=cds GG:jdubf.1 type=cds
输出
GeneA CA:transcript=aaabb.1 DD:hudnf.1 GG:jdubf.1
GeneB CA:transcript=sdfs.1 DD:sdfsw.1 GG:fghfg.1
GeneC DD:hudnf.1 GG:jdubf.1
GeneD AB:transcript=asdasd.2 CA:transcript=aaabb.1 DD:hudnf.1 GG:jdubf.1
假设示例文本中的那些t
是真正的选项卡,这个 Perl 一行将做到这一点。 如果它们是文字t
文本,那么这需要稍微调整一下。 将要抓取的每个字段放在正则表达式交替中GG:
之后。
perl -lne '@wanted = $_ =~ m{(^Gene[ABCD]|(?:transcript=|DD:|GG:)S+)}g; print join "t", @wanted if @wanted; ' inputfile.txt > outputfile.txt
输出:
GeneA transcript=aaabb.1 DD:hudnf.1 GG:jdubf.1
GeneB transcript=sdfs.1 DD:sdfsw.1 GG:fghfg.1
GeneC DD:hudnf.1 GG:jdubf.1
GeneD transcript=asdasd.2 transcript=aaabb.1 DD:hudnf.1 GG:jdubf.1
关于您的澄清,这一个衬里将使您非常精细地控制捕获的内容。 这更像是一种完整的解析方法。
perl -lne '($gene, @parts) = split/t/,$_; my @wanted; foreach $p (@parts) { $p =~ m{(?|^([A-Z]{2}:).*?(transcript=S+)|(DD:S+)|(GG:S+))} and push @wanted, "$1$2"; }; print join "t", $gene, @wanted if @wanted; ' inputfile.txt > outputfile.txt
输出:
GeneA CA:transcript=aaabb.1 DD:hudnf.1 GG:jdubf.1
GeneB CA:transcript=sdfs.1 DD:sdfsw.1 GG:fghfg.1
GeneC DD:hudnf.1 GG:jdubf.1
GeneD AB:transcript=asdasd.2 CA:transcript=aaabb.1 DD:hudnf.1 GG:jdubf.1
这将获取所有 DD 和 GG 优先组件以及任何带有transcript=
的 ID。
它的工作原理是这样的。
分离- 出基因并分解每个标签分离的部分
- 在每个部分中,使用正则表达式抓住我们关心的部分
- 保存匹配的位,并仅在正则表达式匹配时才将其推送到
@wanted
(?|
正则表达式术语会导致编号的捕获变量在交替中保持不变,而不是继续上升。 即我们只有$1
和$2
而不是$1, $2, $3, $4
.- 如果有任何
@wanted
,请打印出该行
请记住,每次"简化"任务都有丢失实际需要的数据的风险。 如果数据足够复杂,您最终可能会得到一个完整的解析器。
点击这里阅读更多关于如何使用Perl进行流水线的信息。 https://perldoc.perl.org/perlrun