-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ec156d3
Showing
8 changed files
with
533 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
# Tracking communities accross time: | ||
|
||
It tracks network communities across consecutive time frames. In particular | ||
it detects the evolutionary phenomenon a community can sustain from one time frame | ||
to another: | ||
- continuing | ||
- shrinking | ||
- growing | ||
- split | ||
- merge | ||
- dissolving | ||
- No_event (when none of the above events holds) | ||
|
||
|
||
|
||
|
||
## File Structure & running | ||
|
||
- It contain 6 .py (thon) files | ||
config.py , event.py , hypergraph.py, inclusion.py , preprocessing.py , tracker.py | ||
- To run from terminal: | ||
python tracker.py <inputfile>.json | ||
- output: a file named 'results.csv', the format is: | ||
```window ID, community ID, window ID, community ID, event``` | ||
- In config.py there are 2 parameters Alpha,Beta. These are the thresholds for the event | ||
identification. Should be between [0.1,1] | ||
|
||
## Input data format | ||
|
||
The file given as input to the GED algorithm must adhere to the following JSON template: | ||
|
||
```sh | ||
{ | ||
"windows": | ||
[ //array of windows (i.e. timeframes) | ||
{ | ||
"communities": | ||
[ //array of communities in each window | ||
[ //array of edges in each community | ||
[ //array containing two node ids between which an edge exists (this is an edge of the community) | ||
] | ||
] | ||
] | ||
} | ||
] | ||
} | ||
``` | ||
We assume that the data set has been split into time frames and that communities have | ||
been discovered in each time frame. | ||
|
||
An example input file is provided in the "input/example" directory to test GED, consisting of 20 windows. | ||
input/example/community_edges.json | ||
|
||
```sh | ||
<------- community ----------> <------- community ----------> | ||
time-step:1 --> {"windows":[{"communities":[ [[id1,id2],[id2,id3],[id3,id1]], [[id4,id5],[id5,id6],[id6,id7]] ] | ||
time-step:2 --> {"windows":[{"communities":[ [[id8,id9],[id9,id10],[id10,id1]], [[id11, id12],[id12,id13],[id13,id11]] ] | ||
``` | ||
## Output-data-format | ||
- The output file generated by GED has the following format: | ||
<window id for current timestep>,<community id for current timestep>,<window id for next timestep>,<community id for next timestep>,<event> | ||
- E.g. ```0,40,1,42,continuing``` means that community 40 in window 0 has evolved (corresponds) to community 42 in the next window (window 1) and the associated event with this evolution is continue. | ||
- Windows are numbered consecutively starting from 0. The same holds for the communities in each window. | ||
## Reference | ||
@inproceedings{brodka2011tracking, | ||
title={Tracking group evolution in social networks}, | ||
author={Br{\'o}dka, Piotr and Saganowski, Stanis{\l}aw and Kazienko, Przemys{\l}aw}, | ||
booktitle={International Conference on Social Informatics}, | ||
pages={316--319}, | ||
year={2011}, | ||
organization={Springer} | ||
} | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
# -*- coding: utf-8 -*- | ||
import json | ||
import networkx as nx | ||
import preprocessing,config | ||
import sys | ||
import time | ||
from inclusion import * | ||
from event import Event | ||
from hypergraph import Hypergraph | ||
|
||
trueEvents = {'merging':0,'splitting':0,'growing':0,'shrinking':0, | ||
'continuing':0,'dissolving':0,'forming':0,'No_event':0} | ||
|
||
class Tracker(): | ||
|
||
def __init__(self, graphs): | ||
self.graphs = graphs | ||
self.communities = [] | ||
self.results = [] | ||
self.inclusions = {} | ||
self.Dcandidates = {} | ||
self.Fcandidates = {} | ||
self.hypergraphs = [] | ||
|
||
def compare_communities(self,): | ||
for index, interval in enumerate(self.graphs): | ||
if index < len(self.graphs) - 1: | ||
self.inclusions = {} | ||
window_id = 'TF%s -> TF%s' % (index,index+1) | ||
Dhypergraph = nx.DiGraph(window=window_id) | ||
print 'Initialize inclusions dict start...' | ||
Dhypergraph = self.initialize_inclusions(index,Dhypergraph) | ||
print 'Initialize inclusions dict finish...' | ||
|
||
for ic,community_t in enumerate(interval): | ||
|
||
for ic2, community_t1 in enumerate(self.graphs[index + 1]): | ||
|
||
inclusion = self.inclusions[community_t.graph['cid']][community_t1.graph['cid']]['inclusion'] | ||
inversed = self.inclusions[community_t.graph['cid']][community_t1.graph['cid']]['inversed_inclusion'] | ||
|
||
event = Event(community_t,community_t1,inclusion,inversed,self.inclusions) | ||
result = event.classify() | ||
if result in ['growing','shrinking','continuing']: | ||
Dhypergraph.add_edge(community_t,community_t1,event_type=result) | ||
self.results.append({ 'network_t': community_t.graph['cid'], | ||
'network_t1': community_t1.graph['cid'], | ||
'resulted_event': result}) | ||
|
||
hypergraph = Hypergraph(Dhypergraph) | ||
self.hypergraphs.append(hypergraph) | ||
|
||
|
||
def initialize_inclusions(self,index,Dhypergraph): | ||
self.Dcandidates = {} | ||
self.Fcandidates = {} | ||
pastTFcommunities = self.graphs[index] | ||
futureTFcommunities = self.graphs[index+1] | ||
for ic,community_t in enumerate(pastTFcommunities): | ||
Dhypergraph.add_node(community_t) | ||
key1 = community_t.graph['cid'] | ||
self.Dcandidates[key1] = community_t | ||
self.inclusions[key1]={} | ||
for ic2, community_t1 in enumerate(futureTFcommunities): | ||
key2 = community_t1.graph['cid'] | ||
if ic==0: | ||
Dhypergraph.add_node(community_t1) | ||
self.Fcandidates[key2] = community_t1 | ||
self.inclusions[key2] = {} | ||
inclusions = CentralityInclusion(community_t, community_t1) | ||
inclusion, inversed = inclusions.compute_inclusion() | ||
if inclusion >0.1 or inversed > 0.1: | ||
if key1 in self.Dcandidates: | ||
del self.Dcandidates[key1] | ||
if key2 in self.Fcandidates: | ||
del self.Fcandidates[key2] | ||
self.inclusions[key1].update({key2:{'inclusion':inclusion,'inversed_inclusion':inversed}}) | ||
|
||
for key in self.Dcandidates.keys(): | ||
Dhypergraph.add_edge(self.Dcandidates[key],futureTFcommunities[-1],event_type='dissolving') | ||
for key in self.Fcandidates.keys(): | ||
Dhypergraph.add_edge(pastTFcommunities[-1],self.Fcandidates[key],event_type='forming') | ||
return Dhypergraph | ||
|
||
def analyze_results(self,): | ||
events_names = ['merging', 'splitting', 'growing', | ||
'shrinking', 'continuing', 'dissolving', | ||
'forming', 'no_event'] | ||
events = [e['resulted_event'] for e in self.results] | ||
for name in events_names: | ||
print name, events.count(name) | ||
|
||
if __name__=='__main__': | ||
if len(sys.argv) != 2: | ||
print 'Usage: Tracker.py <inputfile.json>' | ||
print 'Exiting with code 1' | ||
exit(1) | ||
start_time = time.time() | ||
graphs = preprocessing.getGraphs(sys.argv[1]) | ||
tracker = Tracker(graphs) | ||
tracker.compare_communities() | ||
with open('tmpfiles/ged_results.csv','w')as f: | ||
for hypergraph in tracker.hypergraphs: | ||
hypergraph.calculateEvents(f) | ||
print "--- %s seconds ---" %(time.time() - start_time) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
ALPHA = 0.9 | ||
BETA = 0.9 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
# -*- coding: utf-8 -*- | ||
import config | ||
|
||
class Event(): | ||
|
||
def __init__(self, g1, g2, inclusion, inversed_inclusion, inclusions): | ||
self.alpha = config.ALPHA | ||
self.beta = config.BETA | ||
self.g1 = g1 | ||
self.g2 = g2 | ||
self.g1_size = len(g1) | ||
self.g2_size = len(g2) | ||
self.inclusion = inclusion | ||
self.inversed_inclusion = inversed_inclusion | ||
self.inclusions = inclusions | ||
self.events = {1:'merging', | ||
2:'splitting', | ||
3:'growing', | ||
4:'shrinking', | ||
5:'continuing', | ||
6:'dissolving', | ||
7:'forming', | ||
0:'no_event'} | ||
|
||
def is_merging(self,): | ||
if ((self.inclusion >= self.alpha) and | ||
(self.inversed_inclusion < self.beta) and | ||
(self.g1_size <= self.g2_size) and | ||
(self.check_matchings(self.g1, 'network_t0') == 'more')): | ||
return True | ||
|
||
def is_splitting(self,): | ||
if ((self.inclusion < self.alpha) and | ||
(self.inversed_inclusion >= self.beta) and | ||
(self.g1_size >= self.g2_size) and | ||
(self.check_matchings(self.g2, 'network_t1') == 'more')): | ||
return True | ||
|
||
def is_growing(self,): | ||
if ((self.inclusion >= self.alpha) and | ||
(self.inversed_inclusion >= self.beta) and | ||
(self.g1_size < self.g2_size)): | ||
#~ print "Growing from",self.g1.graph,"to",self.g2.graph | ||
return True | ||
|
||
def is_shrinking(self,): | ||
if ((self.inclusion >= self.alpha) and | ||
(self.inversed_inclusion >= self.beta) and | ||
(self.g1_size > self.g2_size)): | ||
#~ print "Shrinking",self.g1.graph,"to",self.g2.graph | ||
return True | ||
|
||
def is_continuing(self,): | ||
if ((self.inclusion >= self.alpha) and | ||
(self.inversed_inclusion >= self.beta) and | ||
(self.g1_size == self.g2_size)): | ||
#~ print "Continue",self.g1.graph,"to",self.g2.graph | ||
return True | ||
|
||
def is_dissolving(self,): | ||
g1_id = self.g1.graph['cid'] | ||
g2_id = self.g2.graph['cid'].split('_')[0] | ||
coms = [group for group in self.inclusions if (g1_id == group.split('_')[0]+'_'+group.split('_')[1] and | ||
g2_id in group)] | ||
|
||
for tupel in coms: | ||
if (self.inclusions[tupel]['inclusion'] > 0.1 and | ||
self.inclusions[tupel]['inversed_inclusion'] > 0.1): | ||
return False | ||
comm = [c.split('_')[3] for c in coms] | ||
comm.sort(key=lambda x: int(filter(str.isdigit,x))) | ||
|
||
if not (self.g2.graph['cid'].split('_')[1] == comm[-1]): | ||
return False | ||
#~ print "Dissolving: past",self.g1.graph,"future",self.g2.graph | ||
return True | ||
|
||
def is_forming(self,): | ||
g2_id = self.g2.graph['cid'] | ||
g1_id = self.g1.graph['cid'].split('_')[0] | ||
coms = [group for group in self.inclusions if (g1_id in group and | ||
g2_id == group.split('_')[2]+'_'+group.split('_')[3])] | ||
|
||
for tupel in coms: | ||
if (self.inclusions[tupel]['inclusion'] > 0.1 and | ||
self.inclusions[tupel]['inversed_inclusion'] > 0.1): | ||
return False | ||
comm = [c.split('_')[1] for c in coms] #keep the all community ids of past TF | ||
comm.sort(key=lambda x: int(filter(str.isdigit,x))) | ||
|
||
if not (self.g1.graph['cid'].split('_')[1] == comm[-1]): #to avoid multiple formation of the same community | ||
return False | ||
#~ print "Forming: past",self.g1.graph,"future",self.g2.graph | ||
return True | ||
|
||
def check_matchings(self, network, window): | ||
matches = 0 | ||
events = [m for m in self.results if m[window]==network.graph['cid']] | ||
for e in events: | ||
if not e['resulted_event'] == 'no_event': | ||
matches += 1 | ||
if matches == 1: | ||
return 'one' | ||
elif matches > 1: | ||
return 'more' | ||
else: | ||
return 'none' | ||
|
||
def check(self,): | ||
#~ if self.is_merging(): | ||
#~ return 1 | ||
#~ elif self.is_splitting(): | ||
#~ return 2 | ||
if self.is_growing(): | ||
return 3 | ||
elif self.is_shrinking(): | ||
return 4 | ||
elif self.is_continuing(): | ||
return 5 | ||
#~ elif self.is_dissolving(): | ||
#~ return 6 | ||
#~ elif self.is_forming(): | ||
#~ return 7 | ||
else: | ||
return 0 | ||
|
||
def classify(self,): | ||
return self.events[self.check()] |
Oops, something went wrong.