生物信息学多文件数据处理的一些编程思路

· Read in about 4 min · (643 Words)

本文举例介绍生物信息学多文件数据处理的一些编程思路,希望对大家有所帮助。

如果你是想从gff格式提取CDS,请看另一篇《Extract cds fastas from a gff annotation + reference sequence》。

师妹对某物种测序获得很多scaffold,用glean预测了基因结构(CDS、mRNA等),另外还预测了snp位点,她想统计各mRNA中的snp位点。

 

文件信息

1) 基因结构预测结果文件a.gff如下,第一列是序列id,第二列是注释信息来源,第三列是注释信息类型,四五列是开始与结束位置,第七列是序列方向/链(strand),第八列phase,第九列是描述。详见gff格式说明

scaf002 glean   mRNA    11153   14469   0.459   +   .   somestring
scaf002 glean   CDS 11153   11326   .   +   0   somestring

备注: 此文件约10万行,文件大小7M左右。scaffold名称数量大约几百,存在多种格式。一条scaffold的所有记录是按照位置从小到大排序的。

2) snp文件b.snp,有三列,如下所示,第一列是scaffold名称,第二列是snp位点。

nscaf285    181  C T T T - C C C - - - - - T C C C - C Y C C C - C - - - - - - - - C Y T Y C Y T C  SWSNP00000001
nscaf285    201  T - - - - T T - - - - T T T T T T - T T T T T - T - - - - - T T - Y T - T T Y T Y  SWSNP00000002

备注:此文件大约200万行,文件大小约200M。scaffold名称数量大约几十,且都包含在a.gff文件中的scaffold名称中。

 

思路

背景:

a.gff中有多条scaffold,每条scaffold上有多个mRNA;b.snp中有多条scaffold,每条scaffold中有多个snp位点。

目标:

找到各scaffold上每条mRNA上的snp位点,结果按照snp位点数量从多到少对各mRNA排序输出。

大致思路:

嵌套循环snp数据(外循环)和mRNA信息数据(内循环),看每个snp位点是否存在与某个scaffold的某条snp上。

细化:

snp数据文件较大,且每行的记录只需要使用一次(和mRNA位点信息比较),如果读入内存再比较势必浪费大量内存。故只需要从a.gff一次性读入mRNA信息,然后在逐行读取snp文件b.snp过程中,逐一比较snp位点与mRNA位点。

数据存储结构

前面说了要从a.gff一次性读入mRNA信息,数据结构的好坏与运行速度密切相关。

HUGOMORE42

1)mRNA位点信息。由于a.gff有多条scaffold,且b.snp中每行记录提供了scaffold名称,则自然想到用字典<1>(perl中为hash,python为map)存储mRNA为信息,通过scaffold名称查询对应的mRNA位点信息,从而缩小了对mRNA位点信息比较的范围,避免了将所有mRNA信息逐行存入数组中与snp位点信息进行一对一匹配。

此外,由于一条scaffold上有多处mRNA,我们关心的是各mRNA的位置,则可以将各mRNA的位置信息存入一个字典<2>,并将所有mRNA的信息存入一个列表(perl中为数组array,python中为列表list),匹配的时候遍历列表一一匹配即可。由于必须要遍历一条scaffold上所有mRNA,则不必考虑用字典存储所有mRNA来提高速度。

2) 存储各mRNA的snp位点信息。可以单独存储到一个数据结构中,但仍需要通过scaffold名称+mRNA信息(起始位点)来作为唯一标示,这些在前面讲的mRNA位点信息中已经存在,则只需在上面一段中的字典<2>中增加一对键值:”snp”=>位点列表的引用。在逐一比较snp位点与mRNA位点的过程中,发现一条snp存在某条mRNA上,则将该snp位点增加到该mRNA的snp位点列表中去。

最终存储所有mRNA的变量的数据结构如下(perl的Data::Dumper打印,非常直观),

$VAR1 = {                                           # $mrnas={...}表示其为指向字典的引用
          'nscaf3092' => [                          # 'nscaf3092' => [...]表示其为指向列表的引用
                           {                        # 一个mRNA的所有信息存在字典中
                             'strand' => '-',
                             'end' => '4128',
                             'start' => '1536',
                             'snp' => [1, 2, 3],    # 又是[]
                             'snp_amount' => '3'
                           }
                         ],
          'scaffold838' => [
                             {
                               'strand' => '-',
                               'end' => '17649',
                               'start' => '16538',
                               'snp' => [1, 2, 3, 4],
                               'snp_amount' => '4'
                             },
                             {
                               'strand' => '-',
                               'end' => '15974',
                               'start' => '6356',
                               'snp' => [1, 2, 3, 4, 5],
                               'snp_amount' => '5'
                             }
                           ]
          }

 

其它:

结果按照snp位点数量从多到少排序输出,可以使用施瓦茨变换排序。

b.snp行数较多,可以实时显示进度,这在同系列的另一篇文章中有例子。该文章中的文件更大,记录数更多。

如果为了减少内存使用,当记录非常非常多的时候,记录单个mRNA信息的字典的key可以更精简一些。

 

代码实现(Perl)

HUGOMORE42

#!/usr/bin/perl
use strict;

# 因为需要在终端显示一些进度信息,所以结果写入结果文件中
die "Usage:$0 <*.gff> <*.snp> <result file>\n"
  unless @ARGV == 3;
my $gff_file    = shift @ARGV;
my $snp_file    = shift @ARGV;
my $result_file = shift @ARGV;

print "read gff file ..."; # 当运算量大的时候,进度信息很有用
# 读取mRNA信息
my $mrnas = read_data_from_gff_file( $gff_file, "mRNA" );
print "Done.\n";

print "parse snp file\n";
my ( $seq, $loc ); # 只记录序列名称和snp位点
my $mrna;          # 一个临时变量,存放单个mRNA的信息
open IN, "<", $snp_file
  or die "failed to open file: $snp_file\n";

my $count = 0;     # 用于进度显示
my $| = 1;      # 取消输出缓存设置,用于快速实时显示进度
while (<IN>) {
    $count++;
    # 显示进度,\r是回车,即删除整行的文字,并显示新的文字,实现动态显示进度
    print "\r$count";

    next unless /(.+?)\t(\d+)\t/; # 格式约束
    ( $seq, $loc ) = ( $1, $2 );  # 赋值给变量

    next unless exists $$mrnas{$seq}; # 筛选出当前snp记录的序列对应的所有mRNA信息
    # $$mrnas{$seq} 等价于 $mrnas->{$seq},后面的类似

    for $mrna ( @{ $$mrnas{$seq} } ) { # 遍历该序列所有的mRNA位点
        # 检查snp位点是否在mRNA中,如果不在则检查下一个mRNA
        next unless $loc >= $$mrna{start} and $loc <= $$mrna{end};

        $$mrna{snp} = [] unless ref $$mrna{snp} eq 'ARRAY'; # 初始化存储一个mRNA所有snp位点的指向列表的引用
        push @{ $$mrna{snp} }, $loc; # 记录当前的snp位点
    }
}
close IN;

# store and sort
# 先把有snp位点的mRNA信息存入一个列表
my @mrnas_with_snp;
for my $seq ( sort keys %$mrnas ) {
    for $mrna ( @{ $$mrnas{$seq} } ) {
        next unless ref $$mrna{snp} eq 'ARRAY';       # 只存入有snp的mRNA
        $$mrna{snp_amount} = scalar @{ $$mrna{snp} }; # 新加入一个snp数量的信息
        $$mrna{seq} = $seq;                           # 把序列名称也加进来
        push @mrnas_with_snp, $mrna;
    }
}

# 用施瓦茨变换来按照snp位点个数从大到小进行排序。
print "\nsort result ...";
@mrnas_with_snp = map { $_->[0] }
  sort { $b->[1] <=> $a->[1] }
  map { [ $_, $$_{snp_amount} ] } @mrnas_with_snp;
print "Down.\n";

# 把结果写入文件
$| = 0;
print "write result to $result_file ...";
open OUT, ">", $result_file
  or die "failed to open file: $result_file\n";
for $mrna (@mrnas_with_snp) {
    print OUT "$$mrna{seq}\t$$mrna{strand}\t$$mrna{start}\t$$mrna{end}\t";
    print OUT $$mrna{snp_amount}, "\t";
    print OUT join ",", @{ $$mrna{snp} };
    print OUT "\n";
}
close OUT;
print "Done.\n";

# 读取gff文件的函数
sub read_data_from_gff_file ($$) { # $$限制函数参数为两个标量
    my ( $file, $TYPE ) = @_;
    my $data = {};  # 储存信息的变量,初始化为指向hash的引用
    my ( @tmp, $seq, $type, $start, $end, $strand ); # 需要存储的信息的变量
    open IN, "<", $file
      or die "failed to open file: $file\n";
    while (<IN>) {
        @tmp = split /\t/, $_;
        next unless @tmp == 9;
        ( $seq, $type, $start, $end, $strand ) =
          ( $tmp[0], $tmp[2], $tmp[3], $tmp[4], $tmp[6] );
        next unless $type eq $TYPE;    # only mRNA

        $$data{$seq} = [] unless ref $$data{$seq} eq 'ARRAY'; # 初始化变量
        push @{ $$data{$seq} },
          { start => $start, end => $end, strand => $strand };
    }
    close IN;
    return $data;
}

 

总结 

简单的数据处理程序,主要是程序结构和数据结构的选择。当数据量很大的时候,程序结构尤为重要;数据结构主要就是列表和字典的灵活应用。

 

类似的文章

 

-EOF-