Подсчет вхождения подграфа в графе - PullRequest
2 голосов
/ 12 ноября 2010

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

a + b  
b + c  
c - f  
b - d  
a + c  
f + g  
g + h  
f + h  

'+' указывает на то, что ген слева положительно регулирует ген справа.В этих данных я хочу подсчитать подграф, где ген (скажем, x) положительно регулирует другой ген (скажем, y), а y, в свою очередь, положительно регулирует еще один ген (скажем, z).Кроме того, z также положительно регулируется х.На приведенном выше графике есть два таких случая.Я хочу выполнить этот поиск предпочтительно с помощью awk, но любой язык сценариев в порядке.Приношу свои извинения за слишком конкретный вопрос и заранее благодарю за помощь.

Ответы [ 4 ]

2 голосов
/ 12 ноября 2010

Примечание: см. Информацию о Graphviz ниже.

Это должно дать вам отправную точку:

Редактировать: Эта версия обрабатывает геныкоторые описываются более чем одним символом.

awk '
    BEGIN { regdelim = "|" }
    {
        delim=""
        if ($2 == "+") {
            if (plus[$1]) delim=regdelim
            plus[$1]=plus[$1] delim $3
        }
        else
            if ($2 == "-") {
            if (minus[$1]) delim=regdelim
                minus[$1]=minus[$1] delim $3
            }
    }
    END {
        for (root in plus) {
            split(plus[root],regs,regdelim)
            for (reg in regs) {
                if (plus[regs[reg]] && plus[root] ~ plus[regs[reg]]) {
                    print "Match: ", root, "+", regs[reg], "+", plus[regs[reg]]
                }
            }
        }
    }
' inputfile

В предложении BEGIN установите regdelim на символ, который не отображается в ваших данных.

У меня естьпропущен код обработки минус-данных.

Вывод:

Match:  a + b + c
Match:  f + g + h

Редактировать 2:

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

#!/bin/bash
# written by Dennis Williamson - 2010-11-12
# for /3395312/podschet-vhozhdeniya-podgrafa-v-grafe
# A (AB) B, A (AC) C, B (BC) C - where "(XY)" represents a + or a - 
# provided by the positional parameters $1, $2 and $3
# $4 carries the data file name and is referenced at the end of the script
awk -v AB=$1 -v AC=$2 -v BC=$3 '
    BEGIN { regdelim = "|" }
    {
        if ($2 == AB) {
            if (regAB[$1]) delim=regdelim; else delim=""
            regAB[$1]=regAB[$1] delim $3
        }
        if ($2 == AC) {
            if (regAC[$1]) delim=regdelim; else delim=""
            regAC[$1]=regAC[$1] delim $3
        }
        if ($2 == BC) {
            if (regBC[$1]) delim=regdelim; else delim=""
            regBC[$1]=regBC[$1] delim $3
        }
    }
    END {
        for (root in regAB) {
            split(regAB[root],ABarray,regdelim)
            for (ABindex in ABarray) {
                split(regAC[root],ACarray,regdelim)
                for (ACindex in ACarray) {
                    split(regBC[ABarray[ABindex]],BCarray,regdelim)
                    for (BCindex in BCarray) {
                        if (ACarray[ACindex] == BCarray[BCindex]) {
                            print "    Match:", root, AB, ABarray[ABindex] ",", root, AC, ACarray[ACindex] ",", ABarray[ABindex], BC, BCarray[BCindex]
                        }
                    }
                }
            }
        }
    }
' "$4"

Это можно назвать так, чтобы выполнить исчерпывающий поиск:

for ab in + -; do for ac in + -; do for bc in + -; do echo "Searching: $ab$ac$bc"; ./searchgraph $ab $ac $bc inputfile; done; done; done

Для этих данных:

a - e
a + b
b + c
c - f
m - n
b - d
a + c
b - e
l - n
f + g
b + i
g + h
l + m
f + h
a + i
a - j
k - j
a - k

Вывод цикла оболочки, вызывающего новую версию скрипта, будет выглядеть следующим образом:

Searching: +++
    Match: a + b, a + c, b + c
    Match: a + b, a + i, b + i
    Match: f + g, f + h, g + h
Searching: ++-
Searching: +-+
Searching: +--
    Match: l + m, l - n, m - n
    Match: a + b, a - e, b - e
Searching: -++
Searching: -+-
Searching: --+
Searching: ---
    Match: a - k, a - j, k - j

Редактировать 3:

Graphviz

Другим подходом будет использование Graphviz .Язык DOT может описывать график, а gvpr, который является "AWK-подобным" 1 языком программирования, может анализировать и манипулировать файлами DOT.

image

Учитывая входные данные в формате, показанном в вопросе, вы можете использовать следующую программу AWK для преобразования их в DOT:

#!/usr/bin/awk -f
BEGIN {
    print "digraph G {"
    print "    size=\"5,5\""
    print "    ratio=.85"
    print "    node [fontsize=24 color=blue penwidth=3]"
    print "    edge [fontsize=18 labeldistance=5 labelangle=-8 minlen=2 penwidth=3]"
    print "    {rank=same; f l}"
    m  = "-"    # ASCII minus/hyphen as in the source data
    um = "−"    # u2212 minus: − which looks better on the output graphic
    p  = "+"
}

{
    if ($2 == m) { $2 = um; c = lbf = "red"; arr=" arrowhead = empty" }
    if ($2 == p) { c = lbf = "green3"; arr="" }
    print "    " $1, "->", $3, "[taillabel = \"" $2 "\" color = \"" c "\" labelfontcolor = \"" lbf "\"" arr "]"
}
END {
    print "}"
}

Команда для запуска будет выглядеть примерно так:

$ ./dat2dot data.dat > data.dot

Вы можете создать рисунок выше, используя:

$ dot -Tpng -o data.png data.dot

Я использовал расширенные данные, как указано вышев этом ответе.

Чтобы выполнить исчерпывающий поиск по типу указанных вами подграфов, вы можете использовать следующую программу gvpr:

BEGIN {
    edge_t AB, BC, AC;
}

E {
    AB = $;
    BC = fstedge(AB.head);
    while (BC && BC.head.name != AB.head.name) {
        AC = isEdge(AB.tail,BC.head,"");
        if (AC) {
            printf("%s %s %s, ", AB.tail.name, AB.taillabel, AB.head.name);
            printf("%s %s %s, ", AC.tail.name, AC.taillabel, AC.head.name);
            printf("%s %s %s\n", BC.tail.name, BC.taillabel, BC.head.name);
        }
        BC = nxtedge(BC, AB.head);
    }
}

Для ее запуска вы можете использовать:

$ gvpr -f groups.g data.dot | sort -k 2,2 -k 5,5 -k 8,8

Вывод будет аналогичен приведенному выше для комбинации AWK / оболочки (в разделе «Редактировать 2»):

a + b, a + c, b + c
a + b, a + i, b + i
f + g, f + h, g + h
a + b, a − e, b − e
l + m, l − n, m − n
a − k, a − j, k − j

1 Грубо говоря.

1 голос
/ 12 ноября 2010

Нетрадиционный подход с использованием Perl приведен ниже.

#! /usr/bin/perl

use warnings;
use strict;

my $graph = q{
  a + c
  b + c
  c - f
  b - d
  a + b
  f + g
  g + h
  f + h
};

my $nodes = join ",", sort keys %{ { map +($_ => 1), $graph =~ /(\w+)/g } };
my $search = "$nodes:$nodes:$nodes:$graph";

my $subgraph = qr/
  \A  .*?  (?<x>\w+)  .*:
      .*?  (?<y>\w+)  .*:
      .*?  (?<z>\w+)  .*:
  (?= .*^\s*  \k<x>  \s*  \+  \s*  \k<y>  \s*$)
  (?= .*^\s*  \k<y>  \s*  \+  \s*  \k<z>  \s*$)
  (?= .*^\s*  \k<x>  \s*  \+  \s*  \k<z>  \s*$)
  (?{ print "x=$+{x}, y=$+{y}, z=$+{z}\n" })
  (?!)
/smx;

$search =~ /$subgraph/;

Движок регулярных выражений - мощный инструмент .Для вашей задачи мы опишем структуру транзитивного подграфа, а затем дадим полученной машине найти все из них.

Вывод:

x=a, y=b, z=c
x=f, y=g, z=h

Более общий инструмент, использующийтакая же техника возможна.Например, допустим, вы хотите иметь возможность указывать паттерны генов, такие как a+b+c;a+c или g1+g2-g3;g1+g3.Я надеюсь, что значения этих шаблонов очевидны.

В начале я указываю минимальную версию 5.10.0, потому что код использует // и лексический $_.Код создает динамические регулярные выражения, которые будут оценивать код, что позволяет прагма use re 'eval'.

#! /usr/bin/perl

use warnings;
use strict;

use 5.10.0;
use re 'eval';

Идентификатор представляет собой последовательность из одного или нескольких «словосочетаний», , т. Е. , букв, цифры или подчеркивание.

my $ID = qr/\w+/;

Учитывая регулярное выражение, принимающее имена переменных, unique_vars ищет в какой-либо спецификации все имена переменных и возвращает их без повторений.

sub unique_vars {
  my($_,$pattern) = @_;
  keys %{ { map +($_ => undef), /($pattern)/g } };
}

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

Первая часть с несколькими вхождениями переменных, разделенных запятыми, позволяет механизму регулярных выражений пробовать каждое возможное значение для каждого гена.Затем прогнозисты (?=...) сканируют график в поисках ребер с требуемыми свойствами.Если все поиски успешны, мы записываем попадание.

Странное регулярное выражение (?!) в конце - это безусловный сбой, который вынуждает matcher вернуться назад и попытаться найти совпадение с другими генами.Поскольку это безоговорочно, механизм оценит все возможности.

Одновременный вызов одного и того же замыкания из нескольких потоков может привести к странным результатам.

sub compile_gene_pattern {
  my($dataset,$pattern) = @_;
  my @vars   = sort +unique_vars $pattern, qr/[a-z]\d*/;  # / for SO hilite
  my $nodes  = join ",", sort +unique_vars $dataset, $ID;
  my $search = join("", map "$_:", ($nodes) x @vars) . "\n"
             . $dataset;

  my $spec = '\A' . "\n" . join("", map ".*?  (?<$_>$ID)  .*:\n", @vars);
  for (split /;/, $pattern) {
    while (s/^($ID)([-+])($ID)/$3/) {
      $spec .= '(?= .*^\s*  ' .
               ' \b\k<' .           $1  . '>\b ' .
               ' \s*'   . quotemeta($2) . '\s* ' .
               ' \b\k<' .           $3  . '>\b ' .
               ' \s*$)' . "\n";
    }
  }
  my %hits;
  $spec .= '(?{ ++$hits{join "-", @+{@vars}} })' . "\n" .
           '(?!) # backtrack' . "\n";

  my $nfa = eval { qr/$spec/smx } || die "$0: INTERNAL: bad regex:\n$@";
  sub {
    %hits = ();  # thread-safety? :-(
    (my $_ = $search) =~ /$nfa/;
    map [split /-/], sort keys %hits;
  }
}

Прочитайте набор данных и сообщите пользователю о любомпроблемы.

sub read_dataset {
  my($path) = @_;

  open my $fh, "<", $path or die "$0: open $path: $!";

  local $/ = "\n";
  local $_;
  my $graph;

  my @errors;
  while (<$fh>) {
    next if /^\s*#/ || /^\s*$/;

    if (/^ \s* $ID \s* [-+] \s* $ID \s* $/x) {
      $graph .= $_;
    }
    else {
      push @errors, "$.: syntax error";
    }
  }

  return $graph unless @errors;

  die map "$0: $path:$_\n", @errors;
}

Теперь мы приводим все это в движение:

my $graphs = shift // "graphs.txt";
my $dataset = read_dataset $graphs;

my $ppp = compile_gene_pattern $dataset, "a+b+c;a+c";
print "@$_\n" for $ppp->();

my $pmp = compile_gene_pattern $dataset, "g1+g2-g3;g1+g3";
print "@$_\n" for $pmp->();

Учитывая graphs.txt с содержимым

a + b  
b + c  
c - f  
b - d  
a + c  
f + g  
g + h  
f + h

foo + bar
bar - baz
foo + baz

и затем запустив программу, мыполучить следующий вывод:

a b c
f g h
foo bar baz
0 голосов
/ 12 ноября 2010

Структура регулярного выражения в мой другой ответ напоминает обработку списка-монады. Учитывая это вдохновение, поиск переходных подграфов приведен ниже как грамотный Haskell . Скопируйте и вставьте этот ответ в файл с расширением .lhs, чтобы получить работающую программу. Обязательно окружите участки кода, отмеченные линией >, пустыми строками.

Спасибо за интересную проблему!

Немного переднего вопроса:

> {-# LANGUAGE ViewPatterns #-}

> module Main where

> import Control.Monad (guard)
> import Data.List (nub)
> import Data.Map (findWithDefault,fromListWith,toList)

Имя гена может быть любой строкой, и для данного Gene g функция типа PosReg должна дать нам все гены, которые g положительно регулирует.

> type Gene = String
> type PosReg = Gene -> [Gene]

Из графика, указанного в вашем вопросе, мы хотим, чтобы тройки генов были такими, чтобы отношение «положительно-регулируемое» было транзитивным, а subgraphs описывает требуемые свойства. Сначала выберите произвольный ген x из графика. Затем выберите один из генов y , который x положительно регулирует. Чтобы удержать транзитивное свойство, z должен быть геном, который позитивно регулирует как x , так и y .

> subgraphs :: String -> [(Gene,Gene,Gene)]
> subgraphs g = do
>   x <- choose
>   y <- posRegBy x
>   z <- posRegBy y
>   guard $ z `elem` posRegBy x
>   return (x,y,z)
>   where (choose,posRegBy) = decode g

С помощью простого парсера в decode мы получаем список генов на графике и функцию PosReg, которая дает всем генам положительно регулируемые каким-либо другим геном.

> decode :: String -> ([Gene], PosReg)
> decode g =
>   let pr = fromListWith (++) $ go (lines g)
>       gs = nub $ concatMap (\(a,b) -> a : b) $ toList pr
>   in (gs, (\x -> findWithDefault [] x pr))
>   where
>     go ((words -> [a, op, b]):ls)
>       | op == "+" = (a,[b]) : go ls
>       | otherwise = go ls
>     go _ = []

Наконец, основная программа склеивает все это вместе. Для каждого найденного подграфа выведите его на стандартный вывод.

> main :: IO ()
> main = mapM_ (putStrLn . show) $ subgraphs graph
>   where graph = "a + b\n\
>                 \b + c\n\
>                 \c - f\n\
>                 \b - d\n\
>                 \a + c\n\
>                 \f + g\n\
>                 \g + h\n\
>                 \f + h\n"

Выход:

("a","b","c")
("f","g","h")
0 голосов
/ 12 ноября 2010

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

Node {color = 0; title = ""; minusNodeSet = null; plusNodeSet = null}

Если color = 0 (значение по умолчанию color означает, что вы ранее не посещали этот узел), title будет 'a', 'b', 'c' и т. Д. minusNodeSet - это набор узлов, где хранятся эти узлы, где вершина минус указывает на наш узел, plusNodeSet - это набор узлов, где хранятся эти узлы, где вершина плюс указывает на наш узел.

Теперь у нас есть архитектура, и мы должны использовать ее в глубинном алгоритме:

int depth_first(Node actualNode)
{
    if (actualNode.color == 1)
    return;

    number = 1;
    actualNode.color = 1;
    foreach actualNode.nodeSet as node do
        if (node.color == 0)
            number = number + depth_first(node);
    return number;
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...