Source code for teneto.networkmeasures.temporal_degree_centrality

"""Calculates temporal degree centrality"""

import numpy as np
from ..utils import process_input


[docs] def temporal_degree_centrality(tnet, axis=0, calc='overtime', communities=None, decay=0, ignorediagonal=True): r""" Temporal degree of network. The sum of all connections each node has through time (either per timepoint or over the entire temporal sequence). Parameters ----------- net : array, dict Temporal network input (graphlet or contact). Can have nettype: 'bu', 'bd', 'wu', 'wd' axis : int Dimension that is returned 0 or 1 (default 0). Note, only relevant for directed networks. i.e. if 0, node i has Aijt summed over j and t. and if 1, node j has Aijt summed over i and t. calc : str Can be following alternatives: 'overtime' : returns a 1 x node vector. Returns the degree/stregnth over all time points. 'pertime' : returns a node x time array. Returns the degree/strength per time point. 'module_degree_zscore' : returns the Z-scored within community degree centrality (communities argument required). This is done for each time-point i.e. 'pertime' returns static degree centrality per time-point. ignorediagonal: bool if True, diagonal is made to 0. communities : array (Nx1) Vector of community assignment. If this is given and calc='pertime', then the strength within and between each communities is returned. (Note, this is not technically degree centrality). decay : int if calc = 'pertime', then decay is possible where the centrality of the previous time point is carried over to the next time point but decays at a value of $e^decay$ such that $D_d(t+1) = e^{-decay}D_d(t) + D(t+1)$. If decay is 0 then the final D will equal D when calc='overtime', if decay = inf then this will equal calc='pertime'. Returns --------- D : array temporal degree centrality (nodal measure). Array is 1D ('overtime'), 2D ('pertime', 'module_degree_zscore'), or 3D ('pertime' + communities (non-nodal/community measures)). Notes ------ When the network is weighted, this could also be called "temporal strength" or "temporal strength centrality". This is a simple extension of the static definition. At times this has been defined slightly differently. Here we followed the definitions in [Degree-1]_ or [Degree-2]_. There are however many authors prior to this that have used temporal degree centrality. There are two basic versions of temporal degree centrality implemented: the average temporal degree centrality (``calc='overtime'``) and temporal degree centrality (``calc='pertime'``). When ``calc='pertime'``: .. math:: D_{it} = \sum_j A_{ijt} where A is the multi-layer connectivity matrix of the temporal network. This entails that :math:`D_{it}` is the sum of a node i's degree/strength at t. This has also been called the instantaneous degree centrality [Degree-2]_. When ``calc='overtime'``: .. math:: D_{i} = \sum_t\sum_j A_{ijt} i.e. :math:`D_{i}` is the sum of a node i's degree/strength over all time points. There are some additional options which can modify the estimate. One way is to add a decay term. This entails that ..math::`D_{it}`, uses some of the previous time-points estimate. An exponential decay is used here. .. math:: D_{it} = e^{-b} D_{i(t-1)} + \sum_j A_{ijt} where b is the deay parameter specified in the function. This, to my knowledge, was first introdueced by [Degree-2]_. References ----------- .. [Degree-1] Thompson, et al (2017). From static to temporal network theory: Applications to functional brain connectivity. Network Neuroscience, 1(2), 69-99. [`Link <https://www.mitpressjournals.org/doi/full/10.1162/netn_a_00011>`_] .. [Degree-2] Masuda, N., & Lambiotte, R. (2016). A Guidance to Temporal Networks. [`Link to book's publisher <https://www.worldscientific.com/doi/abs/10.1142/9781786341150_0001>`_] """ # Get input in right format tnet = process_input(tnet, ['C', 'G', 'TN'], 'TN') if axis == 1: fromax = 'j' toax = 'i' else: fromax = 'i' toax = 'j' if tnet.sparse and tnet.nettype[0] == 'b': tnet.network['weight'] = 1 # Diagonal is currently deleted. # if ignorediagonal: # tnet = set_diagonal(tnet, 0) # sum sum tnet if calc == 'pertime' and communities is None: # Return node,time if tnet.sparse: tdeg = np.zeros([tnet.netshape[0], tnet.netshape[1]]) df = tnet.network.groupby([fromax, 't']).sum().reset_index() tdeg[df[fromax], df['t']] = df['weight'] # If undirected, do reverse if tnet.nettype[1] == 'u': df = tnet.network.groupby([toax, 't']).sum().reset_index() tdeg[df[toax], df['t']] += df['weight'] else: tdeg = np.sum(tnet.network, axis=axis) elif calc == 'module_degree_zscore' and communities is None: raise ValueError( 'Communities must be specified when calculating module degree z-score.') elif calc != 'pertime' and communities is None: # Return node if tnet.sparse: tdeg = np.zeros([tnet.netshape[0]]) # Strength if weighted df = tnet.network.groupby([fromax])['weight'].sum().reset_index() tdeg[df[fromax]] += df['weight'] # If undirected, do reverse if tnet.nettype[1] == 'u': df = tnet.network.groupby([toax])['weight'].sum().reset_index() tdeg[df[toax]] += df['weight'] else: tdeg = np.sum(np.sum(tnet.network, axis=-1), axis=axis) elif calc == 'module_degree_zscore' and communities is not None: tdeg = np.zeros([tnet.netshape[0], tnet.netshape[1]]) # Need to make this fully sparse if tnet.sparse: network = tnet.df_to_array() else: network = tnet.network for t in range(tnet.netshape[1]): if len(communities.shape) == 2: coms = communities[:, t] else: coms = communities for c in np.unique(coms): k_i = np.sum(network[ :, coms == c, t][coms == c], axis=axis) tdeg[coms == c, t] = (k_i - np.mean(k_i)) / np.std(k_i) tdeg[np.isnan(tdeg) == 1] = 0 elif calc == 'pertime' and communities is not None: # neet to make this fully sparse if tnet.sparse: network = tnet.df_to_array() else: network = tnet.network tdeg_communities = np.zeros( [communities.max() + 1, communities.max() + 1, communities.shape[-1]]) if len(communities.shape) == 2: for t in range(len(communities[-1])): coms = communities[:, t] unique_communities = np.unique(coms) for s1 in unique_communities: for s2 in unique_communities: tdeg_communities[s1, s2, t] = np.sum( np.sum(network[coms == s1, :, t][:, coms == s2], axis=1), axis=0) else: unique_communities = np.unique(communities) tdeg_communities = [np.sum(np.sum(network[communities == s1][:, communities == s2], axis=1), axis=0) for s1 in unique_communities for s2 in unique_communities] tdeg = np.array(tdeg_communities) tdeg = np.reshape(tdeg, [len(np.unique(communities)), len( np.unique(communities)), tnet.netshape[-1]]) # Divide diagonal by 2 if undirected to correct for edges being present twice if tnet.nettype[1] == 'u': for s in range(tdeg.shape[0]): tdeg[s, s, :] = tdeg[s, s, :] / 2 else: raise ValueError("invalid calc argument") if decay > 0 and calc == 'pertime': # Reshape so that time is first dimensions tdeg = tdeg.transpose( np.hstack([len(tdeg.shape) - 1, np.arange(len(tdeg.shape) - 1)])) for n in range(1, tdeg.shape[0]): tdeg[n] = np.exp(0 - decay) * tdeg[n - 1] + tdeg[n] tdeg = tdeg.transpose(np.hstack([np.arange(1, len(tdeg.shape)), 0])) elif decay > 0: print('WARNING: decay cannot be applied unless calc=time, ignoring decay') return tdeg