Skip to content

Commit 17e202f

Browse files
rossbarMridulSdschult
authored andcommitted
API: Rm default value from time_delta for cd_index. (networkx#6953)
* API: Rm default value from time_delta for cd_index. * Update exception message. Co-authored-by: Mridul Seth <[email protected]> * TST: modify pytest.raises regex. * Second cd_index example with integer times * typo * warn about leap years --------- Co-authored-by: Mridul Seth <[email protected]> Co-authored-by: Dan Schult <[email protected]>
1 parent d9ff82f commit 17e202f

File tree

2 files changed

+44
-20
lines changed

2 files changed

+44
-20
lines changed

networkx/algorithms/tests/test_time_dependent.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
"""Unit testing for time dependent algorithms."""
22

3-
from datetime import datetime
3+
from datetime import datetime, timedelta
44

55
import pytest
66

77
import networkx as nx
88

9+
_delta = timedelta(days=5 * 365)
10+
911

1012
class TestCdIndex:
1113
"""Unit testing for the cd index function."""
@@ -43,7 +45,7 @@ def test_common_graph(self):
4345

4446
nx.set_node_attributes(G, node_attrs)
4547

46-
assert nx.cd_index(G, 4) == 0.17
48+
assert nx.cd_index(G, 4, time_delta=_delta) == 0.17
4749

4850
def test_common_graph_with_given_attributes(self):
4951
G = nx.DiGraph()
@@ -78,7 +80,7 @@ def test_common_graph_with_given_attributes(self):
7880

7981
nx.set_node_attributes(G, node_attrs)
8082

81-
assert nx.cd_index(G, 4, time="date") == 0.17
83+
assert nx.cd_index(G, 4, time_delta=_delta, time="date") == 0.17
8284

8385
def test_common_graph_with_int_attributes(self):
8486
G = nx.DiGraph()
@@ -182,7 +184,7 @@ def test_common_graph_with_weights(self):
182184
}
183185

184186
nx.set_node_attributes(G, node_attrs)
185-
assert nx.cd_index(G, 4, weight="weight") == 0.04
187+
assert nx.cd_index(G, 4, time_delta=_delta, weight="weight") == 0.04
186188

187189
def test_node_with_no_predecessors(self):
188190
G = nx.DiGraph()
@@ -215,7 +217,7 @@ def test_node_with_no_predecessors(self):
215217
}
216218

217219
nx.set_node_attributes(G, node_attrs)
218-
assert nx.cd_index(G, 4) == 0.0
220+
assert nx.cd_index(G, 4, time_delta=_delta) == 0.0
219221

220222
def test_node_with_no_successors(self):
221223
G = nx.DiGraph()
@@ -248,7 +250,7 @@ def test_node_with_no_successors(self):
248250
}
249251

250252
nx.set_node_attributes(G, node_attrs)
251-
assert nx.cd_index(G, 4) == 1.0
253+
assert nx.cd_index(G, 4, time_delta=_delta) == 1.0
252254

253255
def test_n_equals_zero(self):
254256
G = nx.DiGraph()
@@ -282,7 +284,7 @@ def test_n_equals_zero(self):
282284
with pytest.raises(
283285
nx.NetworkXError, match="The cd index cannot be defined."
284286
) as ve:
285-
nx.cd_index(G, 4)
287+
nx.cd_index(G, 4, time_delta=_delta)
286288

287289
def test_time_timedelta_compatibility(self):
288290
G = nx.DiGraph()
@@ -315,9 +317,9 @@ def test_time_timedelta_compatibility(self):
315317

316318
with pytest.raises(
317319
nx.NetworkXError,
318-
match="Addition and comparison are not supported between 'time_delta' and 'time' types, default time_delta = datetime.timedelta",
320+
match="Addition and comparison are not supported between",
319321
) as ve:
320-
nx.cd_index(G, 4)
322+
nx.cd_index(G, 4, time_delta=_delta)
321323

322324
def test_node_with_no_time(self):
323325
G = nx.DiGraph()
@@ -353,7 +355,7 @@ def test_node_with_no_time(self):
353355
with pytest.raises(
354356
nx.NetworkXError, match="Not all nodes have a 'time' attribute."
355357
) as ve:
356-
nx.cd_index(G, 4)
358+
nx.cd_index(G, 4, time_delta=_delta)
357359

358360
def test_maximally_consolidating(self):
359361
G = nx.DiGraph()
@@ -393,7 +395,7 @@ def test_maximally_consolidating(self):
393395

394396
nx.set_node_attributes(G, node_attrs)
395397

396-
assert nx.cd_index(G, 5) == -1
398+
assert nx.cd_index(G, 5, time_delta=_delta) == -1
397399

398400
def test_maximally_destabilizing(self):
399401
G = nx.DiGraph()
@@ -426,4 +428,4 @@ def test_maximally_destabilizing(self):
426428

427429
nx.set_node_attributes(G, node_attrs)
428430

429-
assert nx.cd_index(G, 5) == 1
431+
assert nx.cd_index(G, 5, time_delta=_delta) == 1

networkx/algorithms/time_dependent.py

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
"""Time dependent algorithms."""
22

3-
from datetime import datetime, timedelta
4-
53
import networkx as nx
64
from networkx.utils import not_implemented_for
75

@@ -11,7 +9,7 @@
119
@not_implemented_for("undirected")
1210
@not_implemented_for("multigraph")
1311
@nx._dispatch(node_attrs={"time": None, "weight": 1})
14-
def cd_index(G, node, *, time_delta=timedelta(days=5 * 365), time="time", weight=None):
12+
def cd_index(G, node, time_delta, *, time="time", weight=None):
1513
r"""Compute the CD index for `node` within the graph `G`.
1614
1715
Calculates the CD index for the given node of the graph,
@@ -26,12 +24,15 @@ def cd_index(G, node, *, time_delta=timedelta(days=5 * 365), time="time", weight
2624
`weight` attributes (if a weight is not given, it is considered 1).
2725
node : node
2826
The node for which the CD index is calculated.
29-
time_delta : timedelta, integer or float (Optional, default is timedelta(days=5*365))
30-
Amount of time after the `time` attribute of the `node`.
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.
3132
time : string (Optional, default is "time")
3233
The name of the node attribute that will be used for the calculations.
3334
weight : string (Optional, default is None)
34-
the name of the node attribute used as weight.
35+
The name of the node attribute used as weight.
3536
3637
Returns
3738
-------
@@ -50,6 +51,7 @@ def cd_index(G, node, *, time_delta=timedelta(days=5 * 365), time="time", weight
5051
5152
Examples
5253
--------
54+
>>> from datetime import datetime, timedelta
5355
>>> G = nx.DiGraph()
5456
>>> nodes = {
5557
... 1: {"time": datetime(2015, 1, 1)},
@@ -61,7 +63,19 @@ def cd_index(G, node, *, time_delta=timedelta(days=5 * 365), time="time", weight
6163
>>> G.add_nodes_from([(n, nodes[n]) for n in nodes])
6264
>>> edges = [(1, 3), (1, 4), (2, 3), (3, 4), (3, 5)]
6365
>>> G.add_edges_from(edges)
64-
>>> cd = nx.cd_index(G, 3, time="time", weight="weight")
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
6579
6680
Notes
6781
-----
@@ -80,6 +94,14 @@ def cd_index(G, node, *, time_delta=timedelta(days=5 * 365), time="time", weight
8094
of forward citations in `i` and `w_{it}` is a matrix of weight for patent `i`
8195
at time `t`.
8296
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+
83105
References
84106
----------
85107
.. [1] Funk, Russell J., and Jason Owen-Smith.
@@ -99,7 +121,7 @@ def cd_index(G, node, *, time_delta=timedelta(days=5 * 365), time="time", weight
99121
except:
100122
raise nx.NetworkXError(
101123
"Addition and comparison are not supported between 'time_delta' "
102-
"and 'time' types, default time_delta = datetime.timedelta."
124+
"and 'time' types."
103125
)
104126

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

0 commit comments

Comments
 (0)