Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/algorithms/time_dependent.py: 32%

22 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-10-20 07:00 +0000

1"""Time dependent algorithms.""" 

2 

3import networkx as nx 

4from networkx.utils import not_implemented_for 

5 

6__all__ = ["cd_index"] 

7 

8 

9@not_implemented_for("undirected") 

10@not_implemented_for("multigraph") 

11@nx._dispatch(node_attrs={"time": None, "weight": 1}) 

12def cd_index(G, node, time_delta, *, time="time", weight=None): 

13 r"""Compute the CD index for `node` within the graph `G`. 

14 

15 Calculates the CD index for the given node of the graph, 

16 considering only its predecessors who have the `time` attribute 

17 smaller than or equal to the `time` attribute of the `node` 

18 plus `time_delta`. 

19 

20 Parameters 

21 ---------- 

22 G : graph 

23 A directed networkx graph whose nodes have `time` attributes and optionally 

24 `weight` attributes (if a weight is not given, it is considered 1). 

25 node : node 

26 The node for which the CD index is calculated. 

27 time_delta : numeric or timedelta 

28 Amount of time after the `time` attribute of the `node`. The value of 

29 `time_delta` must support comparison with the `time` node attribute. For 

30 example, if the `time` attribute of the nodes are `datetime.datetime` 

31 objects, then `time_delta` should be a `datetime.timedelta` object. 

32 time : string (Optional, default is "time") 

33 The name of the node attribute that will be used for the calculations. 

34 weight : string (Optional, default is None) 

35 The name of the node attribute used as weight. 

36 

37 Returns 

38 ------- 

39 float 

40 The CD index calculated for the node `node` within the graph `G`. 

41 

42 Raises 

43 ------ 

44 NetworkXError 

45 If not all nodes have a `time` attribute or 

46 `time_delta` and `time` attribute types are not compatible or 

47 `n` equals 0. 

48 

49 NetworkXNotImplemented 

50 If `G` is a non-directed graph or a multigraph. 

51 

52 Examples 

53 -------- 

54 >>> from datetime import datetime, timedelta 

55 >>> G = nx.DiGraph() 

56 >>> nodes = { 

57 ... 1: {"time": datetime(2015, 1, 1)}, 

58 ... 2: {"time": datetime(2012, 1, 1), 'weight': 4}, 

59 ... 3: {"time": datetime(2010, 1, 1)}, 

60 ... 4: {"time": datetime(2008, 1, 1)}, 

61 ... 5: {"time": datetime(2014, 1, 1)} 

62 ... } 

63 >>> G.add_nodes_from([(n, nodes[n]) for n in nodes]) 

64 >>> edges = [(1, 3), (1, 4), (2, 3), (3, 4), (3, 5)] 

65 >>> G.add_edges_from(edges) 

66 >>> delta = timedelta(days=5 * 365) 

67 >>> nx.cd_index(G, 3, time_delta=delta, time="time") 

68 0.5 

69 >>> nx.cd_index(G, 3, time_delta=delta, time="time", weight="weight") 

70 0.12 

71 

72 Integers can also be used for the time values: 

73 >>> node_times = {1: 2015, 2: 2012, 3: 2010, 4: 2008, 5: 2014} 

74 >>> nx.set_node_attributes(G, node_times, "new_time") 

75 >>> nx.cd_index(G, 3, time_delta=4, time="new_time") 

76 0.5 

77 >>> nx.cd_index(G, 3, time_delta=4, time="new_time", weight="weight") 

78 0.12 

79 

80 Notes 

81 ----- 

82 This method implements the algorithm for calculating the CD index, 

83 as described in the paper by Funk and Owen-Smith [1]_. The CD index 

84 is used in order to check how consolidating or destabilizing a patent 

85 is, hence the nodes of the graph represent patents and the edges show 

86 the citations between these patents. The mathematical model is given 

87 below: 

88 

89 .. math:: 

90 CD_{t}=\frac{1}{n_{t}}\sum_{i=1}^{n}\frac{-2f_{it}b_{it}+f_{it}}{w_{it}}, 

91 

92 where `f_{it}` equals 1 if `i` cites the focal patent else 0, `b_{it}` equals 

93 1 if `i` cites any of the focal patents successors else 0, `n_{t}` is the number 

94 of forward citations in `i` and `w_{it}` is a matrix of weight for patent `i` 

95 at time `t`. 

96 

97 The `datetime.timedelta` package can lead to off-by-one issues when converting 

98 from years to days. In the example above `timedelta(days=5 * 365)` looks like 

99 5 years, but it isn't because of leap year days. So it gives the same result 

100 as `timedelta(days=4 * 365)`. But using `timedelta(days=5 * 365 + 1)` gives 

101 a 5 year delta **for this choice of years** but may not if the 5 year gap has 

102 more than 1 leap year. To avoid these issues, use integers to represent years, 

103 or be very careful when you convert units of time. 

104 

105 References 

106 ---------- 

107 .. [1] Funk, Russell J., and Jason Owen-Smith. 

108 "A dynamic network measure of technological change." 

109 Management science 63, no. 3 (2017): 791-817. 

110 http://russellfunk.org/cdindex/static/papers/funk_ms_2017.pdf 

111 

112 """ 

113 if not all(time in G.nodes[n] for n in G): 

114 raise nx.NetworkXError("Not all nodes have a 'time' attribute.") 

115 

116 try: 

117 # get target_date 

118 target_date = G.nodes[node][time] + time_delta 

119 # keep the predecessors that existed before the target date 

120 pred = {i for i in G.pred[node] if G.nodes[i][time] <= target_date} 

121 except: 

122 raise nx.NetworkXError( 

123 "Addition and comparison are not supported between 'time_delta' " 

124 "and 'time' types." 

125 ) 

126 

127 # -1 if any edge between node's predecessors and node's successors, else 1 

128 b = [-1 if any(j in G[i] for j in G[node]) else 1 for i in pred] 

129 

130 # n is size of the union of the focal node's predecessors and its successors' predecessors 

131 n = len(pred.union(*(G.pred[s].keys() - {node} for s in G[node]))) 

132 if n == 0: 

133 raise nx.NetworkXError("The cd index cannot be defined.") 

134 

135 # calculate cd index 

136 if weight is None: 

137 return round(sum(bi for bi in b) / n, 2) 

138 else: 

139 # If a node has the specified weight attribute, its weight is used in the calculation 

140 # otherwise, a weight of 1 is assumed for that node 

141 weights = [G.nodes[i].get(weight, 1) for i in pred] 

142 return round(sum(bi / wt for bi, wt in zip(b, weights)) / n, 2)