使用Perl哈希来处理制表符分隔的文件

时间:2012-05-13 21:11:04

标签: perl perl-data-structures

我有两个文件:

  • file_1有三列(标记(SNP),染色体和位置)
  • file_2有三列(Chromosome,peak_start和peak_end)。

除SNP列外,所有列均为数字。

文件的排列如屏幕截图所示。 file_1有几百个SNP作为行,而file_2有61个峰。每个峰由peak_start和peak_end标记。在任一文件中都可以有23条染色体中的任何一条,而file_2每条染色体上有几个峰。

我想找出每个匹配染色体中file_1中SNP的位置是否落在file_2的peak_start和peak_end内。如果是这样,我想显示哪个SNP落在哪个峰值(最好将输出写入制表符分隔文件)。

我更喜欢拆分文件,并使用染色体是关键的散列。我发现只有几个与此类似的问题,但我无法理解建议的解决方案。

以下是我的代码示例。它只是为了说明我的问题,到目前为止还没有做任何事情,所以把它想象成"伪代码"。

#!usr/bin/perl

use strict;
use warnings;

my (%peaks, %X81_05);
my @array;

# Open file or die

unless (open (FIRST_SAMPLE, "X81_05.txt")) {
    die "Could not open X81_05.txt";
}

# Split the tab-delimited file into respective fields

while (<FIRST_SAMPLE>) {

    chomp $_;
    next if (m/Chromosome/); # Skip the header

    @array = split("\t", $_);
    ($chr1, $pos, $sample) = @array;

    $X81_05{'$array[0]'} = (
        'position' =>'$array[1]'
    )
}

close (FIRST_SAMPLE);

# Open file using file handle
unless (open (PEAKS, "peaks.txt")) {
    die "could not open peaks.txt";
}

my ($chr, $peak_start, $peak_end);

while (<PEAKS>) {
    chomp $_;

    next  if (m/Chromosome/); # Skip header
    ($chr, $peak_start, $peak_end) = split(/\t/);
    $peaks{$chr}{'peak_start'} = $peak_start;
    $peaks{$chr}{'peak_end'}  = $peak_end;
}

close (PEAKS);

for my $chr1 (keys %X81_05) {
    my $val = $X81_05{$chr1}{'position'};

    for my $chr (keys %peaks) {
        my $min = $peaks{$chr}{'peak_start'};

        my $max = $peaks{$chr}{'peak_end'};

        if (($val > $min) and ($val < $max)) {
            #print $val, " ", "lies between"," ", $min, " ", "and", " ", $max, "\n";
        }
        else {
                #print $val, " ", "does not lie between"," ", $min, " ", "and", " ", $max, "\n";
        }
    }
}

更棒的代码:

  1. http://i.stack.imgur.com/fzwRQ.png
  2. http://i.stack.imgur.com/2ryyI.png

4 个答案:

答案 0 :(得分:3)

Perl中的一些程序提示:

你可以这样做:

open (PEAKS, "peaks.txt") 
   or die "Couldn't open peaks.txt";

而不是:

unless (open (PEAKS, "peaks.txt")) {
    die "could not open peaks.txt";
}

它更标准的Perl,它更容易阅读。

谈到标准Perl,你应该使用3参数open形式,并使用标量来处理文件句柄:

open (my $peaks_fh, "<", "peaks.txt") 
   or die "Couldn't open peaks.txt";

这样,如果您的文件名称恰好以|>开头,那么它仍然可以使用。使用标量变量(以$开头的变量)可以更容易地在函数之间传递文件句柄。

无论如何,只是为了确保我理解正确:你说“我更喜欢......使用染色体是关键的哈希。

现在,我有23对染色体,但每条染色体上可能有数千个SNP。如果您通过这种方式染色,那么每条染色体只能存储一个SNP。这是你想要的吗?我注意到你的数据显示了所有相同的染色体。这意味着你不能通过染色体来锁定。我现在忽略了这一点,并使用我自己的数据。

我还注意到您所说的文件包含的内容以及您的程序如何使用它们的区别:

你说:“文件1有3列(SNP,染色体和位置)”,但你的代码是:

($chr1, $pos, $sample) = @array;

我假设是染色体,位置和SNP。文件的排列方式是什么?

你必须准确澄清你的要求。

无论如何,这是测试版本以制表符分隔格式打印出来的。这是一种更现代的Perl格式。请注意,我只有一个染色体哈希(如您所指定)。我先读了peaks.txt。如果我在我的位置文件中找到了我的peaks.txt文件中不存在的染色体,我就会忽略它。否则,我将为 POSITION SNP 添加额外的哈希:

我做了一个最终循环,按照你的指定打印出所有内容(制表符分隔符),但你没有指定格式。如果必须,请更改它。

#! /usr/bin/env perl

use strict;
use warnings;
use feature qw(say);
use autodie;        #No need to check for file open failure
use constant {
    PEAKS_FILE        => "peak.txt",
    POSITION_FILE => "X81_05.txt",
};

open ( my $peak_fh, "<", PEAKS_FILE );
my %chromosome_hash;

while ( my $line = <$peak_fh> ) {
    chomp $line;
    next if $line =~ /Chromosome/;   #Skip Header
    my ( $chromosome, $peak_start, $peak_end ) = split ( "\t", $line );
    $chromosome_hash{$chromosome}->{PEAK_START} = $peak_start;
    $chromosome_hash{$chromosome}->{PEAK_END} = $peak_end;
}
close $peak_fh;

open ( my $position_fh, "<", POSITION_FILE );

while ( my $line = <$position_fh> ) {
    chomp $line;
    my ( $chromosome, $position, $snp ) = split ( "\t", $line );
    next unless exists $chromosome_hash{$chromosome};

    if ( $position >= $chromosome_hash{$chromosome}->{PEAK_START}
            and $position <= $chromosome_hash{$chromosome}->{PEAK_END} ) {
        $chromosome_hash{$chromosome}->{SNP} = $snp;
        $chromosome_hash{$chromosome}->{POSITION} = $position;
    }
}
close $position_fh;

#
# Now Print
#

say join ("\t", qw(Chromosome, SNP, POSITION, PEAK-START, PEAK-END) );
foreach my $chromosome ( sort keys %chromosome_hash ) {
    next unless exists $chromosome_hash{$chromosome}->{SNP};
    say join ("\t",
        $chromosome,
        $chromosome_hash{$chromosome}->{SNP},
        $chromosome_hash{$chromosome}->{POSITION},
        $chromosome_hash{$chromosome}->{PEAK_START},
        $chromosome_hash{$chromosome}->{PEAK_END},
    );
}

一些事情:

  • 在两侧的括号周围留出空格。它使阅读更容易。
  • 当别人不使用括号时,我会使用括号。除非必须,否则当前的样式不会使用它们。我倾向于将它们用于所有需要多个参数的函数。例如,我可以说open my $peak_fh, "<", PEAKS_FILE;,但我认为当函数上有三个参数时参数会开始丢失。
  • 请注意我使用use autodie;。如果程序无法打开文件,则会导致程序退出。这就是我甚至不必测试文件是否打开的原因。
  • 我宁愿使用面向对象的Perl来隐藏哈希散列的结构。这可以防止出现错误,例如认为开始窥视存储在START_PEEK而不是PEAK_START中。 Perl不会检测到这些类型的错误密钥错误。因此,每当我进行数组数组或散列哈希时,我都更喜欢使用对象。

答案 1 :(得分:1)

您只需要一个for循环,因为您希望在第二批中找到一些SNP。因此,循环遍历%X81_05哈希并检查%peak中是否有匹配。类似的东西:

for my $chr1 (keys %X81_05)
{
    if (defined $peaks{$chr1})
    {
        if (    $X81_05{$chr1}{'position'} > $peaks{$chr1}{'peak_start'}
             && $X81_05{$chr1}{'position'} < $peaks{$chr1}{'peak_end'})
        {
            print YOUROUTPUTFILEHANDLE $chr1 . "\t"
              . $peaks{$chr1}{'peak_start'} . "\t"
              . $peaks{$chr1}{'peak_end'};
        }
        else
        {
            print YOUROUTPUTFILEHANDLE $chr1
              . "\tDoes not fall between "
              . $peaks{$chr1}{'peak_start'} . " and "
              . $peaks{$chr1}{'peak_end'};
        }
    }
}

注意:我没有测试过代码。

查看您添加的屏幕截图,这不会起作用。

答案 2 :(得分:0)

@David提出的观点很好;尝试将这些内容合并到您的程序中。 (我从@David的帖子中借用了大部分代码。)

我不明白的一件事是,为什么加载峰值和散列位置,因为加载一个就足够了。由于每条染色体都有不止一条记录,请使用HoA。我的解决方案就是基于此。您可能需要更改cols及其位置。

use strict;
use warnings;

our $Sep = "\t";
open (my $peak_fh, "<", "data/file2");
my %chromosome_hash;

while (my $line = <$peak_fh>) {
    chomp $line;
    next if $line =~ /Chromosome/; #Skip Header
    my ($chromosome) = (split($Sep, $line))[0];
    push @{$chromosome_hash{$chromosome}}, $line; # Store the line(s) indexed by chromo
}
close $peak_fh;

open (my $position_fh, "<", "data/file1");

while (my $line = <$position_fh>) {
    chomp $line;
    my ($chromosome, $snp, $position) = split ($Sep, $line);
    next unless exists $chromosome_hash{$chromosome};

    foreach my $peak_line (@{$chromosome_hash{$chromosome}}) {
        my ($start,$end) = (split($Sep, $line))[1,2];

        if ($position >= $start and $position <= $end) {
            print "MATCH REQUIRED-DETAILS...$line-$peak_line\n";
        }
        else {
            print "NO MATCH REQUIRED-DETAILS...$line-$peak_line\n";
        }
    }
}
close $position_fh;

答案 3 :(得分:0)

我使用了@tuxuday和@ David的代码来解决这个问题。这是完成我想要的最终代码。我不仅学到了很多东西,而且还能成功地解决了我的问题! Kudos家伙!

use strict;
use warnings;
use feature qw(say);

# Read in peaks and sample files from command line
my $usage = "Usage: $0 <peaks_file> <sample_file>";
my $peaks = shift @ARGV or die "$usage \n";
my $sample = shift @ARGV or die "$usage \n";

our $Sep = "\t";
open (my $peak_fh, "<", "$peaks");
my %chromosome_hash;

while (my $line = <$peak_fh>) {
    chomp $line;
    next if $line =~ /Chromosome/; #Skip Header
    my ($chromosome) = (split($Sep, $line))[0];

    push @{$chromosome_hash{$chromosome}}, $line; # Store the line(s) indexed by chromosome
}
close $peak_fh;

open (my $position_fh, "<", "$sample");

while (my $line = <$position_fh>) {
    chomp $line;
    next if $line =~ /Marker/; #Skip Header
    my ($snp, $chromosome, $position) = split ($Sep, $line);

    # Check if chromosome in peaks_file matches chromosome in sample_file
    next unless exists $chromosome_hash{$chromosome};

    foreach my $peak_line (@{$chromosome_hash{$chromosome}}) {

        my ($start,$end,$peak_no) = (split( $Sep, $peak_line ))[1,2,3];

        if ( $position >= $start and $position <= $end) {

            # Print output
            say join ("\t",
                $snp,
                $chromosome,
                $position,
                $start,
                $end,
                $peak_no,
            );
        }
        else {
            next; # Go to next chromosome
        }
    }
}
close $position_fh;