1

Given this graph:

import igraph as ig 
g=ig.Graph.Erdos_Renyi(10, 0.5, directed=True)

we can find its triad census easily with the triad_census function:

tc = g.triad_census()

If we print 'tc', we get something like:

003 : -2147483648 | 012 :  5 | 102 :  2 | 021D:  4
021U:  6 | 021C:  8 | 111D:  9 | 111U: 14
030T:  5 | 030C:  3 | 201 :  6 | 120D:  9
120U:  5 | 120C: 21 | 210 : 18 | 300 :  4

That is, for every triad type, we have the number of times it was found in the graph.

Unlike "triad census", a "triad list" would give not only the number of times that triad was found, but the participants nodes in every occurrence. As far as I know, the problem here is that "triad listing algorithms" are not necessarily the same ones than "triad census algorithms", the second being less computationally expensive.

I tried by looking at isomorphisms, defining every triad and then searching them in the graph:

# Set up the 16 possible triads
triad_dict=dict()

#003 A,B,C, the empty graph.
triad = ig.Graph(n = 3, directed=True)
triad_dict['003'] = triad

#012 A->B, C, the graph with a single directed edge.
triad = ig.Graph(n = 3, directed=True)
triad.add_edges([(0,1)])
triad_dict['012'] = triad

#102 A<->B, C, the graph with a mutual connection between two vertices.
triad = ig.Graph(n = 3, directed=True)
triad.add_edges([(0,1),
                (0,2)])
triad_dict['102'] = triad

#021D A<-B->C, the out-star.
triad = ig.Graph(n = 3, directed=True)
triad.add_edges([(0,1),
               (0,2)])
triad_dict['021D'] = triad

#021U A->B<-C, the in-star.
triad = ig.Graph(n = 3, directed=True)
triad.add_edges([(1,0),
                 (2,0)])
triad_dict['021U'] = triad 

#021C A->B->C, directed line.
triad = ig.Graph(n = 3, directed=True)
triad.add_edges([(0,1),
                 (1,2)])
triad_dict['021C'] = triad 

#111D A<->B<-C.
triad = ig.Graph(n = 3, directed=True)
triad.add_edges([(0,1),
               (0,2)])
triad_dict['111D'] = triad

#111U A<->B->C.
triad = ig.Graph(n = 3, directed=True)
triad.add_edges([(0,1),
               (0,2),(2,0)])
triad_dict['111U'] = triad

#030T A->B<-C, A->C.
triad = ig.Graph(n = 3, directed=True)
triad.add_edges([(0,1),
               (2,1),
               (0,2)])
triad_dict['030T'] = triad

#030C A<-B<-C, A->C.
triad = ig.Graph(n = 3, directed=True)
triad.add_edges([(1,0),
                 (2,1),
                 (0,2)])
triad_dict['030C'] = triad

#201 A<->B<->C.
triad = ig.Graph(n = 3, directed=True)
triad.add_edges([(0,1),(1,0),
                 (1,2),(2,1)])
triad_dict['201'] = triad

#120D A<-B->C, A<->C.
triad = ig.Graph(n = 3, directed=True)
triad.add_edges([(0,1),
                 (0,2),
                 (1,2), (2,1)])
triad_dict['120D'] = triad

#120U A->B<-C, A<->C.
triad = ig.Graph(n = 3, directed=True)
triad.add_edges([(0,1),
                (2,1),
                (0,2), (2,0)])
triad_dict['120U'] = triad

#120C A->B->C, A<->C.
triad = ig.Graph(n = 3, directed=True)
triad.add_edges([(0,1),
                 (1,2),
                 (0,2), (2,0)])
triad_dict['120C'] = triad

#210 A->B<->C, A<->C.
triad = ig.Graph(n = 3, directed=True)
triad.add_edges([(0,1), 
                 (1,2), (2,1),
                 (0,2), (2,0)])
triad_dict['210'] = triad

#300 A<->B<->C, A<->C, the complete graph.
triad = ig.Graph(n = 3, directed=True)
triad.add_edges([(0,1), (1,0),
                (1,2), (2,1),
                (0,2), (2,0)])
triad_dict['300'] = triad

seq = ['300', 
       '210', 
       '120C', '120U', '120D', '201',
       '030C', '030T','111U','111D',
       '021C','021U','021D','102',
       '012',
       '003']

#Search isomorphisms for every triad
isomorphisms = dict()
for key in seq:
    isomorphisms[key] = g.get_subisomorphisms_vf2(triad_dict[key])

However, if we have A<->B<->C, it will be associated to several triads:

A<->B<->C
A->B->C
A<->B->C
etc.

and I only want to consider the more complete triad (the one with more edges) and discard its sub-triads. We could clean the duplicated triads in the isomorphisms dictionary, going from higher order (more edges) to lower order level, for instance:

#Search isomorphisms for every triad
seen = []
for key in seq:
    isos = isomorphisms[key]
    isos = [i for i in isos if i not in seen]
    isomorphisms[key] = isos 
    seen.extend(isos)

However, this is not an option because the graph represents a set of conversations between nodes. If two different conversations happened:

A->B->C->D (1)
A->B->C (2)

(the graph is multiedged)

the script will delete (1) as it will think that (2) is the subgraph corresponding to (1), but it is not necessarily true as in this case they represent different conversations.

alberto
  • 2,625
  • 4
  • 29
  • 48

1 Answers1

0

You can list the subgraph isomorphisms to a specific motif, see http://igraph.sourceforge.net/doc/python/igraph.GraphBase-class.html#get_subisomorphisms_vf2

EDIT

This is actually a wrong answer, because you will find induced subgraphs that contain the motif you are searching for. See comments.

Unfortunately (currently) the only way is to do it from C and write a callback function that is called every time a motif is found. http://igraph.sourceforge.net/doc/html/ch17s03.html#igraph_motifs_randesu_callback

Gabor Csardi
  • 10,705
  • 1
  • 36
  • 53
  • the problem with isomorphisms is that, if I look for two isomorphisms where the first one is a subgraph of the second one: `A->B (1); A->B->C (2);` then pattern (1) will be detected also in subgraphs that are like (2). (I edited the question to clarify this) – alberto Nov 26 '13 at 12:47
  • 1
    Yes, you are right, of course. Unfortunately then the only way is to do it from C and write a callback function that is called every time a motif is found. http://igraph.sourceforge.net/doc/html/ch17s03.html#igraph_motifs_randesu_callback – Gabor Csardi Nov 26 '13 at 14:43
  • Also, please submit a feature request for this here: https://github.com/igraph/igraph/issues. Thanks – Gabor Csardi Nov 26 '13 at 14:43