class_docstr: Class for generating training and test data for link prediction in graphs.
  
  The class requires as input a graph (in networkx format) and a percentage as a function of the total number of edges
  in the given graph of the number of positive and negative edges to sample. For heterogeneous graphs, the caller
  can also specify the type of edge and an edge property to split on. In the latter case, only a date property
  can be used and it must be in the format ``dd/mm/yyyy``. A date to be used as a threshold value such that only
  edges that have date after the threshold must be given. This effects only the sampling of positive edges.
  
  Negative edges are sampled at random by (for 'global' method) selecting two nodes in the graph and
  then checking if these edges are connected or not. If not, the pair of nodes is considered a negative sample.
  Otherwise, it is discarded and the process repeats. Alternatively, negative edges are sampled (for 'local' method)
  using DFS search at a distance from the source node (selected at random from all nodes in the graph)
  sampled according to a given set of probabilities.
  
  Positive edges can be sampled so that when they are subsequently removed from the graph, the reduced graph is either
  guaranteed, or not guaranteed, to remain connected. In the former case, graph connectivity is maintained by first
  calculating the minimum spanning tree. The edges that belong to the minimum spanning tree are protected from
  removal, and therefore cannot be sampled for the training set. The edges that do not belong to the minimum spanning
  tree are then sampled uniformly at random, until the required number of positive edges have been sampled for the
  training set. In the latter case, when connectedness of the reduced graph is not guaranteed, positive edges are
  sampled uniformly at random from all the edges in the graph, regardless of whether they belong to the spanning tree
  (which is not calculated in this case).
  
  Args:
      g (StellarGraph or networkx object): The graph to sample edges from.
      g_master (StellarGraph or networkx object): The graph representing the original dataset and a superset of the
          graph g. If it is not None, then when positive and negative edges are sampled, care is taken to make sure
          that a true positive edge is not sampled as a negative edge.
class_docstr_tok: ["Class","for","generating","training","and","test","data","for","link","prediction","in","graphs",".","The","class","requires","as","input","a","graph","(","in","networkx","format",")","and","a","percentage","as","a","function","of","the","total","number","of","edges","in","the","given","graph","of","the","number","of","positive","and","negative","edges","to","sample",".","For","heterogeneous","graphs",",","the","caller","can","also","specify","the","type","of","edge","and","an","edge","property","to","split","on",".","In","the","latter","case",",","only","a","date","property","can","be","used","and","it","must","be","in","the","format","``","dd\/mm\/yyyy","``",".","A","date","to","be","used","as","a","threshold","value","such","that","only","edges","that","have","date","after","the","threshold","must","be","given",".","This","effects","only","the","sampling","of","positive","edges",".","Negative","edges","are","sampled","at","random","by","(","for","'global","'","method",")","selecting","two","nodes","in","the","graph","and","then","checking","if","these","edges","are","connected","or","not",".","If","not",",","the","pair","of","nodes","is","considered","a","negative","sample",".","Otherwise",",","it","is","discarded","and","the","process","repeats",".","Alternatively",",","negative","edges","are","sampled","(","for","'local","'","method",")","using","DFS","search","at","a","distance","from","the","source","node","(","selected","at","random","from","all","nodes","in","the","graph",")","sampled","according","to","a","given","set","of","probabilities",".","Positive","edges","can","be","sampled","so","that","when","they","are","subsequently","removed","from","the","graph",",","the","reduced","graph","is","either","guaranteed",",","or","not","guaranteed",",","to","remain","connected",".","In","the","former","case",",","graph","connectivity","is","maintained","by","first","calculating","the","minimum","spanning","tree",".","The","edges","that","belong","to","the","minimum","spanning","tree","are","protected","from","removal",",","and","therefore","can","not","be","sampled","for","the","training","set",".","The","edges","that","do","not","belong","to","the","minimum","spanning","tree","are","then","sampled","uniformly","at","random",",","until","the","required","number","of","positive","edges","have","been","sampled","for","the","training","set",".","In","the","latter","case",",","when","connectedness","of","the","reduced","graph","is","not","guaranteed",",","positive","edges","are","sampled","uniformly","at","random","from","all","the","edges","in","the","graph",",","regardless","of","whether","they","belong","to","the","spanning","tree","(","which","is","not","calculated","in","this","case",")",".","Args",":","g","(","StellarGraph","or","networkx","object",")",":","The","graph","to","sample","edges","from",".","g_master","(","StellarGraph","or","networkx","object",")",":","The","graph","representing","the","original","dataset","and","a","superset","of","the","graph","g.","If","it","is","not","None",",","then","when","positive","and","negative","edges","are","sampled",",","care","is","taken","to","make","sure","that","a","true","positive","edge","is","not","sampled","as","a","negative","edge","."]
docstr: This method produces a list of edges that don't exist in graph self.g (negative examples). The number of
  negative edges produced is equal to the number of edges in the graph times p (that should be in the range (0,1]
  or limited to maximum limit_samples if the latter is not None. The negative samples are between node types
  as inferred from the edge type of the positive examples previously removed from the graph and given in
  edges_positive.
  
  This method uses depth-first search to efficiently (memory-wise) sample negative edges based on the local
  neighbourhood of randomly (uniformly) sampled source nodes at distances defined by the probabilities in probs.
  The source graph is not modified.
  
  Args:
      p (float): Factor that multiplies the number of edges in the graph and determines the number of negative
      edges to be sampled.
      probs (list): Probability distribution for the distance between source and target nodes.
      edges_positive (list): The positive edge examples that have previously been removed from the graph
      edge_label (str): The edge type to sample negative examples of
      limit_samples (int, optional): It limits the maximum number of samples to the given number, if not None
  
  Returns:
      (list) A list of 2-tuples that are pairs of node IDs that don't have an edge between them in the graph.
docstr_tok: ["This","method","produces","a","list","of","edges","that","do","n't","exist","in","graph","self.g","(","negative","examples",")",".","The","number","of","negative","edges","produced","is","equal","to","the","number","of","edges","in","the","graph","times","p","(","that","should","be","in","the","range","(","0,1","]","or","limited","to","maximum","limit_samples","if","the","latter","is","not","None",".","The","negative","samples","are","between","node","types","as","inferred","from","the","edge","type","of","the","positive","examples","previously","removed","from","the","graph","and","given","in","edges_positive",".","This","method","uses","depth-first","search","to","efficiently","(","memory-wise",")","sample","negative","edges","based","on","the","local","neighbourhood","of","randomly","(","uniformly",")","sampled","source","nodes","at","distances","defined","by","the","probabilities","in","probs",".","The","source","graph","is","not","modified",".","Args",":","p","(","float",")",":","Factor","that","multiplies","the","number","of","edges","in","the","graph","and","determines","the","number","of","negative","edges","to","be","sampled",".","probs","(","list",")",":","Probability","distribution","for","the","distance","between","source","and","target","nodes",".","edges_positive","(","list",")",":","The","positive","edge","examples","that","have","previously","been","removed","from","the","graph","edge_label","(","str",")",":","The","edge","type","to","sample","negative","examples","of","limit_samples","(","int",",","optional",")",":","It","limits","the","maximum","number","of","samples","to","the","given","number",",","if","not","None","Returns",":","(","list",")","A","list","of","2-tuples","that","are","pairs","of","node","IDs","that","do","n't","have","an","edge","between","them","in","the","graph","."]
code: def _sample_negative_examples_by_edge_type_local_dfs(
      self,
      p=0.5,
      probs=None,
      edges_positive=None,
      edge_label=None,
      limit_samples=None,
  ):
      """
      This method produces a list of edges that don't exist in graph self.g (negative examples). The number of
      negative edges produced is equal to the number of edges in the graph times p (that should be in the range (0,1]
      or limited to maximum limit_samples if the latter is not None. The negative samples are between node types
      as inferred from the edge type of the positive examples previously removed from the graph and given in
      edges_positive.
  
      This method uses depth-first search to efficiently (memory-wise) sample negative edges based on the local
      neighbourhood of randomly (uniformly) sampled source nodes at distances defined by the probabilities in probs.
      The source graph is not modified.
  
      Args:
          p (float): Factor that multiplies the number of edges in the graph and determines the number of negative
          edges to be sampled.
          probs (list): Probability distribution for the distance between source and target nodes.
          edges_positive (list): The positive edge examples that have previously been removed from the graph
          edge_label (str): The edge type to sample negative examples of
          limit_samples (int, optional): It limits the maximum number of samples to the given number, if not None
  
      Returns:
          (list) A list of 2-tuples that are pairs of node IDs that don't have an edge between them in the graph.
      """
      if probs is None:
          probs = [0.0, 0.25, 0.50, 0.25]
          warnings.warn(
              "Using default sampling probabilities up to 3 hops from source node with values {}".format(
                  probs
              )
          )
  
      if not isclose(sum(probs), 1.0):
          raise ValueError("Sampling probabilities do not sum to 1")
  
      self.negative_edge_node_distances = []
      n = len(probs)
  
      # determine the number of edges in the graph that have edge_label type
      # Multiply this number by p to determine the number of positive edge examples to sample
      all_edges = self._get_edges(edge_label=edge_label)
      num_edges_total = len(all_edges)
      print(
          "Network has {} edges of type {}".format(
              num_edges_total, edge_label
          )
      )
      #
      num_edges_to_sample = int(num_edges_total * p)
  
      if limit_samples is not None:
          if num_edges_to_sample > limit_samples:
              num_edges_to_sample = limit_samples
  
      edge_source_target_node_types = (
          self._get_edge_source_and_target_node_types(
              edges=edges_positive
          )
      )
  
      if self.g_master is None:
          edges = self.g.edges()
      else:
          edges = self.g_master.edges()
  
      # to speed up lookup of edges in edges list, create a set the values stored are the concatenation of
      # the source and target node ids.
      edges_set = set(edges)
      edges_set.update({(e[1], e[0]) for e in edges})
      sampled_edges_set = set()
  
      start_nodes = list(self.g.nodes(data=True))
      nodes_dict = {node[0]: node[1]["label"] for node in start_nodes}
  
      count = 0
      sampled_edges = []
  
      num_iter = (
          int(np.ceil(num_edges_to_sample / (1.0 * len(start_nodes))))
          + 1
      )
  
      for _ in np.arange(0, num_iter):
          self._random.shuffle(start_nodes)
          # sample the distance to the target node using probs
          target_node_distances = (
              self._random.choice(n, len(start_nodes), p=probs) + 1
          )
          for u, d in zip(start_nodes, target_node_distances):
              # perform DFS search up to d distance from the start node u.
              visited = {
                  node[0]: False for node in start_nodes
              }  # for marking already visited nodes
              nodes_stack = list()
              # start at node u
              nodes_stack.append((u[0], 0))  # tuple is (node, depth)
              while len(nodes_stack) > 0:
                  next_node = nodes_stack.pop()
                  v = next_node[0]  # retrieve node id
                  dv = next_node[1]  # retrieve node distance from u
                  if not visited[v]:
                      visited[v] = True
                      # Check if this nodes is at depth d; if it is, then this could be selected as the
                      # target node for a negative edge sample. Otherwise add its neighbours to the stack, only
                      # if the depth is less than the search depth d.
                      if dv == d:
                          u_v_edge_type = (
                              nodes_dict[u[0]],
                              nodes_dict[v],
                          )
                          # if no edge between u and next_node[0] then this is the sample, so record and stop
                          # searching
                          # Note: The if statement below is very expensive to evaluate because it need to checks
                          # the membership of an element in a number of lists that can grow very large for large
                          # graphs and number examples to sample. Later, we should have a closer look at how we can
                          # speed this up.
                          if (
                              (
                                  u_v_edge_type
                                  in edge_source_target_node_types
                              )
                              and (u[0] != v)
                              and ((u[0], v) not in edges_set)
                              and ((u[0], v) not in sampled_edges_set)
                          ):
                              sampled_edges.append(
                                  (u[0], v, 0)
                              )  # the last entry is the class label
                              sampled_edges_set.add((u[0], v))
                              sampled_edges_set.add((v, u[0]))
                              count += 1
                              self.negative_edge_node_distances.append(
                                  d
                              )
                          break
                      elif dv < d:
                          neighbours = list(nx.neighbors(self.g, v))
                          self._random.shuffle(neighbours)
                          neighbours = [(k, dv + 1) for k in neighbours]
                          nodes_stack.extend(neighbours)
  
              if count == num_edges_to_sample:
                  return sampled_edges
  
      if len(sampled_edges) != num_edges_to_sample:
          raise ValueError(
              "Unable to sample {} negative edges. Consider using smaller value for p.".format(
                  num_edges_to_sample
              )
          )
  
imports: import datetime
  import warnings
  import networkx
  import pandas
  import numpy
  from math import isclose
  from ..core import StellarGraph
  from ..globalvar import FEATURE_ATTR_NAME
label_desc: Use image node_id 10 for calling the EdgeSplitter obj's underlying member method code with example usage: obj._sample_negative_examples_by_edge_type_local_dfs(p, probs, edges_positive, edge_label, limit_samples) and returns: sampled_edges