Ejemplo n.º 1
0
def degree_sequence(network: Network, weight: Weight = None) -> np.array:
    """Calculates the degree sequence of a network.

    Parameters
    ----------

    network : Network

        The :py:class:`Network` object that contains the network

    weights : bool

        If True weighted degrees will be calculated

    Examples
    --------
    Generate a simple network

    >>> import pathpy as pp
    >>> net = pp.Network(directed=False)
    >>> net.add_edge('a', 'b', weight = 2.1)
    >>> net.add_edge('a', 'c', weight = 1.0)
    >>> s = pp.algorithms.statistics.degrees.degree_sequence(net)
    >>> s
    np.array([2., 1., 1.])

    Return weighted degree sequence
    >>> s = pp.algorithms.statistics.degrees.degree_sequence(net,weight=True)
    >>> s
    array([3.1, 2.1, 1.0])
    """
    _degrees = np.zeros(network.number_of_nodes(), dtype=float)
    for v in network.nodes.uids:
        _degrees[network.nodes.index[v]] = network.degrees(weight=weight)[v]
    return _degrees
Ejemplo n.º 2
0
def ER_np_randomize(network: Network, loops: bool = False) -> Network:
    """Generates a random microstate based on the G(n,p) model. The number of nodes,
    the expected number of edges, the edge directedness and the node uids of the 
    generated network match the corresponding values of a given network instance.
    """

    n = network.number_of_nodes()
    m = network.number_of_edges()
    M = max_edges(n, directed=network.directed, loops=loops)
    p = m/M
    return ER_np(n=n, p=p, directed=network.directed, loops=loops, node_uids=list(network.nodes.uids))
Ejemplo n.º 3
0
def Q_max_modularity(network: Network, cluster_mapping: Dict) -> float:
    """Computes the maximum theoretically possible Q-modularity

    for a given network and cluster mapping
    """
    m = network.number_of_edges()
    qmax: float = 2*m
    for v in network.nodes.uids:
        for w in network.nodes.uids:
            if cluster_mapping[v] == cluster_mapping[w]:
                qmax -= network.degrees()[v]*network.degrees()[w]/(2*m)

    return qmax/(2*m)
Ejemplo n.º 4
0
def Q_modularity(network: Network, cluster_mapping: Dict) -> float:
    """Computes the Q-modularity of a network for a given cluster mapping
    """
    A = network.adjacency_matrix()
    m = network.number_of_edges()

    q = 0.0
    for v in network.nodes.uids:
        for w in network.nodes.uids:
            if cluster_mapping[v] == cluster_mapping[w]:
                q += A[network.nodes.index[v], network.nodes.index[w]] - \
                    network.degrees()[v] * network.degrees()[w]/(2*m)
    return q/(2*m)
Ejemplo n.º 5
0
def degree_centrality(network: Network, mode: str = 'degree') -> dict:
    """Calculates the degree centrality of all nodes.

    Parameters
    ----------
    network : Network

        The :py:class:`Network` object that contains the network

    mode : str

        Can be chose nas 'degree', 'indegree', or 'outdegree'. Determines
        whether to calculate undirected/total degrees, indegrees, or degrees

    Examples
    --------
    Compute degree centrality in a simple network

    >>> import pathpy as pp
    >>> net = pp.Network(directed=True)
    >>> net.add_edge('a', 'x')
    >>> net.add_edge('x', 'b')
    >>> c = pp.algorithms.centralities.degree_centrality(net)
    >>> c['a']
    1

    >>> c = pp.algorithms.centralities.degree_centrality(net, mode='indegree')
    >>> c['a']
    0

    """
    d: dict = dict()
    if mode not in set(['degree', 'indegree', 'outdegree']):
        LOG.error('Mode must be \'degree\', \'indegree\' or \'outdegree\'')
        raise KeyError

    for v in network.nodes.keys():
        if mode == 'indegree':
            d[v] = network.indegrees()[v]
        elif mode == 'outdegree':
            d[v] = network.outdegrees()[v]
        else:
            d[v] = network.degrees()[v]

    return d
Ejemplo n.º 6
0
def ER_nm_randomize(network: Network, loops: bool = False, multiedges: bool = False) -> Union[Network, None]:
    """Generates a random graph whose number of nodes, edges, edge directedness and node uids 
    match the corresponding values of a given network instance. Useful to generate a randomized 
    version of a network.

    Parameters
    ----------
    network : pathpy.Network

        Given network used to determine number of nodes, edges, node uids, and edge directedness    

    loops : bool

        Whether or not the generated network can contain loops.

    multi_edge : bool

        Whether or not multiple edges can be added to the same node pair

    Examples
    --------
    Generate random undirected network with 10 nodes and 25 edges

    >>> import pathpy as pp
    >>> n = pp.Network(directed=False)
    >>> n.add_edge('a', 'b')
    >>> n.add_edge('b', 'c')
    >>> n.add_edge('d', 'e')
    >>> r = pp.generators.ER_nm(n)
    >>> print(r)
    Uid:		0x...
    Type:		Network
    Directed:	False
    Unique nodes:	5
    Unique edges:	3
    Unique paths:	0
    Total paths:	0
    >>> print(r.nodes.uids)
    { 'a', 'b', 'c', 'd', 'e'}

    """

    return ER_nm(network.number_of_nodes(), network.number_of_edges(),
                 directed=network.directed, loops=loops, multiedges=multiedges,
                 node_uids=list(network.nodes.uids))
Ejemplo n.º 7
0
def local_clustering_coefficient(network: Network, v: str) -> float:
    """Calculates the local clustering coefficient of a node in a network.


    The local clustering coefficient of any node with an (out-)degree smaller
    than two is defined as zero. For all other nodes, it is defined as:

        cc(c) := 2*k(i)/(d_i(d_i-1))

        or

        cc(c) := k(i)/(d_out_i(d_out_i-1))

        in undirected and directed networks respectively.

    Parameters
    ----------
    network : Network

        The network in which to calculate the local clustering coefficient

    node : str

        The node for which the local clustering coefficient shall be calculated

    """
    lcc: float = 0.
    d = network.degrees()
    o = network.outdegrees()

    if network.directed and o[v] >= 2 or network.directed == False and d[
            v] >= 2:
        k: int = 0
        # compute set of closed triads
        closed = closed_triads(network, v)
        k = len(closed)

        if network.directed:
            return k / (o[v] * (o[v] - 1))
        else:
            return 2 * k / (d[v] * (d[v] - 1))
    else:
        return 0.
Ejemplo n.º 8
0
def edge_reciprocity(network: Network) -> float:
    """
    """

    if network.directed == False:
        return 1.0
    else:
        reciproc = 0
        for e in network.edges:
            if (e.w, e.v) in network.edges:
                reciproc += 1.0
        return reciproc / network.number_of_edges()
Ejemplo n.º 9
0
def Molloy_Reed_randomize(network: Network) -> Optional[Network]:
    """Generates a random realization of a given network based on the observed degree sequence.
    """
    # degrees are listed in order of node indices 
    degrees = network.degree_sequence()    

    # generate node uids in same order
    node_uids = ['-']*len(degrees)
    for v in network.nodes.uids:
        node_uids[network.nodes.index[v]] = v

    return Molloy_Reed(degrees, node_uids=node_uids)
Ejemplo n.º 10
0
def from_networkx(graph: Any) -> Network:
    n = Network(directed=graph.is_directed(), multiedges=graph.is_multigraph())
    for v in graph.nodes:
        n.add_node(v, **graph.nodes[v])
    for e in graph.edges:
        n.add_edge(e[0], e[1], **graph.edges[e])
    return n
Ejemplo n.º 11
0
def degree_assortativity(network: Network,
                         mode: str = 'total',
                         weight: Weight = None) -> float:
    """Calculates the degree assortativity coefficient of a network.

    Parameters
    ----------

    network : Network

        The network in which to calculate the Molloy-Reed fraction
    """
    A = network.adjacency_matrix(weight=weight)
    m = np.sum(A)

    d = network.degrees(weight)
    if network.directed and mode == 'in':
        d = network.indegrees(weight)
    elif network.directed and mode == 'out':
        d = network.outdegrees(weight)
    elif network.directed and mode == 'total':
        d = network.degrees(weight)
    elif not network.directed:
        m = m / 2.
    idx = network.nodes.index

    cov: float = 0.
    var: float = 0.
    for i in network.nodes.keys():
        for j in network.nodes.keys():
            cov += (A[idx[i], idx[j]] - (d[i] * d[j]) / (2 * m)) * d[i] * d[j]
            if i != j:
                var -= (d[i] * d[j]) / (2 * m) * d[i] * d[j]
            else:
                var += (d[i] - (d[i] * d[j]) / (2 * m)) * d[i] * d[j]
    return cov / var
Ejemplo n.º 12
0
def lattice_network(start: Optional[int]=0, stop: Optional[int]=10, dims: Optional[int]=2) -> Network:
    """
    Generates a n-dimensional lattice network with coordinates in each dimension 
    ranging from start (inclusive) to stop (exclusive)
    """
    network = Network(directed=False)

    for pos in _multi_dim_range(start, stop, dims):
        network.add_node(Node("".join(str(i)+'-' for i in pos).strip('-'), pos=np.array(pos)))
    
    for v in network.nodes:
        for w in network.nodes:
            if np.sum(np.abs(v['pos']-w['pos']))==1 and (v.uid, w.uid) not in network.edges:
                network.add_edge(v, w)
    return network
Ejemplo n.º 13
0
def Molloy_Reed(degrees: Union[np.array, Dict[int, float]], multiedge: bool = False, relax: bool=False, node_uids: Optional[list] = None) -> Optional[Network]:
    """Generate Molloy-Reed graph.

    Generates a random undirected network with given degree sequence based on
    the Molloy-Reed algorithm.

    .. note::

        The condition proposed by Erdös and Gallai (1967) is used to test
        whether the degree sequence is graphic, i.e. whether a network with the
        given degree sequence exists.

    Parameters
    ----------
    degrees : list

        List of integer node degrees. The number of nodes of the generated
        network corresponds to len(degrees).

    relax : bool

        If True, we conceptually allow self-loops and multi-edges, but do not
        add them to the network This implies that the generated network may not
        have exactly sum(degrees)/2 links, but it ensures that the algorithm
        always finishes.

    node_uids : list

        Optional list of node uids that will be used.

    Examples
    --------
    Generate random undirected network with given degree sequence

    >>> import pathpy as pp
    >>> random_network = pp.algorithms.random_graphs.Molloy_Reed([1,0])
    >>> print(random_network.summary())
    ...

    Network generation fails for non-graphic sequences

    >>> import pathpy as pp
    >>> random_network = pp.algorithms.random_graphs.Molloy_Reed([1,0])
    >>> print(random_network)
    None

    """

    # assume that we are given a graphical degree sequence
    if not is_graphic_Erdos_Gallai(degrees):
        return None

    # create empty network with n nodes
    n = len(degrees)
    network = Network(directed=False, multiedges=multiedge)

    if node_uids is None or len(node_uids) != n:
        LOG.info('No valid node uids given, generating numeric node uids')
        node_uids = []
        for i in range(n):
            node_uids.append(str(i))
    
    for i in range(n):
        network.add_node(node_uids[i])

    # generate link stubs based on degree sequence
    stubs = []
    for i in range(n):
        for _ in range(int(degrees[i])):
            stubs.append(str(node_uids[i]))

    # connect randomly chosen pairs of link stubs
    while(len(stubs) > 0):
        v, w = np.random.choice(stubs, 2, replace=False)

        if v == w or (multiedge == False and relax == False and network.nodes[w] in network.successors[v]):
            # remove random edge and add stubs
            if network.number_of_edges()>0:
                edge = random.choice(list(network.edges))
                stubs.append(edge.v.uid)
                stubs.append(edge.w.uid)
                network.remove_edge(edge)
        else:
            if not network.nodes[w] in network.successors[v]:
                network.add_edge(v, w)
            stubs.remove(v)
            stubs.remove(w)            
            

    return network
Ejemplo n.º 14
0
def Watts_Strogatz(n: int, s: int, p: float = 0.0, loops: bool = False,
                   node_uids: Optional[list] = None) -> Network:
    """Undirected Watts-Strogatz lattice network

    Generates an undirected Watts-Strogatz lattice network with lattice
    dimensionality one.

    Parameters
    ----------
    n : int

        The number of nodes in the generated network

    s : float

        The number of nearest neighbors that will be connected
        in the ring lattice

    p : float

        The rewiring probability

    node_uids : list

        Optional list of node uids that will be used.

    Examples
    --------
    Generate a Watts-Strogatz network with 100 nodes

    >>> import pathpy as pp
    >>> small_world = pp.algorithms.random_graphs.Watts_Strogatz(n=100, s=2, p=0.1)
    >>> print(small_world.summary())
    ...

    """
    network = Network(directed=False)
    if node_uids is None or len(node_uids) != n:
        LOG.info('No valid node uids given, generating numeric node uids')
        node_uids = []
        for i in range(n):
            network.add_node(Node(str(i)))
            node_uids.append(str(i))
    else:
        for i in range(n):
            network.add_node(node_uids[i])

    # construct a ring lattice (dimension 1)
    for i in range(n):
        if loops:
            x = 0
            y = s
        else:
            x = 1
            y = s+1
        for j in range(x, y):
            v = network.nodes[node_uids[i]]
            w = network.nodes[node_uids[(i+j) % n]]
            if (v.uid, w.uid) not in network.edges:
                network.add_edge(v, w)

    if p == 0:
        # nothing to do here
        return network

    # Rewire each link with probability p
    for edge in tqdm(list(network.edges.values()), 'generating WS network'):
        if np.random.rand() < p:
            # Delete original link and remember source node
            v = edge.v.uid
            network.remove_edge(edge)

            # Find new random tgt, which is not yet connected to src
            new_target = None

            # This loop repeatedly chooses a random target until we find
            # a target not yet connected to src. Note that this could potentially
            # result in an infinite loop depending on parameters.
            while new_target is None:
                x = node_uids[np.random.randint(n)]
                if (x != v or loops) and (v, x) not in network.edges:
                    new_target = x
            network.add_edge(v, new_target)
    return network
Ejemplo n.º 15
0
def ER_np(n: int, p: float, directed: bool = False, loops: bool = False,
          node_uids: Optional[list] = None) -> Network:
    """(n, p) Erdös-Renyi model

    Generates a random graph with a fixed number of n nodes and edge probability
    p based on the Erdös-Renyi model.

    Parameters
    ----------
    n : int

        The number of nodes in the generated network

    p : float

        The probability with which an edge will be created
        between each pair of nodes

    directed : bool

        Whether a directed network should be generated

    loops : bool

        Whether or not the generated network may contain
        loops.

    node_uids : list

        Optional list of node uids that will be used.

    Examples
    --------
    Generate random undirected network with 10 nodes

    >>> import pathpy as pp
    >>> random_graph = pp.algorithms.random_graphs.ER_np(n=10, p=0.03)
    >>> print(random_graph.summary())
    ...

    """
    network = Network(directed=directed)

    if node_uids is None or len(node_uids) != n:
        LOG.info('No valid node uids given, generating numeric node uids')
        node_uids = []
        for i in range(n):
            node_uids.append(str(i))

    for i in range(n):
        network.add_node(node_uids[i])

    for s in tqdm(range(n), 'generating G(n,p) network'):
        if directed:
            x = n
        else:
            x = s+1
        for t in range(x):
            if t == s and not loops:
                continue
            if np.random.random_sample() < p:
                network.add_edge(node_uids[s], node_uids[t])
    return network
Ejemplo n.º 16
0
def train_test_split(network: Network,
                     test_size: Optional[float] = 0.25,
                     train_size: Optional[float] = None,
                     split: Optional[str] = 'node') -> tuple(Network, Network):
    """Returns a train/test split of a network object. This method is implemented for instances of Network and TemporalNetwork. The train/test split is non-destructive and based on object references, i.e. the function returns new Network instances that contain references to the same node/edge objects. The original network is not affected.

    Parameters
    ----------

    network: Union[Network, TemporalNetwork]

        The network or temporal network for which the train/test split shall be performed.

    test_size: Optional[float] = 0.25

        Fraction of the network to include in the test network

    train_size: Optional[float] = 0.25

        Fraction of the network to include in the training network

    split: Optional['str'] = 'node'

        Specifies how the train/test split shall be performed. For 'node' a random subset of nodes is selected, while for 'edge' a random subset of edges is selected.

    Returns
    -------

    Tuple (n1, n2) where n1 is the training network and n2 is the test network

    Examples
    --------

    >>> n = pp.Network() 
    >>> n.add_edge('a', 'b')
    >>> n.add_edge('b', 'c')
    >>> n.add_edge('c', 'd')
    >>> n.add_edge('d', 'a')
    >>> train, test = train_test_split(n, test_size=0.25)
    >>> print(train)
    >>> print(test)
    Network with one node
    Network with three nodes

    """
    test_network = Network(directed=network.directed,
                           multiedges=network.multiedges,
                           uid=network.uid + '_test')
    train_network = Network(directed=network.directed,
                            multiedges=network.multiedges,
                            uid=network.uid + '_train')

    if train_size == None:
        ts = test_size
    else:
        ts = 1.0 - train_size

    if split == 'node':
        test_nodes = choice([v.uid for v in network.nodes],
                            size=int(ts * network.number_of_nodes()),
                            replace=False)
        for v in test_nodes:
            test_network.add_node(network.nodes[v])
        train_nodes = [
            v.uid for v in network.nodes
            if v.uid not in test_network.nodes.uids
        ]
        for v in train_nodes:
            train_network.add_node(network.nodes[v])
        for e in network.edges:
            if e.v.uid in test_network.nodes.uids and e.w.uid in test_network.nodes.uids:
                test_network.add_edge(e)
            if e.v.uid in train_network.nodes.uids and e.w.uid in train_network.nodes.uids:
                train_network.add_edge(e)

    elif split == 'edge':
        for v in network.nodes:
            test_network.add_node(v)
            train_network.add_node(v)
        test_edges = choice([e.uid for e in network.edges],
                            size=int(ts * network.number_of_edges()),
                            replace=False)
        for e in test_edges:
            test_network.add_edge(network.edges[e])
        train_edges = [
            e.uid for e in network.edges
            if e.uid not in test_network.edges.uids
        ]
        for e in train_edges:
            train_network.add_edge(network.edges[e])
    else:
        raise NotImplementedError(
            'Unsupported split method "{0}" for instance of type Network'.
            format(split))

    return train_network, test_network
Ejemplo n.º 17
0
def ER_nm(n: int, m: int,
          directed: bool = False,
          loops: bool = False,
          multiedges: bool = False,
          node_uids: Optional[list] = None) -> Union[Network, None]:
    """(n, m) Erdös-Renyi model.

    Generates a random graph with a fixed number of n nodes and m edges based on
    the Erdös-Renyi model.

    Parameters
    ----------
    n : int

        The number of nodes in the generated network

    m : int

        The number of randomly generated edges in the network

    directed : bool

        Whether a directed network should be generated

    loops : bool

        Whether or not the generated network may contain
        loops.

    multi_edge : bool

        Whether or not the same edge can be added multiple times

    node_uids : list

        Optional list of node uids that will be used.

    Examples
    --------
    Generate random undirected network with 10 nodes and 25 edges

    >>> import pathpy as pp
    >>> random_graph = pp.algorithms.random_graphs.ER_nm(n=10, m=25)
    >>> print(random_graph.summary())
    ...

    """
    # Check parameter sanity
    M = max_edges(n, directed=directed, loops=loops, multiedges=multiedges)
    if m > M:
        LOG.error(
            'Given network type with n nodes can have at most {} edges.'.format(M))
        return None

    network = Network(directed=directed)

    if node_uids is None or len(node_uids) != n:
        LOG.info('No valid node uids given, generating numeric node uids')
        node_uids = []
        for i in range(n):
            node_uids.append(str(i))

    for i in range(n):
        network.add_node(node_uids[i])

    edges = 0
    while edges < m:
        v, w = np.random.choice(node_uids, size=2, replace=loops)
        if multiedges or network.nodes[w] not in network.successors[v]:
            network.add_edge(v, w)
            edges += 1
    return network