Source code for teneto.networkmeasures.volatility

"""
Function to calculate volatility
"""
import numpy as np
from ..utils import process_input,\
                    check_distance_funciton_input,\
                    get_distance_function


[docs] def volatility(tnet, distance_func='default', calc='overtime', communities=None, event_displacement=None): r""" Volatility of temporal networks. Volatility is the average distance between consecutive time points (difference is caclualted either globally or per edge). Parameters ---------- tnet : array or dict temporal network input (graphlet or contact). Nettype: 'bu','bd','wu','wd' D : str Distance function. Following options available: 'default', 'hamming', 'euclidean'. (Default implies hamming for binary networks, euclidean for weighted). calc : str Version of volaitility to caclulate. Possibilities include: 'overtime' - (default): the average distance of all nodes for each consecutive time point). 'edge' - average distance between consecutive time points for each edge). Takes considerably longer 'node' - (i.e. returns the average per node output when calculating volatility per 'edge'). 'pertime' - returns volatility per time point 'communities' - returns volatility per communitieswork id (see communities). Also is returned per time-point and this may be changed in the future (additional options are then required) 'event_displacement' - calculates the volatility from a specified point. Returns time-series. communities : array Array of indicies for community (eiter (node) or (node,time) dimensions). event_displacement : int if calc = event_displacement specify the temporal index. All other time-points are calculated in relation to this time point. Notes ----- Volatility calculates the difference between network snapshots. .. math:: V_t = D(G_t,G_{t+1}) Where D is some distance function (e.g. Hamming distance for binary matrices). V can be calculated for the entire network (global), but can also be calculated for individual edges, nodes or given a community vector. Index of communities are returned "as is" with a shape of: (max(communities)+1, max(communities)+1). So if the indexes used are [1,2,3,5], V.shape==(6,6). The returning V[1,2] will correspond indexes 1 and 2. And missing index (e.g. here 0 and 4 will be NANs in rows and columns). If this behaviour is unwanted, call clean_communitiesdexes first. Examples -------- Import everything needed. >>> import teneto >>> import numpy >>> np.random.seed(1) >>> tnet = teneto.TemporalNetwork(nettype='bu') Here we generate a binary network where edges have a 0.5 change of going "on", and once on a 0.2 change to go "off" >>> tnet.generatenetwork('rand_binomial', size=(3,10), prob=(0.5,0.2)) Calculate the volatility >>> tnet.calc_networkmeasure('volatility', distance_func='hamming') 0.5555555555555556 If we change the probabilities to instead be certain edges disapeared the time-point after the appeared: >>> tnet.generatenetwork('rand_binomial', size=(3,10), prob=(0.5,1)) This will make a more volatile network >>> tnet.calc_networkmeasure('volatility', distance_func='hamming') 0.1111111111111111 We can calculate the volatility per time instead >>> vol_time = tnet.calc_networkmeasure('volatility', calc='pertime', distance_func='hamming') >>> len(vol_time) 9 >>> vol_time[0] 0.3333333333333333 Or per node: >>> vol_node = tnet.calc_networkmeasure('volatility', calc='node', distance_func='hamming') >>> vol_node array([0.07407407, 0.07407407, 0.07407407]) Here we see the volatility for each node was the same. It is also possible to pass a community vector. The function will return volatility both within and between each community. So the following has two communities: >>> vol_com = tnet.calc_networkmeasure('volatility', calc='communities', communities=[0,1,1], distance_func='hamming') >>> vol_com.shape (2, 2, 9) >>> vol_com[:,:,0] array([[nan, 0.5], [0.5, 0. ]]) And we see that, at time-point 0, there is some volatility between community 0 and 1. Further, there is no volatility within community 1. The reason for nan appearing is due to there only being 1 node in community 0. Output ------ vol : array """ # Get input (C or G) tnet, netinfo = process_input(tnet, ['C', 'G', 'TN']) distance_func = check_distance_funciton_input( distance_func, netinfo) if not isinstance(distance_func, str): raise ValueError('Distance metric must be a string') # If not directional, only calc on the uppertriangle if netinfo['nettype'][1] == 'd': ind = np.triu_indices(tnet.shape[0], k=-tnet.shape[0]) elif netinfo['nettype'][1] == 'u': ind = np.triu_indices(tnet.shape[0], k=1) if calc == 'communities': # Make sure communities is np array for indexing later on. communities = np.array(communities) if len(communities) != netinfo['netshape'][0]: raise ValueError( 'When processing per network, communities vector must equal the number of nodes') if communities.min() < 0: raise ValueError( 'Communitiy assignments must be positive integers') # Get chosen distance metric fucntion distance_func = get_distance_function(distance_func) if calc == 'overtime': vol = np.mean([distance_func(tnet[ind[0], ind[1], t], tnet[ind[0], ind[1], t + 1]) for t in range(0, tnet.shape[-1] - 1)]) elif calc == 'pertime': vol = [distance_func(tnet[ind[0], ind[1], t], tnet[ind[0], ind[1], t + 1]) for t in range(0, tnet.shape[-1] - 1)] elif calc == 'event_displacement': vol = [distance_func(tnet[ind[0], ind[1], event_displacement], tnet[ind[0], ind[1], t]) for t in range(0, tnet.shape[-1])] # This takes quite a bit of time to loop through. When calculating per edge/node. elif calc == 'edge' or calc == 'node': vol = np.zeros([tnet.shape[0], tnet.shape[1]]) for i in ind[0]: for j in ind[1]: vol[i, j] = np.mean([distance_func( tnet[i, j, t], tnet[i, j, t + 1]) for t in range(0, tnet.shape[-1] - 1)]) if netinfo['nettype'][1] == 'u': vol = vol + np.transpose(vol) if calc == 'node': vol = np.mean(vol, axis=1) elif calc == 'communities': net_id = set(communities) vol = np.zeros([max(net_id) + 1, max(net_id) + 1, netinfo['netshape'][-1] - 1]) for net1 in net_id: for net2 in net_id: if net1 != net2: for tind in range(0, tnet.shape[-1] - 1): com1 = tnet[communities == net1][:, communities == net2, tind].flatten() com2 = tnet[communities == net1][:, communities == net2, tind + 1].flatten() vol[net1, net2, tind] = distance_func(com1, com2) else: nettmp = tnet[communities == net1][:, communities == net2, :] triu = np.triu_indices(nettmp.shape[0], k=1) nettmp = nettmp[triu[0], triu[1], :] vol[net1, net2, :] = [distance_func(nettmp[:, t].flatten( ), nettmp[:, t + 1].flatten()) for t in range(0, tnet.shape[-1] - 1)] elif calc == 'withincommunities': withi = np.array([[ind[0][n], ind[1][n]] for n in range( 0, len(ind[0])) if communities[ind[0][n]] == communities[ind[1][n]]]) vol = [distance_func(tnet[withi[:, 0], withi[:, 1], t], tnet[withi[:, 0], withi[:, 1], t + 1]) for t in range(0, tnet.shape[-1] - 1)] elif calc == 'betweencommunities': beti = np.array([[ind[0][n], ind[1][n]] for n in range( 0, len(ind[0])) if communities[ind[0][n]] != communities[ind[1][n]]]) vol = [distance_func(tnet[beti[:, 0], beti[:, 1], t], tnet[beti[:, 0], beti[:, 1], t + 1]) for t in range(0, tnet.shape[-1] - 1)] return vol