Perl脚本用于对多个DNA序列进行分组

时间:2017-12-20 21:11:38

标签: perl

我有一个~500个DNA序列的FASTA文件,每个序列都有我所知的单核苷酸多态性(SNP)的目标位置。

对于文件中的每个条目,我都有一个单独的制表符分隔的文本文件,每行都有

  1. FASTA序列名称
  2. 开始位置
  3. 结束位置
  4. SNP位置
  5. 文本文件中的序列和位置顺序相同。

    虚拟FASTA文件是:

    >AOS-94_S25_L002_R1_001_trimmed_contig_767
    GACACACACTGATTGTTAGTGGTGTACAGACATTGCTTCAAACTGCA
    >AOS-94_S25_L002_R1_001_trimmed_contig_2199
    TAGGTTTTCTTTCCCATGTCCCCTGAATAACATGGGATTCCCTGTGACTGTGGGGACCCCTGAGAGCCTGGT
    >AOS-94_S25_L002_R1_001_trimmed_contig_2585
    GATAAGGAGCTCACAGCAACCCACATGAGTTGTCC
    

    并且虚拟位置文件是

    AOS-94_S25_L002_R1_001_trimmed_contig_767   5   15  10
    AOS-94_S25_L002_R1_001_trimmed_contig_2199  8   19  11
    AOS-94_S25_L002_R1_001_trimmed_contig_2585  4   20  18
    

    这是我编写并试过的脚本

    use warnings;
    use strict; 
    
    # Read in the complete FASTA file:
    print "What is the name of the fasta contig file?\n";
    my $fasta = <STDIN>;
    chomp $fasta;
    
    # Read in file of contig name, start pos, stop pos, SNP pos in tab delimited 
    text:
    print "Name of text file with contig name and SNP position info? \n";
    my $text = <STDIN>;
    chomp $text;
    
    # Output file
    print "What are we calling the output? \n";
    my $out= <STDIN>;
    chomp $out;
    
    local $/ = "\n>"; #Read by fasta record
    
    my $seq1 = (); 
    
    open(FASTA,$fasta) || die "\n Unable to open the file!\n";
    open(POS,$text) || die "\n Unable to open the file! \n";
    my @fields = <POS>;
        while (my $seq = <FASTA>){
            chomp $seq;
            my @seq = split(/\n/,$seq);
                if($seq[0] =~ /^>/){
                    $seq1 = $seq[0];
                }elsif($seq[0] =~ /[^>]/){ #matches any character except the >
                    $seq1 = ">".$seq[0];
                }
        for my $pos (@fields){
            chomp $pos;
            my @field = split(/\t/,$pos);
        open(OUTFILE,">>$out");
        print OUTFILE "$seq1";
        my $subseq = substr $seq[1], $field[1] -1, $field[2] - $field[1]; 
        print OUTFILE "$subseq\n";
        }   
    }
    close FASTA;
    close POS;
    close OUTFILE; 
    

    这就是我的结果,这就是我想要的:

    >AOS-94_S25_L002_R1_001_trimmed_contig_767
    CACACTGATT
    >AOS-94_S25_L002_R1_001_trimmed_contig_2199
    TTTTCTTTCC
    >AOS-94_S25_L002_R1_001_trimmed_contig_2585
    AGGAGCTCAC
    

    但是,我还需要在序列名称之后打印出SNP位置(第4列),例如

    >AOS-94_S25_L002_R1_001_trimmed_contig_767
    pos=10
    CACACTGATT
    >AOS-94_S25_L002_R1_001_trimmed_contig_2199
    pos=11
    TTTTCTTTCC
    >AOS-94_S25_L002_R1_001_trimmed_contig_2585
    pos=18
    AGGAGCTCAC
    

    我尝试在print OUTFILE "pos= $field[3]\n";之后插入print OUTFILE "$seq1";并获得以下内容:

    >AOS-94_S25_L002_R1_001_trimmed_contig_767
    10
    AOS-94_S25_L002_R1_001_trimmed_contig_2199
    CACACTGATT
    >AOS-94_S25_L002_R1_001_trimmed_contig_2199
    10
    AOS-94_S25_L002_R1_001_trimmed_contig_2199
    TTTTCTTTCC
    >AOS-94_S25_L002_R1_001_trimmed_contig_2585
    10
    AOS-94_S25_L002_R1_001_trimmed_contig_2199
    AGGAGCTCAC
    

    显然我搞乱了我的循环,可能还有一些chomp命令。

    例如,当我print "$seq1"到文件时,为什么打印字符串中不包含"\n"?字符串中必须有一个硬回车?

    我知道我遗漏了一些关于这种结构的基础知识,但到目前为止我还无法弄清楚如何解决我的错误。任何人都可以提供任何建议吗?

    更新

    为易读性重新格式化Perl代码

    use warnings;
    use strict;
    
    # Read in the complete FASTA file:
    print "What is the name of the fasta contig file?\n";
    my $fasta = <STDIN>;
    chomp $fasta;
    
    # Read in file of contig name, start pos, stop pos, SNP pos in tab delimited
    text:
    print "Name of text file with contig name and SNP position info? \n";
    my $text = <STDIN>;
    chomp $text;
    
    #Output file
    print "What are we calling the output? \n";
    my $out = <STDIN>;
    chomp $out;
    
    local $/ = "\n>";    # Read by FASTA record
    
    my $seq1 = ();
    
    open( FASTA, $fasta ) || die "\n Unable to open the file!\n";
    open( POS,   $text )  || die "\n Unable to open the file! \n";
    
    my @fields = <POS>;
    
    while ( my $seq = <FASTA> ) {
    
        chomp $seq;
        my @seq = split( /\n/, $seq );
    
        if ( $seq[0] =~ /^>/ ) {
            $seq1 = $seq[0];
        }
        elsif ( $seq[0] =~ /[^>]/ ) {    # matches any character except the >
            $seq1 = ">" . $seq[0];
        }
    
        for my $pos ( @fields ) {
            chomp $pos;
            my @field = split( /\t/, $pos );
    
            open( OUTFILE, ">>$out" );
            print OUTFILE "$seq1";
    
            my $subseq = substr $seq[1], $field[1] - 1, $field[2] - $field[1];
            print OUTFILE "$subseq\n";
        }
    }
    
    close FASTA;
    close POS;
    close OUTFILE;
    

2 个答案:

答案 0 :(得分:2)

您的代码存在许多问题

  • 您的评论与代码不符。例如,当代码从STDIN接受文件名并修剪它时,您有Read in the complete FASTA file。通常最好用精心选择的标识符编写干净的代码;这样程序解释了自己

  • 您正在使用open的双参数形式和全局文件句柄。您在die字符串中也没有原因失败,并且最后有一个换行符,这将阻止Perl为您提供源文件名和行号发生错误

    这样的东西
    open( FASTA, $fasta ) || die "\n Unable to open the file!\n"
    

    应该是

    open my $fasta_fh, '<', $fasta_file or die qq{Unable to open "$fasta_file" for input: $!}
    

    open( OUTFILE, ">>$out" );
    

    应该是

    open my $out_fh, '>>', $output_file or die qq{Unable to open "$output_file" for appending: $!}
    
  • 您应该避免在变量名称周围添加引号。

    print OUTFILE "$seq1"
    

    应该是

    print OUTFILE $seq1
    
  • 您将输入记录分隔符设置为"\n>"。这意味着每次调用<FASTA> Perl都将读取该字符串的下一次出现。这也意味着chomp将从行的末尾删除该字符串,如果它在那里

最大的问题是,在从$/阅读之前,您永远不会重置POS。请注意,其设置会影响每个 readline(或<>)和每个 chomp。并且因为您的$text文件可能在行的开头不包含>个字符,所以您将一次性读取整个文件

这就是为什么你在输出中看到换行而不需要它们的原因。您已经阅读了整个文件以及所有嵌入的换行符,chomp在这里没用,因为您修改了它删除的字符串

出于某种原因,

local以这种方式命名。它会暂时将值和本地更改为当前范围。但是您的“当前范围”是文件其余部分的全部内容,并且您正在使用修改后的终结符读取这两个文件

使用一些大括号{ ... }来限制local修改的范围。或者,因为更新版本的Perl中的文件句柄表现为IO::Handle个对象,所以可以编写

$fasta_fh->input_record_separator("\n>")

并且更改仅将 应用于该文件句柄,并且无需根本本地化$/

这是您的程序的修订版本,它还解决了一些糟糕的标识符选择以及其他一些问题。 请注意 此代码 未经测试 。我目前正在火车上工作,只能检查我在写什么心理

请注意,while ( <$fasta_fh> )for ( @pos_records )之类的内容在未指定循环变量时使用默认变量$_。同样,chompsplit等运算符会在缺少相应参数时应用于$_。这样就不需要明确地提及任何变量,并且它会导致更简洁和可读的代码。 $_相当于英语中的 it

我鼓励您了解您正在撰写的内容实际上是做什么的。通常的做法是从互联网的一个部分复制代码并将其提供给其他地方的某些人,以使其适合您。这不是“学习编程”,除非你学习语言并把它放在心上,否则你什么都不会理解

更加谨慎地布置您的代码。我希望你能看到我对你的问题所做的编辑,以及我的解决方案中的代码,比你发布的程序更容易阅读?虽然你可以随心所欲地让自己的工作变得尴尬,但是向那些你要求免费编程帮助的陌生人世界提供这样的混乱是不公平和不礼貌的。一个不错的中间线是当按下Tab键时,改变你的编辑器使用四个空格的缩进。 切勿在源代码中使用制表符!

use strict;
use warnings 'all';

print "Name of the FASTA contig file: ";
chomp( my $fasta_file = <STDIN> );

print "Name file with SNP position info: ";
chomp( my $pos_file = <STDIN> );

print "Name of the output file: ";
chomp( my $out_file = <STDIN> );

open my $out_fh, '>', $out_file die qq{Unable to open "$out_file" for output: $!};

my @pos_records = do {
    open $pos_, '<', $pos_file or die qq{Unable to open "$pos_file" for input: $!};
    <$pos_fh>;
};
chomp @pos_records; # Remove all newlines

{
    open my $fasta_fh, '<', $fasta_file or die qq{Unable to open "$fasta_file" for input: $!};

    local $/ = "\n>"; # Reading FASTA format now

    while ( <$fasta_fh> ) {

        chomp;    # Remove "">\n" from the end

        my ( $header, $seq ) = split /\n/; # Separate the two lines

        $header =~ s/^>?/>/; # Replace any chomped >

        for ( @pos_records ) {

            my ( $name, $beg, $end, $pos ) = split /\t/;
            my $subseq = substr $seq, $beg-1, $end-$beg;

            print $out_fh "$header\n";
            print $out_fh "pos=$pos\n";
            print $out_fh "$subseq\n";
        }
    }
} # local $/ expires here

close $out_fh or die $!;

答案 1 :(得分:0)

好的,通过一些非常小的编辑,您的代码完美无缺。这是对我有用的解决方案:

#!/usr/bin/perl
use strict;
use warnings;

print "Name of the FASTA contig file: ";
chomp( my $fasta_file = <STDIN> );

print "Name file with SNP position info: ";
chomp( my $pos_file = <STDIN> );

print "Name of the output file: ";
chomp( my $out_file = <STDIN> );

open my $out_fh, '>', $out_file or die qq{Unable to open "out_file" for output: $!};


my @pos_records = do {
    open my $pos_, '<' , $pos_file or die qq{Unable to open "$pos_file" for input: $!};
    <$pos_>;
};
chomp @pos_records; #remove all newlines  

{
     open my $fasta_fh, '<', $fasta_file or die qq{Unable to open "$fasta_file" for input: $!};

     local $/ = "\n>"; #Reading FASTA format now

     for ( <$fasta_fh> ) {

         chomp; #Remove ">\n" from the end

         my ( $header, $seq) = split /\n/; #separate the two lines

         $header = ">$header" unless $header =~ /^>/; # Replace any chomped >


     for ( @pos_records ) {

             my ($name,$beg,$end,$pos) = split /\t/;
             my $subseq = substr $seq, $beg-1, $end-$beg;
             my $final_SNP = $end - $pos; 

             if($header =~ /$name/){

               print $out_fh "$header\n";
               print $out_fh "pos=$final_SNP\n";
               print $out_fh "$subseq\n";
     }
    } 
  }
} #local expires here

close $out_fh or die $!;

我改变的唯一实质性内容是增加了一个if语句。没有它,每个fasta序列被写三次,每一个都有一个具有三个SNP位置之一。我也略微改变了我在做什么来表示SNP位置,在切除序列之后,实际上是$ end - $ pos而不仅仅是$ pos。

同样,我不能够感谢你,因为很明显你花了很多时间来帮助我。为了它的价值,我真诚地感激它。你的解决方案将作为我未来努力的模板(这可能是对fasta文件的类似操作),你的解释帮助我更好地理解像豌豆大脑可以理解的本地所做的事情。