【问题标题】:parse a tab delimited data using perl使用 perl 解析制表符分隔的数据
【发布时间】:2013-02-24 19:42:57
【问题描述】:

我有一个制表符分隔的数据。我想使用 perl 处理这些数据。我是 perl 的新手,不知道如何解决。

这是示例表:我的原始文件几乎是 GB

gi|306963568|gb|GL429799.1|_1316857_1453052 13  1
gi|306963568|gb|GL429799.1|_1316857_1453052 14  1
gi|306963568|gb|GL429799.1|_1316857_1453052 15  1
gi|306963568|gb|GL429799.1|_1316857_1453052 16  1
gi|306963568|gb|GL429799.1|_1316857_1453052 17  1
gi|306963568|gb|GL429799.1|_1316857_1453052 360 1
gi|306963568|gb|GL429799.1|_1316857_1453052 361 1
gi|306963568|gb|GL429799.1|_1316857_1453052 362 1
gi|306963568|gb|GL429799.1|_1316857_1453052 363 1
gi|306963568|gb|GL429799.1|_1316857_1453052 364 1
gi|306963568|gb|GL429799.1|_1316857_1453052 365 1
gi|306963568|gb|GL429799.1|_1316857_1453052 366 1
gi|306963580|gb|GL429787.1|_4276355_4500645 38640   1
gi|306963580|gb|GL429787.1|_4276355_4500645 38641   1
gi|306963580|gb|GL429787.1|_4276355_4500645 38642   1
gi|306963580|gb|GL429787.1|_4276355_4500645 38643   1
gi|306963580|gb|GL429787.1|_4276355_4500645 38644   1
gi|306963580|gb|GL429787.1|_4276355_4500645 38645   1

我想得到输出 名称、起始值、结束值、平均值

gi|306963568|gb|GL429799.1|_1316857_1453052 13  17  1   
gi|306963568|gb|GL429799.1|_1316857_1453052 360 366 1   
gi|306963580|gb|GL429787.1|_4276355_4500645 38640   38645   1

如果有人能分享他们的智慧,那就太好了。

【问题讨论】:

  • 使用Text::CSV_XS。 CSV 是一种讨厌全人类的可怕讨厌的格式,请始终在您和 CSV 之间保留一个经过良好测试的解析库。
  • @muistooshort:制表符分隔的文件与 CSV 不同,并且几乎总是表现良好,因为没有尝试引用包含分隔符的字段:制表符在数据中根本无效。
  • @Borodin:无关紧要。即使它被称为 Text::CSV/CSV_XS,它也可以与制表符或管道分隔符一起使用。只需将sep_char 参数传递给构造函数即可。
  • 这是制表符分隔的,不是制表符分隔的。
  • @Borodin 分隔和分隔总是不同的东西,这在解析和处理方面有所不同。仅仅因为有些人用错了词并不意味着他们是一样的。划界的意思是被包围;带引号的字符串是引号分隔的。如果您有类似“:a:b:c:”的内容,则以冒号分隔时为3个字段,以冒号分隔时为5个字段,以冒号结尾时为4个字段。明白为什么使用正确的词很重要?否则无法正确编程。

标签: perl


【解决方案1】:

一般模式是

use strict;
use warnings;

open my $fh, '<', 'myfile' or die $!;
while (<$fh>) {
  chomp;
  my @fields = split /\t/;
  ...
}

在循环中,可以通过$fields[0]$fields[2] 访问字段。


更新

我已经更好地理解了你的问题,我认为这个解决方案对你有用。 请注意,它假定输入数据已排序,正如您在问题中所显示的那样。

它在哈希%data 中累积开始和结束值、总数和计数,并保留@names 中遇到的所有名称的列表,以便数据可以按照读取的顺序显示。

程序需要输入文件名作为命令行参数。

您需要考虑平均值的格式,因为它是一个浮点值。就目前而言,它会将值显示为 16 个有效数字,您可能希望使用 sprintf 减少它。

use strict;
use warnings;

my ($filename) = @ARGV;
open my $fh, '<', $filename or die qq{Unable to open "$filename": $!};

my @names;
my %data;
my $current_name = '';
my $last_index;

while (<$fh>) {
  chomp;
  my ($name, $index, $value) = split /\t/;

  if ( $current_name ne $name or $index > $last_index + 1 ) {
    push @names, $name unless $data{$name};
    push @{ $data{$name} }, {
      start => $index,
      count => 0,
      total => 0,
    };
    $current_name = $name;
  }

  my $entry = $data{$name}[-1];
  $entry->{end} = $index;
  $entry->{count} += 1;
  $entry->{total} += $value;
  $last_index = $index;
}

for my $name (@names) {
  for my $entry (@{ $data{$name} }) {
    my ($start, $end, $total, $count) = @{$entry}{qw/ start end total count /};
    print join("\t", $name, $start, $end, $total / $count), "\n";
  }
}

输出

gi|306963568|gb|GL429799.1|_1316857_1453052 13  17  1
gi|306963568|gb|GL429799.1|_1316857_1453052 360 366 1
gi|306963580|gb|GL429787.1|_4276355_4500645 38640 38645 1

【讨论】:

    【解决方案2】:

    这将为您的问题中的示例产生相同的输出:

    #!/usr/bin/env perl -n
    #
    my ($name, $i, $value) = split(/\t/);
    
    sub print_stats {
        print join("\t", $prev_name, $start, $prev_i, $sum / ($prev_i - $start + 1)), "\n";
    }
    
    if ($prev_name eq $name && $i == $prev_i + 1) {
        $sum += $value;
        $prev_i = $i;
    }
    else {
        if ($prev_name) {
            &print_stats();
        }
        $start = $i;
        $prev_name = $name;
        $sum = $value;
        $prev_i = $i;
    }
    END {
        &print_stats();
    }
    

    将其用作:

    ./parser.pl < sample.txt
    

    更新:回答 cmets 中的问题:

    • 要将输出打印到文件,运行如下:./parser.pl &lt; sample.txt &gt; output.txt
    • $prev_name$prev_i 没有初始化,所以它们最初是 undef (= NULL)

    【讨论】:

    • 嗨,非常感谢。它完美地工作。我想知道如何打印输出到文件。我想知道您如何初始化“$prev_name”和“$prev_i”变量,以及如何通读这些行。我知道可以使用 while (defined()) 来完成。我也想知道为什么你必须使用 $prev_name 两次。 "如果 ($prev_name && $prev_name".
    • 我更新了我的帖子来回答你的问题。 $prev_name &amp;&amp; ... 在人类语言中的意思是“$prev_name 不是 null AND ...`。经过一番思考,我意识到这个条件是多余的,所以我从帖子中删除了它,请参阅更新版本。
    【解决方案3】:

    你可以这样做......

    open (FILE, 'data.txt');
    while (<FILE>) {
    chomp;
    ($name, $start_value, $end_value, $average) = split("\t");
    print "Name: $name\n";
    print "Start Value: $start_value\n";
    print "End Value: $End_Value\n";
    print "Average: %average
    print "---------\n";
    }
    close (FILE);
    exit;
    

    那些看起来像 GenBank 文件...所以我不确定你从哪里得到开始、结束值和平均值。

    【讨论】:

    • 很长一段时间以来,最佳实践一直是使用词法文件句柄和三参数open。在不检查文件是否成功的情况下打开文件并在die 字符串中打印$! 从来都不是一个好主意。没有use strict 的代码也是一个非常糟糕的主意。
    【解决方案4】:

    这是一个使用Text::CSV的例子:

    use Text::CSV;  # This will implicitly use Text::CSV_XS if it's installed
    
    my $parser = Text::CSV->new( { sep_char => '|' } );
    open my $fh, '<', 'myfile' or die $!;
    
    while (my $row = $parser->getline($fh)) {
      # $row references an array of field values from the line just read
    }
    

    另外,作为一个次要的细节,您的示例数据由竖线字符而不是制表符分隔,尽管这可能只是为了避免回答您问题的人出现复制/粘贴错误。如果实际数据是制表符分隔的,请将sep_char 设置为"\t" 而不是'|'

    【讨论】:

    • OP 的数据 以制表符分隔。每行有三个字段,第一个字段,他称之为Name,包含多个管道。
    • 好的,在这种情况下,正如我的回答所说:“如果实际数据是制表符分隔的,请将 sep_char 设置为 "\t" 而不是 '|'。”发布的示例数据不包含选项卡。
    • 是的,它确实包含制表符。如果您复制了呈现的 HTML,那么您当然不会看到它们。您需要编辑问题并从编辑框中复制。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-16
    • 1970-01-01
    • 2013-10-20
    • 1970-01-01
    • 2015-09-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多