Perl: удаление дубликатов из большого набора данных

#perl #duplicates #bioinformatics

#perl #дубликаты #биоинформатика

Вопрос:

Я использую Perl для генерации списка уникальных экзонов (которые являются единицами генов).

Я сгенерировал файл в этом формате (с сотнями тысяч строк):

chr1 1000 2000 gene1

chr1 3000 4000 gene2

chr1 5000 6000 gene3

chr1 1000 2000 gene4

Позиция 1 — это хромосома, позиция 2 — начальная координата экзона, позиция 3 — конечная координата экзона, а позиция 4 — это название гена.

Поскольку гены часто состоят из разного расположения экзонов, у вас есть один и тот же экзон в нескольких генах (см. Первый и четвертый наборы). Я хочу удалить эти «дубликаты», то есть удалить gene1 или gene4 (не важно, какой из них будет удален).

Я часами бился головой о стену, пытаясь выполнить то, что (я думаю) является простой задачей. Кто-нибудь может указать мне правильное направление? Я знаю, что люди часто используют хэши для удаления повторяющихся элементов, но это не совсем дубликаты (поскольку названия генов разные). Важно, чтобы я также не потерял название гена. В противном случае это было бы проще.

Вот совершенно нефункциональный цикл, который я пробовал. В массиве «exons» каждая строка хранится как скаляр, отсюда и подпрограмма. Не смейтесь. Я знаю, что это не работает, но, по крайней мере, вы можете видеть (я надеюсь), что я пытаюсь сделать:

 for (my $i = 0; $i < scalar @exons; $i  ) {
my @temp_line = line_splitter($exons[$i]);                      # runs subroutine turning scalar into array
for (my $j = 0; $j < scalar @exons_dup; $j  ) {
    my @inner_temp_line = line_splitter($exons_dup[$j]);        # runs subroutine turning scalar into array
    unless (($temp_line[1] == $inner_temp_line[1]) amp;amp;           # this loop ensures that the the loop
            ($temp_line[3] eq $inner_temp_line[3])) {           # below skips the identical lines
                if (($temp_line[1] == $inner_temp_line[1]) amp;amp;   # if the coordinates are the same
                    ($temp_line[2] == $inner_temp_line[2])) {   # between the comparisons
                        splice(@exons, $i, 1);                  # delete the first one
                    }
            }
}
  

}

Ответ №1:

 my @exons = (
    'chr1 1000 2000 gene1',
    'chr1 3000 4000 gene2',
    'chr1 5000 6000 gene3',
    'chr1 1000 2000 gene4'
);

my %unique_exons = map { 
    my ($chro, $scoor, $ecoor, $gene) = (split(/s /, $_));
    "$chro $scoor $ecoor" => $gene
} @exons;

print "$_ $unique_exons{$_} n" for keys %unique_exons;
  

Это придаст вам уникальность, и будет включено последнее название гена. Это приводит к:

 chr1 1000 2000 gene4 
chr1 5000 6000 gene3 
chr1 3000 4000 gene2
  

Комментарии:

1. Что ж, кажется, это работает. Я говорю «кажется», поскольку я не просматривал это вручную — теперь я ухожу, чтобы узнать больше о «карте» и хэшах.

2. Теперь, когда я рассмотрел это более внимательно, я думаю, что понимаю, что это делает — и это элегантно. Я думал об использовании имени гена в качестве ключа, а остальных — в качестве значений, но это (краткий) способ сделать обратное. Поскольку у вас не может быть идентичных ключей, это удаляет дубликаты. Итак, сохраненное имя гена является «последним» в, верно?

Ответ №2:

Вы можете использовать хэш для быстрой дедупликации, но вам нужен способ объединить части, которые вы хотите использовать для обнаружения дубликатов, в одну строку.

 sub extract_dup_check_string {
    my $exon = shift;
    my @parts = line_splitter($exon);
    # modify to suit:
    my $dup_check_string = join( ';', @parts[0..2] );
    return $dup_check_string;
}

my %seen;
@deduped_exons = grep !$seen{ extract_dup_check_string($_) }  , @exons;
  

Ответ №3:

Вы можете использовать хэш для отслеживания дубликатов, которые вы уже видели, а затем пропустить их. В этом примере предполагается, что поля во входном файле разделены пробелами:

 #!/usr/bin/env perl                                                                                                                                                                                                                   

use strict;
use warnings;

my %seen;
while (my $line = <>) {

  my($chromosome, $exon_start, $exon_end, $gene) = split /s /, $line;
  my $key = join ':', $chromosome, $exon_start, $exon_end;

  if ($seen{$key}) {
    next;
  }
  else {
    $seen{$key}  ;
    print $line;
  }

}
  

Комментарии:

1. Если вы цените краткость, а не ясность, мое решение можно переписать как однострочное на perl: perl -lanF"s" -e 'print unless $seen{join(":", @F[0-2])} ' input_file

2. Спасибо ysth и Sam за ваши ответы. Я самостоятельно изучаю Perl, поэтому пока не совсем разбираюсь в хэшах, поскольку пока не видел практического применения в своих попытках. Я поиграю с кодом, который вы двое написали после того, как я еще немного почитаю хэши, чтобы я мог лучше понять, что вы написали.

Ответ №4:

Настолько просто, насколько это возможно. Я старался использовать как можно меньше магии.

 my %exoms = ();
my $input;

open( $input, '<', "lines.in" ) or die $!;

while( <$input> )
{
    if( $_ =~ /^(w s ){3}(w )$/ ) #ignore lines that are not in expected format
    {
        my @splits = split( /s /, $_ ); #split line in $_ on multiple spaces

        my $key = $splits[1] . '_' . $splits[2];

        if( !exists( $exoms{$key} ) )
        {
            #could output or write to a new file here, probably output to a file
            #for large sets.
            $exoms{$key} = @splits; 
        }
    }

}


#demo to show what was parsed from demo input
while( my ($key, $value) = each(%exoms) )
{
    my @splits = @{$value};
    foreach my $position (@splits)
    {
        print( "$position " );
    }
    print( "n" );

}