-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathevaluate.py
executable file
·73 lines (67 loc) · 2.7 KB
/
evaluate.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
"""
@author: yohanna
@email: [email protected]
"""
import networkx as nx
import numpy as np
def count_accuracy(G_true, G,
G_und: nx.DiGraph = None) -> tuple:
'''
Performance evaluation matrics.
Compute FDR, TPR, and FPR for B, or optionally for CPDAG B + B_und.
Arguments:
G_true : ground truth graph
G : predicted graph
G_und : predicted undirected edges in CPDAG, asymmetric
Returns:
fdr: (reverse + false positive) / prediction positive
tpr: (true positive) / condition positive
fpr: (reverse + false positive) / condition negative
shd: undirected extra + undirected missing + reverse
nnz: prediction positive
'''
G_true = nx.DiGraph(G_true)
G = nx.DiGraph(G)
B_true = nx.to_numpy_array(G_true) != 0
B = nx.to_numpy_array(G) != 0
B_und = None if G_und is None else nx.to_numpy_array(G_und)
d = B.shape[0]
# linear index of nonzeros
if B_und is not None:
pred_und = np.flatnonzero(B_und)
pred = np.flatnonzero(B)
cond = np.flatnonzero(B_true)
cond_reversed = np.flatnonzero(B_true.T)
cond_skeleton = np.concatenate([cond, cond_reversed])
# true pos
true_pos = np.intersect1d(pred, cond, assume_unique=True)
if B_und is not None:
# treat undirected edge favorably
true_pos_und = np.intersect1d(pred_und, cond_skeleton, assume_unique=True)
true_pos = np.concatenate([true_pos, true_pos_und])
# false pos
false_pos = np.setdiff1d(pred, cond_skeleton, assume_unique=True)
if B_und is not None:
false_pos_und = np.setdiff1d(pred_und, cond_skeleton, assume_unique=True)
false_pos = np.concatenate([false_pos, false_pos_und])
# reverse
extra = np.setdiff1d(pred, cond, assume_unique=True)
reverse = np.intersect1d(extra, cond_reversed, assume_unique=True)
# compute ratio
pred_size = len(pred)
if B_und is not None:
pred_size += len(pred_und)
cond_neg_size = 0.5 * d * (d - 1) - len(cond)
fdr = float(len(reverse) + len(false_pos)) / max(pred_size, 1)
tpr = float(len(true_pos)) / max(len(cond), 1)
fpr = float(len(reverse) + len(false_pos)) / max(cond_neg_size, 1)
# structural hamming distance
B_lower = np.tril(B + B.T)
if B_und is not None:
B_lower += np.tril(B_und + B_und.T)
pred_lower = np.flatnonzero(B_lower)
cond_lower = np.flatnonzero(np.tril(B_true + B_true.T))
extra_lower = np.setdiff1d(pred_lower, cond_lower, assume_unique=True)
missing_lower = np.setdiff1d(cond_lower, pred_lower, assume_unique=True)
shd = len(extra_lower) + len(missing_lower) + len(reverse)
return fdr, tpr, fpr, shd, pred_size