def alter_weights(gdag: GaussDAG, prob_altered: float = None, num_altered: int = None, rand_weight_fn=unif_away_original): """ Return a copy of a GaussDAG with some of its arc weights randomly altered by `rand_weight_fn`. Parameters ---------- gdag: GaussDAG prob_altered: Probability each arc has its weight altered. num_altered: Number of arcs whose weights are altered. rand_weight_fn: Function to generate random weights, given the original weight. """ if num_altered is None and prob_altered is None: raise ValueError( "Must specify at least one of `percent_altered` or `num_altered`.") num_altered = num_altered if num_altered is not None else np.random.binomial( gdag.num_arcs, prob_altered) altered_arcs = random.sample(list(gdag.arcs), num_altered) new_gdag = gdag.copy() weights = gdag.arc_weights for i, j in altered_arcs: new_gdag.set_arc_weight(i, j, rand_weight_fn(weights[(i, j)])) return new_gdag
def to_gauss_dag(self, perm): """ Return a GaussDAG with the same mean and covariance as this GGM, and is a minimal IMAP of this GGM consistent with the node ordering `perm`. Parameters ---------- perm: The desired permutation, or total order, of the nodes in the result. Returns ------- Examples -------- TODO """ from causaldag import DAG, GaussDAG d = DAG(nodes=self.nodes) ixs = list( itr.chain.from_iterable( ((f, s) for f in range(s)) for s in range(len(perm)))) for i, j in ixs: pi_i, pi_j = perm[i], perm[j] if not np.isclose( self.partial_correlation(pi_i, pi_j, d.markov_blanket(pi_i)), 0): d.add_arc(pi_i, pi_j, unsafe=True) arcs = dict() means = [] Sigma = self.covariance variances = [] for i in perm: ps = list(d.parents_of(i)) # === LINEAR REGRESSION TO FIND EDGE WEIGHTS S_xx = Sigma[np.ix_(ps, ps)] S_xy = Sigma[ps, i] coeffs = inv(S_xx) @ S_xy # === COMPUTE MEAN AND VARIANCE mean = self.means[i] - self.means[ps] @ coeffs.T variance = Sigma[i, i] - Sigma[i, ps] @ coeffs for p, coeff in zip(ps, coeffs): print(p, i) arcs[(p, i)] = coeff means.append(mean) variances.append(variance) return GaussDAG(list(range(self.num_nodes)), arcs, means=means, variances=variances)
def rand_weights(dag, rand_weight_fn=unif_away_zero) -> GaussDAG: """ Generate a GaussDAG from a DAG, with random edge weights independently drawn from `rand_weight_fn`. Parameters ---------- dag: DAG rand_weight_fn: Function to generate random weights. Examples -------- >>> d = cd.DAG(arcs={(1, 2), (2, 3)}) >>> g = cd.rand.rand_weights(d) """ weights = rand_weight_fn(size=len(dag.arcs)) return GaussDAG(nodes=list(range(len(dag.nodes))), arcs=dict(zip(dag.arcs, weights)))
def alter_weights(gdag: GaussDAG, prob_altered: float = None, num_altered: int = None, prob_added: float = None, num_added: int = None, prob_removed: float = None, num_removed: int = None, rand_weight_fn=unif_away_zero, rand_change_fn=unif_away_original): """ Return a copy of a GaussDAG with some of its arc weights randomly altered by `rand_weight_fn`. Parameters ---------- gdag: GaussDAG prob_altered: Probability each arc has its weight altered. num_altered: Number of arcs whose weights are altered. prob_added: Probability that each missing arc is added. num_added: Number of missing arcs added. prob_removed: Probability that each arc is removed. num_removed: Number of arcs removed. rand_weight_fn: Function that returns a random weight for each new edge. rand_change_fn: Function that takes the current weight of an edge and returns the new weight. """ if num_altered is None and prob_altered is None: raise ValueError( "Must specify at least one of `prob_altered` or `num_altered`.") if num_added is None and prob_added is None: raise ValueError( "Must specify at least one of `prob_added` or `num_added`.") if num_removed is None and prob_removed is None: raise ValueError( "Must specify at least one of `prob_removed` or `num_removed`.") if num_altered + num_removed > gdag.num_arcs: raise ValueError( f"Tried altering {num_altered} arcs and removing {num_removed} arcs, but there are only {gdag.num_arcs} arcs in this DAG." ) num_missing_arcs = comb(gdag.nnodes, 2) - gdag.num_arcs if num_added > num_missing_arcs: raise ValueError( f"Tried adding {num_added} arcs but there are only {num_missing_arcs} arcs missing from the DAG." ) # GET NUMBER ADDED/CHANGED/REMOVED num_altered = num_altered if num_altered is not None else np.random.binomial( gdag.num_arcs, prob_altered) num_removed = num_removed if num_removed is not None else np.random.binomial( gdag.num_arcs, prob_removed) num_removed = min(num_removed, gdag.num_arcs - num_altered) num_added = num_added if num_added is not None else np.random.binomial( num_missing_arcs, prob_added) # GET ACTUAL ARCS THAT ARE ADDED/CHANGED/REMOVED altered_arcs = random.sample(list(gdag.arcs), num_altered) removed_arcs = random.sample(list(gdag.arcs - set(altered_arcs)), num_removed) valid_arcs_to_add = set(itr.combinations(gdag.topological_sort(), 2)) - gdag.arcs added_arcs = random.sample(list(valid_arcs_to_add), num_added) # CREATE NEW DAG new_gdag = gdag.copy() weights = gdag.arc_weights for i, j in altered_arcs: new_gdag.set_arc_weight(i, j, rand_change_fn(weights[(i, j)])) for i, j in removed_arcs: new_gdag.remove_arc(i, j) new_weights = rand_weight_fn(size=num_added) for (i, j), val in zip(added_arcs, new_weights): new_gdag.set_arc_weight(i, j, val) return new_gdag
mean = self.means[i] - self.means[ps] @ coeffs.T variance = Sigma[i, i] - Sigma[i, ps] @ coeffs for p, coeff in zip(ps, coeffs): print(p, i) arcs[(p, i)] = coeff means.append(mean) variances.append(variance) return GaussDAG(list(range(self.num_nodes)), arcs, means=means, variances=variances) if __name__ == '__main__': P = np.eye(3) P[0, 1] = P[1, 0] = .1 P[2, 1] = P[1, 2] = .1 g = GGM(P) samples = g.sample(1000) cov_sample = np.cov(samples, rowvar=False) from causaldag import GaussDAG A = np.array([[0, .1, 0], [0, 0, .1], [0, 0, 0]]) gdag = GaussDAG.from_amat(A) ggm = GGM.from_covariance(gdag.covariance) gdag2 = ggm.to_gauss_dag([0, 1, 2]) print(gdag.weight_mat) print(gdag2.weight_mat)