计算图中子图的出现次数

时间:2010-11-12 01:59:01

标签: perl scripting graph awk pattern-matching

我有一个三维数据集,描述了可以表示为图形的基因相互作用。数据集的样本是:

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

'+'表示左侧的基因正向调节右侧的基因。在这个数据中,我想计算一个子图(其中一个基因(比方说,x)正向调节另一个基因(比如y)),y依次正调节另一个基因(比方说,z)。此外,z也受x的正向调节。上图中有两种这样的情况。我想最好使用awk执行此搜索,但任何脚本语言都可以。我为过于具体的问题而道歉,并提前感谢您的帮助。

4 个答案:

答案 0 :(得分:2)

注意:请参阅以下有关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 http://stackoverflow.com/questions/4161001/counting-the-occurrence-of-a-sub-graph-in-a-graph
# 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

调用新版本脚本的shell循环的输出如下所示:

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

另一种方法是使用GraphvizDOT language可以描述图形,gvpr是一种“类似AWK”的 1 编程语言,可以分析和操作DOT文件。

根据问题中显示的格式输入数据,您可以使用以下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 / shell组合的输出(在“编辑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 :(得分:1)

使用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/;

正则表达式引擎is a powerful tool。对于您的问题,我们描述了传递子图的结构,然后允许生成的机器找到所有这些。

输出:

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

使用相同技术的更通用的工具是可能的。例如,假设您希望能够指定基因模式,例如a+b+c;a+cg1+g2-g3;g1+g3。我希望这些模式的含义是显而易见的。

在前面的内容中,我指定了最低版本5.10.0,因为代码使用//和词汇$_。该代码构造了动态正则表达式,用于评估use re 'eval' pragma启用的代码。

#! /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 } };
}

将基因模式编译成正则表达式有点毛茸茸。它以与上面静态格式相同的形式动态生成搜索目标和正则表达式。

第一部分出现多次逗号分隔变量,让正则表达式引擎尝试每个基因的每个可能值。然后,前瞻(?=...)扫描图形,查找具有所需属性的边。如果所有前瞻都成功,我们会记录命中。

最后的奇怪的正则表达式(?!)是无条件的失败,迫使匹配者回溯并尝试与不同基因的匹配。因为它是无条件的,引擎将评估所有可能性。

同时从多个线程调用相同的闭包可能会产生奇怪的结果。

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

答案 2 :(得分:0)

我假设“计算子图”是指计算子图中的节点。如果这就是你需要的,你可以使用任何脚本语言,并且必须存储图形,首先,通过创建存储图形的结构或类,节点结构/类应该如下所示(这不符合要求)任何语言的语法,这只是你的应用程序的计划):

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

如果color = 0(颜色的默认值意味着您之前没有访问过此节点),则标题将为“a”,“b”,“c”等。 minusNodeSet是一组节点,其中存储了这些节点,其中来自Node的负顶点,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;
}

如果我误解了你的问题,请告诉我,能够编辑我的答案,使其更有用。

答案 3 :(得分:0)

my other answer中正则表达式的结构类似于list-monad处理。鉴于这种灵感,对传递子图的搜索低于literate 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 。接下来,选择 x 正向调节的 y 基因之一。对于要传递的传递属性, 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")