def slice(self, start, end): """ Keep only the selected period :param start: :param end: """ to_return = tn.DynCommunitiesSN() interv = tn.Intervals((start, end)) for t in list(self.snapshots.keys()): if t in interv: to_return.set_communities(self.snapshots[t], t) return to_return
def slice(self, start, end): """ Keep only the selected period :param start: :param end: """ to_return = tn.DynGraphSN() interv = tn.Intervals((start, end)) for t in list(self._snapshots.keys()): if interv.contains_t(t): to_return.add_snapshot(t, self._snapshots[t]) return to_return
def seed_expansion(seed, granularity, t_quality, t_persistance, t_similarity, QC, CSS, pre_computed_snapshots, C): # Find recursively snapshots in which this community still makes sense similars = [] this_seed_nodes = seed[2] similars += _track_one_community(this_seed_nodes, seed[0], pre_computed_snapshots, score=QC, t_quality=t_quality, backward=True) similars += [(seed[0], seed[3])] similars += _track_one_community(this_seed_nodes, seed[0], pre_computed_snapshots, score=QC, t_quality=t_quality) #if the community is stable, i.e., makes sense more than t_persistance steps if len(similars) >= t_persistance: similars = [ similars ] # for genericity, we want to be able to deal with non-continuous intervals inter_presence = tn.Intervals([(sim[0][0], sim[-1][0] + granularity) for sim in similars]) # check that a similar community has not already been found redundant = False for nodes, period, gran, score in C: # if the communities are similar and one of them has at least half of its duration included in the other if CSS(this_seed_nodes, nodes) > t_similarity and inter_presence.intersection( period).duration() > 0.5 * min( [inter_presence.duration(), period.duration()]): redundant = True break # If community not redundant, we compute its quality and add it to the list of communities if not redundant: sum_quality = 0 for stable in similars: sum_quality += sum([1 - (x[1]) for x in stable]) C.append( (this_seed_nodes, inter_presence, granularity, sum_quality))
def slice(self,start,end): """ Keep only the selected period :param start: :param end: """ to_return = tn.DynCommunitiesIG() interv = tn.Intervals((start,end)) for c_id,c in self.communities().items(): for n,the_inter in c.items(): duration = interv.intersection(the_inter) if duration.duration()>0: to_return.add_affiliation(n,c_id,duration) return to_return
def test_add_interactions_ig(self): dg = tn.DynGraphIG() dg.add_interaction(1,2,(4,5)) self.assertEqual(set(dg.graph_at_time(4).edges()),set([(1,2)])) dg.add_interaction(1, 2, (6,7)) self.assertEqual(dg.edge_presence((1,2),as_intervals=True),tn.Intervals([(4,5),(6,7)])) dg.add_interactions_from({2, 3}, (6,7,9)) self.assertEqual(list(dg.graph_at_time(6).edges()),[(1,2),(2,3)]) dg.add_interactions_from([(5, 6),(6,7)], (9,10)) self.assertEqual(list(dg.graph_at_time(9).edges()),[(5,6),(6,7)]) dg.add_interactions_from([(1, 2),(1,3)], (10,12)) self.assertEqual(list(dg.graph_at_time(10).edges()),[(1,2),(1,3)]) self.assertEqual(list(dg.graph_at_time(11).edges()),[(1,2),(1,3)])
def to_DynCommunitiesIG(self, sn_duration, convertTimeToInteger=False): """ Convert to SG snapshot_affiliations :param sn_duration: time of a snapshot, or None for automatic: each snapshot last until start of the next :param convertTimeToInteger: if True, snapshot_affiliations IDs will be forgottent and replaced by consecutive integers :return: DynamicCommunitiesIG """ dynComTN= tn.DynCommunitiesIG() for i in range(len(self.snapshots)): if convertTimeToInteger: t=i tNext=i+1 else: current_t = self.snapshots.peekitem(i)[0] if sn_duration != None: tNext = current_t + sn_duration else: if i < len(self.snapshots) - 1: tNext = self.snapshots.peekitem(i + 1)[0] else: # computing the min duration to choose as duration of the last period dates = list(self.snapshots.keys()) minDuration = min([dates[i + 1] - dates[i] for i in range(len(dates) - 1)]) tNext = current_t + minDuration for (cID,nodes) in self.snapshots.peekitem(i)[1].items(): #for each community for this timestep dynComTN.add_affiliation(nodes,cID,tn.Intervals((current_t,tNext))) #convert also events for (u,v,d) in self.events.edges(data=True): if d["type"]!="continue": #if snapshot_affiliations have different IDs dynComTN.events.add_event(u[1],v[1],d["time"][0],d["time"][1],d["type"]) return dynComTN
def track_communities(dyn_graph, t_granularity=1, t_persistance=3, t_quality=0.7, t_similarity=0.3, similarity=jaccard, CD="louvain", good_community=score_conductance, weighted_aggregation=True, Granularity=None, start_time=None): """ Proposed method to track communities :param dyn_graph: :param t_granularity: (\theta_\gamma) min temporal granularity/scale to analyze :param t_persistance:(\theta_p) minimum number of successive occurences for the community to be persistant :param t_quality: (\theta_q) threashold of community quality :param t_similarity: (\theta_q) threashold of similarity between communities :param similarity: (CSS)function that give a score of similarity between communities. Default: jaccard :param CD: (CD) community detection algorithm. A function returning a set of set of nodes. By default, louvain algorithm :param good_community: (QC)function to determine the quality of communities. Default: inverse of conductance :param weighted_aggregation: if true, the aggregation over time periods is done using weighted networks :param Granularity: (\Gamma) can be used to replace the default scales. List of int. :param start_time: the date at which to start the analysis. Can be useful, for instance, to start analysis at 00:00 :return: """ #set up the list of granularity/temporal scales to analyze if Granularity == None: Granularity = studied_scales(dyn_graph, t_granularity, t_persistance) if isinstance(Granularity, int): Granularity = [Granularity] if start_time == None: start_time = dyn_graph.snapshots_timesteps()[0] persistant_coms = [] all_graphs = {} all_coms = {} #for each granularity level for current_granularity in Granularity: seeds = [] #Aggregate the graph WITH or WITHOUT weights (on real data I checked, give better results without weights due to some very stronger weights between a small subset of nodes) s_graph_current_granularity = dyn_graph.aggregate_sliding_window( t_start=start_time, bin_size=current_granularity, weighted=weighted_aggregation) all_graphs[current_granularity] = s_graph_current_granularity #print("computing communities") dyn_coms = iterative_match(s_graph_current_granularity, CDalgo=CD) #,CDalgo=infomap_communities) for t, g in s_graph_current_granularity.snapshots().items(): interesting_connected_com = nx.connected_components(g) interesting_connected_com = [ x for x in interesting_connected_com if len(x) >= 3 ] for c in interesting_connected_com: dyn_coms.add_community(t, c) all_coms[current_granularity] = dyn_coms #print("computing quality for each com") for t, coms in all_coms[current_granularity].snapshots.items(): current_graph = s_graph_current_granularity.snapshots(t) for cID, nodes in coms.items(): quality = good_community(nodes, current_graph) seeds.append( (t, cID, frozenset(nodes), quality, current_granularity) ) ##structure of items in coms_and_qualities: (t,cID,frozenset(nodes),quality,granularity) seeds.sort(key=lambda x: x[3], reverse=True) #print("#nb seeds total",len(seeds)) seeds = [c for c in seeds if c[3] >= t_quality] nb_good_seeds = len(seeds) #print("--",seeds) #filter out similar communities (same nodes in same period) for nodes, period, gran, score in persistant_coms: #for each already save community # keep in the list of seeds those that are in a different period or that are sufficiently different, compared with the current one #seeds = [c for c in seeds if not period.contains_t(c[0]) or similarity(c[2],current_com[2])<t_no_overlap] seeds = [ c for c in seeds if not seed_contained_in_persistent_com(nodes, c[2], c[0], period, similarity=similarity, t_similarity=t_similarity) ] while len(seeds) > 0: current_com = seeds.pop(0) this_seed_nodes = current_com[2] #if interesting_node in this_seed_nodes: # print("com tested ",this_seed_nodes) #Find recursively snapshots in which this community still makes sense similars = [] similars += _track_one_community(this_seed_nodes, current_com[0], s_graph_current_granularity, score=good_community, t_quality=t_quality, backward=True) similars += [(current_com[0], current_com[3])] similars += _track_one_community(this_seed_nodes, current_com[0], s_graph_current_granularity, score=good_community, t_quality=t_quality) #if interesting_node in this_seed_nodes: # print("similars found", similars) #If the duration of the com is long enough, keep the community if len(similars) >= t_persistance: similars = [ similars ] #for genericity, we want to be able to deal with non-continuous intervals inter_presence = tn.Intervals([ (sim[0][0], sim[-1][0] + current_granularity) for sim in similars ]) #check that a similar community has not already been found redundant = False for nodes, period, gran, score in persistant_coms: #if the communities are similar and one of them has at least half of its duration included in the other if similarity( this_seed_nodes, nodes ) > t_similarity and inter_presence.intersection( period).duration() > 0.5 * min( [inter_presence.duration(), period.duration()]): redundant = True break if not redundant: #persistant_coms[current_com]=(inter_presence,sum([x[1] for x in similars])) sum_quality = 0 for stable in similars: sum_quality += sum([1 - (x[1]) for x in stable]) persistant_coms.append((this_seed_nodes, inter_presence, current_granularity, sum_quality)) #keep in the list of seeds those that are in a different period or that are sufficiently different, compared with the current one seeds = [ c for c in seeds if not seed_contained_in_persistent_com( this_seed_nodes, c[2], c[0], inter_presence, similarity, t_similarity) ] print("------- granularity (gamma): ", current_granularity, " | ", "# good seeds: ", nb_good_seeds, "# persistent communities found (total): ", len(persistant_coms)) persistant_coms = sorted(persistant_coms, key=lambda x: x[3], reverse=True) return persistant_coms