def get_eattr(self, edges, name=None): g = self.parent()._graph if nonstring_container(edges[0]): # many edges eids = [g.get_eid(*e) for e in edges] if name is None: eprop = {} if nonstring_container(name[0]): for k in self.keys(): dtype = _np_dtype(super().__getitem__(k)) eprop[k] = _to_np_array( [g.es[eid][k] for eid in eids], dtype=dtype) dtype = _np_dtype(super().__getitem__(name)) return _to_np_array([g.es[eid][name] for eid in eids], dtype=dtype) elif not nonstring_container(edges): raise ValueError("Invalid `edges` entry: {}.".format(edges)) # single edge eid = g.get_eid(*edges) if name is None: eprop = {} for k in self.keys(): eprop[k] = g.es[eid][k] return eprop return g.es[eid][name]
def __getitem__(self, name): ''' Return the attributes of an edge or a list of edges. ''' eprop = {} graph = self.parent() if isinstance(name, slice): for k in self.keys(): dtype = _np_dtype(super().__getitem__(k)) eprop[k] = _to_np_array(self.prop[k], dtype)[name] return eprop elif nonstring_container(name): if nonstring_container(name[0]): eids = [graph.edge_id(e) for e in name] for k in self.keys(): dtype = _np_dtype(super().__getitem__(k)) eprop[k] = _to_np_array(self.prop[k], dtype=dtype)[eids] else: eid = graph.edge_id(name) for k in self.keys(): eprop[k] = self.prop[k][eid] return eprop dtype = _np_dtype(super().__getitem__(name)) return _to_np_array(self.prop[name], dtype=dtype)
def __getitem__(self, name): edges = None if isinstance(name, slice): edges = self.parent().edges_array[name] elif nonstring_container(name): if nonstring_container(name[0]): edges = name else: if len(name) != 2: raise InvalidArgument( "key for edge attribute must be one of the following: " "slice, list of edges, edges or attribute name.") return self.parent()[name[0]][name[1]] if isinstance(name, str): dtype = _np_dtype(super(_NxEProperty, self).__getitem__(name)) eprop = np.empty(self.parent().edge_nb(), dtype=dtype) g = self.parent() for d, eid in zip(g.edges(data=name), g.edges(data="eid")): eprop[eid[2]] = d[2] return eprop else: eprop = {k: [] for k in self.keys()} for edge in edges: data = self.parent().get_edge_data(edge[0], edge[1]) for k, v in data.items(): if k != "eid": eprop[k].append(v) for k, v in eprop.items(): dtype = _np_dtype(super(_NxEProperty, self).__getitem__(k)) eprop = {k: np.array(v, dtype)} return eprop
def edge_id(self, edge): ''' Return the ID a given edge or a list of edges in the graph. Raises an error if the edge is not in the graph or if one of the vertices in the edge is nonexistent. Parameters ---------- edge : 2-tuple or array of edges Edge descriptor (source, target). Returns ------- index : int or array of ints Index of the given `edge`. ''' g = self._graph if nonstring_container(edge) and len(edge): if is_integer(edge[0]): return g.edge_index[g.edge(*edge)] elif nonstring_container(edge[0]): Edge = g.edge return [g.edge_index[Edge(*e)] for e in edge] raise AttributeError("`edge` must be either a 2-tuple of ints or " "an array of 2-tuples of ints.")
def new_node(self, n=1, ntype=1, attributes=None, value_types=None, positions=None, groups=None): ''' Adding a node to the graph, with optional properties. Parameters ---------- n : int, optional (default: 1) Number of nodes to add. ntype : int, optional (default: 1) Type of neuron (1 for excitatory, -1 for inhibitory) Returns ------- The node or an iterator over the nodes created. ''' first_node_idx = self.vcount() super(_IGraph, self).add_vertices(n) nodes = list(range(first_node_idx, first_node_idx + n)) if attributes is not None: for k, v in attributes.items(): if k not in self._nattr: self._nattr.new_attribute(k, value_types[k], val=v) else: v = v if nonstring_container(v) else [v] self._nattr.set_attribute(k, v, nodes=nodes) self.vs[nodes[0]:nodes[-1] + 1]['type'] = ntype if self.is_spatial(): old_pos = self._pos self._pos = np.full((self.node_nb(), 2), np.NaN) num_existing = len(old_pos) if old_pos is not None else 0 if num_existing != 0: self._pos[:num_existing, :] = old_pos if positions is not None: assert self.is_spatial(), \ "`positions` argument requires a SpatialGraph/SpatialNetwork." self._pos[nodes] = positions if groups is not None: assert self.is_network(), \ "`positions` argument requires a Network/SpatialNetwork." if nonstring_container(groups): assert len(groups) == n, "One group per neuron required." for g, node in zip(groups, nodes): self.population.add_to_group(g, node) else: self.population.add_to_group(groups, nodes) if n == 1: return nodes[0] return nodes
def new_node(self, n=1, ntype=1, attributes=None, value_types=None, positions=None, groups=None): ''' Adding a node to the graph, with optional properties. Parameters ---------- n : int, optional (default: 1) Number of nodes to add. ntype : int, optional (default: 1) Type of neuron (1 for excitatory, -1 for inhibitory) attributes : dict, optional (default: None) Dictionary containing the attributes of the nodes. value_types : dict, optional (default: None) Dict of the `attributes` types, necessary only if the `attributes` do not exist yet. Returns ------- The node or a list of the nodes created. ''' nodes = super(_GtGraph, self).add_vertex(n) nodes = [nodes] if n == 1 else list(nodes) if attributes is not None: for k, v in attributes.items(): if k not in self._nattr: self._nattr.new_attribute(k, value_types[k], val=v) else: v = v if nonstring_container(v) else [v] self._nattr.set_attribute(k, v, nodes=nodes) if self.is_spatial(): old_pos = self._pos self._pos = np.full((self.node_nb(), 2), np.NaN) num_existing = len(old_pos) if old_pos is not None else 0 if num_existing != 0: self._pos[:num_existing, :] = old_pos if positions is not None: assert self.is_spatial(), \ "`positions` argument requires a SpatialGraph/SpatialNetwork." self._pos[nodes] = positions if groups is not None: assert self.is_network(), \ "`positions` argument requires a Network/SpatialNetwork." if nonstring_container(groups): assert len(groups) == n, "One group per neuron required." for g, node in zip(groups, nodes): self.population.add_to_group(g, node) else: self.population.add_to_group(groups, nodes) if n == 1: return nodes[0] return nodes
def new_node(self, n=1, ntype=1, attributes=None, value_types=None, positions=None, groups=None): ''' Adding a node to the graph, with optional properties. Parameters ---------- n : int, optional (default: 1) Number of nodes to add. ntype : int, optional (default: 1) Type of neuron (1 for excitatory, -1 for inhibitory) Returns ------- The node or a list of the nodes created. ''' new_nodes = list(range(len(self), len(self)+n)) for v in new_nodes: super(_NxGraph, self).add_node(v) if attributes is not None: for k, v in attributes.items(): if k not in self._nattr: self._nattr.new_attribute(k, value_types[k], val=v) else: v = v if nonstring_container(v) else [v] self._nattr.set_attribute(k, v, nodes=new_nodes) else: filler = [None for _ in new_nodes] for k in self._nattr: self._nattr.set_attribute(k, filler, nodes=new_nodes) if self.is_spatial(): old_pos = self._pos self._pos = np.full((self.node_nb(), 2), np.NaN) num_existing = len(old_pos) if old_pos is not None else 0 if num_existing != 0: self._pos[:num_existing, :] = old_pos if positions is not None and len(positions): assert self.is_spatial(), \ "`positions` argument requires a SpatialGraph/SpatialNetwork." self._pos[new_nodes, :] = positions if groups is not None: assert self.is_network(), \ "`positions` argument requires a Network/SpatialNetwork." if nonstring_container(groups): assert len(groups) == n, "One group per neuron required." for g, node in zip(groups, new_nodes): self.population.add_to_group(g, node) else: self.population.add_to_group(groups, new_nodes) if len(new_nodes) == 1: return new_nodes[0] return new_nodes
def get_degrees(self, mode="total", nodes=None, weights=None): ''' Returns the degree of the nodes. .. warning :: When using MPI, returns only the degree related to local edges. ''' g = self._graph num_nodes = None weights = 'weight' if weights is True else weights if nodes is None: num_nodes = self.node_nb() nodes = slice(num_nodes) elif nonstring_container(nodes): nodes = list(nodes) num_nodes = len(nodes) else: nodes = [nodes] num_nodes = 1 # weighted if nonstring_container(weights) or weights in self._eattr: degrees = np.zeros(num_nodes) adj_mat = self.adjacency_matrix(types=False, weights=weights) if mode in ("in", "total") or not self.is_directed(): degrees += adj_mat.sum(axis=0).A1[nodes] if mode in ("out", "total") and self.is_directed(): degrees += adj_mat.sum(axis=1).A1[nodes] return degrees elif weights not in {None, False}: raise ValueError("Invalid `weights` {}".format(weights)) # unweighted degrees = np.zeros(num_nodes, dtype=int) if not g._directed or mode in ("in", "total"): if isinstance(nodes, slice): degrees += g._in_deg[nodes] else: degrees += [g._in_deg[i] for i in nodes] if g._directed and mode in ("out", "total"): if isinstance(nodes, slice): degrees += g._out_deg[nodes] else: degrees += [g._out_deg[i] for i in nodes] if num_nodes == 1: return degrees[0] return degrees
def _get_data(source): ''' Returns the (times, senders) array. Parameters ---------- source : list or str Indices of spike detectors or path to the .gdf files. Returns ------- data : 2D array of shape (N, 2) ''' data = [[], []] is_string = isinstance(source, str) if is_string: source = [source] elif nonstring_container(source) and isinstance(source[0], str): is_string = True if is_string: for path in source: tmp = np.loadtxt(path) data[0].extend(tmp[:, 0]) data[1].extend(tmp[:, 1]) else: source_shape = np.shape(np.squeeze(source)) if len(source_shape) == 2: # source is directly the data if source_shape[0] == 2 and source_shape[1] != 2: return np.array(source).T else: return np.array(source) else: # source contains gids source = _get_nest_gids(source) events = None if nonstring_container(source[0]): events = [nest.GetStatus(gid, "events")[0] for gid in source] else: events = nest.GetStatus(source, "events") for ev in events: data[0].extend(ev["senders"]) data[1].extend(ev["times"]) idx_sort = np.argsort(data[1]) return np.array(data)[:, idx_sort].T
def _format_arg(arg, num_expected, arg_name): if nonstring_container(arg): assert len(arg) == num_expected, "One entry per attribute " +\ "required for `" + arg_name + "`." elif arg is not None: arg = [arg for _ in range(num_expected)] return arg
def binning(x, bins='bayes', log=False): """ Binning function providing automatic binning using Bayesian blocks in addition to standard linear and logarithmic uniform bins. .. versionadded:: 0.7 Parameters ---------- x : array-like Array of data to be histogrammed bins : int, list or 'auto', optional (default: 'bayes') If `bins` is 'bayes', in use bayesian blocks for dynamic bin widths; if it is an int, the interval will be separated into log : bool, optional (default: False) Whether the bins should be evenly spaced on a logarithmic scale. """ x = np.asarray(x) new_bins = None if bins == 'bayes': return bayesian_blocks(x) elif nonstring_container(bins): return bins elif is_integer(bins): if log: return np.logspace(np.log10(np.maximum(x.min(), 1e-10)), np.log10(x.max()), bins) else: return np.linspace(x.min(), x.max(), bins) else: raise ValueError("unrecognized bin code: '" + str(bins) + "'.")
def node_attributes(network, attributes, nodes=None, data=None): ''' Return node `attributes` for a set of `nodes`. Parameters ---------- network : :class:`~nngt.Graph` The graph where the `nodes` belong. attributes : str or list Attributes which should be returned, among: * "betweenness" * "clustering" * "in-degree", "out-degree", "total-degree" * "subgraph_centrality" nodes : list, optional (default: all nodes) Nodes for which the attributes should be returned. data : :class:`numpy.array` of shape (N, 2), optional (default: None) Potential data on the spike events; if not None, it must contain the sender ids on the first column and the spike times on the second. Returns ------- values : array-like or dict Returns the attributes, either as an array if only one attribute is required (`attributes` is a :obj:`str`) or as a :obj:`dict` of arrays. ''' if nonstring_container(attributes): values = {} for attr in attributes: values[attr] = _get_attribute(network, attr, nodes, data) return values else: return _get_attribute(network, attributes, nodes, data)
def correlation_to_attribute(network, reference_attribute, other_attributes, nodes=None, title=None, show=True): ''' For each node plot the value of `reference_attributes` against each of the `other_attributes` to check for correlations. Parameters ---------- network : :class:`~nngt.Graph` The graph where the `nodes` belong. reference_attribute : str or array-like Attribute which should serve as reference, among: * "betweenness" * "clustering" * "in-degree", "out-degree", "total-degree" * "subgraph_centrality" * "b2" (requires NEST) * "firing_rate" (requires NEST) * a custom array of values, in which case one entry per node in `nodes` is required. other_attributes : str or list Attributes that will be compared to the reference. nodes : list, optional (default: all nodes) Nodes for which the attributes should be returned. ''' import matplotlib.pyplot as plt if not nonstring_container(other_attributes): other_attributes = [other_attributes] fig = plt.figure() fig.patch.set_visible(False) # get reference data ref_data = reference_attribute if isinstance(reference_attribute, str): ref_data = node_attributes(network, reference_attribute, nodes=nodes) else: reference_attribute = "user defined attribute" # plot the remaining attributes assert isinstance(other_attributes, (str, list)), \ "Only attribute names are allowed for `other_attributes`" values = node_attributes(network, other_attributes, nodes=nodes) fig, axes = _set_new_plot(fignum=fig.number, names=other_attributes) for i, (attr, val) in enumerate(values.items()): end_attr = attr[1:] end_ref_attr = reference_attribute[1:] if nngt._config["use_tex"]: end_attr = end_attr.replace("_", "\\_") end_ref_attr = end_ref_attr.replace("_", "\\_") # reference nodes axes[i].plot(val, ref_data, ls="", marker="o") axes[i].set_xlabel(attr[0].upper() + end_attr) axes[i].set_ylabel(reference_attribute[0].upper() + end_ref_attr) axes[i].set_title( "{}{} vs {} for each ".format( reference_attribute[0].upper(), end_ref_attr, attr[0] + \ end_attr, network.name) + \ "node in {}".format(network.name), loc='left', x=0., y=1.05) # adjust space, set title, and show _format_and_show(fig, 0, values, title, show)
def get_eattr(self, edges, name=None): g = self.parent() eid = g.edge_id if nonstring_container(edges[0]): # many edges if name is None: eprop = {} for k in self.keys(): prop = self.prop[k] dtype = super().__getitem__(k) eprop[k] = _to_np_array( [prop[eid(tuple(e))] for e in edges], dtype) return eprop prop = self.prop[name] dtype = super().__getitem__(name) return _to_np_array([prop[eid(tuple(e))] for e in edges], dtype) # single edge if name is None: eprop = {} for k in self.keys(): eprop[k] = self.prop[k][eid(tuple(edges))] return eprop return self.prop[name][eid(tuple(edges))]
def node_attributes(network, attributes, nodes=None): ''' Return node `attributes` for a set of `nodes`. Parameters ---------- network : :class:`~nngt.Graph` The graph where the `nodes` belong. attributes : str or list Attributes which should be returned, among: * "betweenness" * "clustering" * "in-degree", "out-degree", "total-degree" * "subgraph_centrality" nodes : list, optional (default: all nodes) Nodes for which the attributes should be returned. Returns ------- values : array-like or dict Returns the attributes, either as an array if only one attribute is required (`attributes` is a :obj:`str`) or as a :obj:`dict` of arrays. ''' if nonstring_container(attributes): values = {} for attr in attributes: values[attr] = _get_attribute(network, attr, nodes) return values else: return _get_attribute(network, attributes, nodes)
def delete_nodes(self, nodes): ''' Remove nodes (and associated edges) from the graph. ''' g = self._graph if nonstring_container(nodes): for n in nodes: g.remove_node(n) else: g.remove_node(nodes) # relabel nodes from zero nx = nngt._config["library"] nx.relabel_nodes(g, {n: i for i, n in enumerate(g.nodes)}, copy=False) # update attributes for key in self._nattr: self._nattr._num_values_set[key] = self.node_nb() for key in self._eattr: self._eattr._num_values_set[key] = self.edge_nb() # check spatial and structure properties _post_del_update(self, nodes)
def monitor_nodes(gids, nest_recorder=None, params=None, network=None): ''' Monitoring the activity of nodes in the network. Parameters ---------- gids : tuple of ints or list of tuples GIDs of the neurons in the NEST subnetwork; either one list per recorder if they should monitor different neurons or a unique list which will be monitored by all devices. nest_recorder : strings or list, optional (default: spike recorder) Device(s) to monitor the network. params : dict or list of, optional (default: `{}`) Dictionarie(s) containing the parameters for each recorder (see `NEST documentation <http://www.nest-simulator.org/quickref/#nodes>`_ for details). network : :class:`~nngt.Network` or subclass, optional (default: None) Network which population will be used to differentiate groups. Returns ------- recorders : list or NodeCollection containing the recorders' gids recordables : list of the recordables' names. ''' if nest_recorder is None: nest_recorder = [spike_rec] elif not nonstring_container(nest_recorder): nest_recorder = [nest_recorder] if params is None: params = [{}] elif isinstance(params, dict): params = [params] return _monitor(gids, nest_recorder, params)
def new_edges(self, edge_list, attributes=None): ''' Add a list of edges to the graph. Parameters ---------- edge_list : list of 2-tuples or np.array of shape (edge_nb, 2) List of the edges that should be added as tuples (source, target) attributes : dict, optional (default: ``None``) Dictionary of the form ``{ "name": [], "values": [], "type": [] }``, containing the attributes of the new edges. warning :: For now attributes works only when adding edges for the first time (i.e. adding edges to an empty graph). @todo: add example, check the edges for self-loops and multiple edges Returns ------- Returns new edges only. ''' if attributes is None: attributes = {} initial_ecount = self.ecount() if not self._directed: edge_list = np.concatenate((edge_list, edge_list[:, ::-1])) for key, val in attributes.items(): attributes[key] = np.concatenate((val, val)) first_eid = self.ecount() super(_IGraph, self).add_edges(edge_list) _set_edge_attr(self, edge_list, attributes) num_edges = self.ecount() # attributes if self._weighted and "weight" not in attributes: attributes["weight"] = np.repeat(1., num_edges) if attributes: elist0 = None #@todo: make elist supported and remove this # take care of classic attributes if "weight" in attributes: self.set_weights(weight=attributes["weight"], elist=elist0) if "delay" in attributes: self.set_delays(delay=attributes["delay"], elist=elist0) if "distance" in attributes: raise NotImplementedError("distance not implemented yet") #~ self.set_distances(elist=edge_list, #~ dlist=attributes["distance"]) # take care of potential additional attributes if "names" in attributes: num_attr = len(attributes["names"]) for i in range(num_attr): v = attributes["values"] if not nonstring_container(v): v = np.repeat(v, self.ecount()) self._eattr.new_ea(attributes["names"][i], attributes["types"][i], values=v) return edge_list
def get_degrees(self, mode="total", nodes=None, weights=None): g = self._graph w = _get_ig_weights(self, weights) mode = 'all' if mode == 'total' else mode if nonstring_container(weights) or weights not in {False, None}: return np.array(g.strength(nodes, mode=mode, weights=w)) return np.array(g.degree(nodes, mode=mode), dtype=int)
def delete_edges(self, edges): ''' Remove a list of edges ''' if len(edges): if nonstring_container(edges[0]): self._graph.remove_edges_from(edges) else: self._graph.remove_edge(*edges) for key in self._eattr: self._eattr._num_values_set[key] = self.edge_nb()
def new_edges(self, edge_list, attributes=None): ''' Add a list of edges to the graph. Parameters ---------- edge_list : list of 2-tuples or np.array of shape (edge_nb, 2) List of the edges that should be added as tuples (source, target) attributes : dict, optional (default: ``None``) Dictionary of the form ``{ "name": [], "values": [], "type": [] }``, containing the attributes of the new edges. warning :: For now attributes works only when adding edges for the first time (i.e. adding edges to an empty graph). @todo: add example, check the edges for self-loops and multiple edges ''' if attributes is None: attributes = {} initial_ecount = self.ecount() if not self._directed: edge_list = np.concatenate((edge_list, edge_list[:,::-1])) for key, val in attributes.items(): attributes[key] = np.concatenate((val, val)) edge_generator = (edge for edge in edge_list) edge_list = np.array(edge_list) first_eid = self.ecount() super(_IGraph, self).add_edges(edge_list) last_eid = self.ecount() edge_list = self.edges_array # attributes if self._weighted and "weight" not in attributes: attributes["weight"] = np.repeat(1., edge_list.shape[0]) if attributes: elist0 = None #@todo: make elist supported and remove this # take care of classic attributes if "weight" in attributes: self.set_weights(weight=attributes["weight"], elist=elist0) if "delay" in attributes: self.set_delays(delay=attributes["delay"], elist=elist0) if "distance" in attributes: raise NotImplementedError("distance not implemented yet") #~ self.set_distances(elist=edge_list, #~ dlist=attributes["distance"]) # take care of potential additional attributes if "names" in attributes: num_attr = len(attributes["names"]) for i in range(num_attr): v = attributes["values"] if not nonstring_container(v): v = np.repeat(v, self.ecount()) self._eattr.new_ea(attributes["names"][i], attributes["types"][i], values=v) return edge_list
def __getitem__(self, name): ''' Return the attributes of an edge or a list of edges. ''' if isinstance(name, slice): eprop = {} for k in self.keys(): eprop[k] = self.parent().edge_properties[k].a[name] return eprop elif nonstring_container(name): eprop = {} if nonstring_container(name[0]): eids = [self.parent().edge_index[e] for e in name] for k in self.keys(): eprop[k] = self.parent().edge_properties[k].a[eids] else: for k in self.keys(): eprop[k] = self.parent().edge_properties[k][name] return eprop return np.array(self.parent().edge_properties[name].a)
def _get_nest_gids(gids): ''' Convert nodes to NodeCollection if NEST is version 3+ ''' if nest_version == 3: if isinstance(gids, NodeCollection): return gids return NodeCollection(sorted(gids)) if nonstring_container(gids): return list(gids) return [gids]
def delete_edges(self, edges): ''' Remove a list of edges ''' if len(edges): if nonstring_container(edges[0]): if isinstance(edges[0], tuple): self._graph.delete_edges(edges) else: self._graph.delete_edges([tuple(e) for e in edges]) else: self._graph.delete_edges([edges]) for key in self._eattr: self._eattr._num_values_set[key] = self.edge_nb()
def __getitem__(self, name): if isinstance(name, slice): eprop = {} for k in self.keys(): dtype = _np_dtype(super(_IgEProperty, self).__getitem__(k)) eprop[k] = np.array(self.parent().es[k], dtype=dtype)[name] return eprop elif nonstring_container(name): eprop = {} if nonstring_container(name[0]): eids = [self.parent().get_eid(*e) for e in name] for k in self.keys(): dtype = _np_dtype( super(_IgENProperty, self).__getitem__(k)) eprop[k] = np.array(self.parent().es[k], dtype=dtype)[eids] else: eid = self.parent().get_eid(*name) for k in self.keys(): eprop[k] = self.parent().es[k][eid] return eprop dtype = _np_dtype(super(_IgEProperty, self).__getitem__(name)) return np.array(self.parent().es[name], dtype=dtype)
def new_edges(self, edge_list, attributes=None): ''' Add a list of edges to the graph. .. warning :: This function currently does not check for duplicate edges! Parameters ---------- edge_list : list of 2-tuples or np.array of shape (edge_nb, 2) List of the edges that should be added as tuples (source, target) attributes : :class:`dict`, optional (default: ``{}``) Dictionary containing optional edge properties. If the graph is weighted, defaults to ``{"weight": ones}``, where ``ones`` is an array the same length as the `edge_list` containing a unit weight for each connection (synaptic strength in NEST). @todo: add example, check the edges for self-loops and multiple edges ''' #check attributes if attributes is None: attributes = {} initial_edges = self.edge_nb() if not isinstance(edge_list, np.ndarray): edge_list = np.array(edge_list) if not self._directed: recip_edges = edge_list[:,::-1] # slow but works unique = ~(recip_edges[..., np.newaxis] == edge_list[..., np.newaxis].T).all(1).any(1) edge_list = np.concatenate((edge_list, recip_edges[unique])) for key, val in attributes.items(): attributes[key] = np.concatenate((val, val[unique])) # create the edges ws = None num_added = len(edge_list) if "weight" in attributes: if nonstring_container(attributes["weight"]): ws = attributes["weight"] else: ws = (attributes["weight"] for _ in range(num_added)) else: ws = (1 for _ in range(num_added)) for i, (e, w) in enumerate(zip(edge_list, ws)): self._edges[tuple(e)] = initial_edges + i self._out_deg[e[0]] += 1 self._in_deg[e[1]] += 1 self._adj_mat[e[0], e[1]] = w # call parent function to set the attributes self.attr_new_edges(edge_list, attributes=attributes) return edge_list
def __getitem__(self, name): ''' Return the attributes of an edge or a list of edges. ''' eprop = {} if isinstance(name, slice): for k in self.keys(): dtype = _np_dtype(super(_EProperty, self).__getitem__(k)) eprop[k] = np.array(self.prop[k][name], dtype=dtype) return eprop elif nonstring_container(name): if nonstring_container(name[0]): eids = [self.parent().edge_id(e) for e in name] for k in self.keys(): dtype = _np_dtype(super(_EProperty, self).__getitem__(k)) eprop[k] = np.array(self.prop[k][eids], dtype=dtype) else: for k in self.keys(): dtype = _np_dtype(super(_EProperty, self).__getitem__(k)) eprop[k] = np.array(self.prop[k][name], dtype=dtype) return eprop dtype = _np_dtype(super(_EProperty, self).__getitem__(name)) return np.array(self.prop[name], dtype=dtype)
def _get_weights(g, weights): if weights in g.edge_attributes: # existing edge attribute return np.array(g._graph.es[weights]) elif nonstring_container(weights): # user-provided array return np.array(weights) elif weights is True: # "normal" weights return np.array(g._graph.es["weight"]) elif not weights: # unweighted return None raise ValueError("Unknown edge attribute '" + str(weights) + "'.")
def delete_edges(self, edges): ''' Remove a list of edges ''' if len(edges): g = self._graph Edge = g.edge if nonstring_container(edges[0]): # fast loop [self._graph.remove_edge(Edge(*e)) for e in edges] else: self._graph.remove_edge(Edge(*edges)) self._eattr.edges_deleted() self._edges_deleted = True
def local_clustering_binary_undirected(g, nodes=None): r''' Returns the undirected local clustering coefficient of some `nodes`. .. math:: C_i = \frac{A^3_{ii}}{d_i(d_i - 1)} = \frac{\Delta_i}{T_i} with :math:`A` the adjacency matrix, :math:`d_i` the degree of node :math:`i`, :math:`\Delta_i` is the number of triangles, and :math:`T_i` is the number of triplets to which :math:`i` belongs. If `g` is directed, then it is converted to a simple undirected graph (no parallel edges), both directed and reciprocal edges are merged into a single edge. Parameters ---------- g : :class:`~nngt.Graph` Graph to analyze. nodes : list, optional (default: all nodes) The list of nodes for which the clustering will be returned Returns ------- lc : :class:`numpy.ndarray` The list of clustering coefficients, on per node. References ---------- .. [gt-local-clustering] :gtdoc:`clustering.local_clustering` .. [ig-local-clustering] :igdoc:`transitivity_local_undirected` .. [nx-local-clustering] :nxdoc:`algorithms.cluster.clustering` ''' # Note, this function is overloaded by the library-specific version # if igraph, graph-tool, or networkx is used triangles = triangle_count(g, weights=None, nodes=nodes, directed=False) triplets = triplet_count(g, weights=None, nodes=nodes, directed=False) if nonstring_container(triangles): triplets[triangles == 0] = 1 elif triangles == 0: return 0 return triangles / triplets
def correlation_to_attribute(network, reference_attribute, other_attributes, nodes=None, title=None, show=True): ''' For each node plot the value of `reference_attributes` against each of the `other_attributes` to check for correlations. Parameters ---------- network : :class:`~nngt.Graph` The graph where the `nodes` belong. reference_attribute : str or array-like Attribute which should serve as reference, among: * "betweenness" * "clustering" * "in-degree", "out-degree", "total-degree" * "subgraph_centrality" * "b2" (requires NEST) * "firing_rate" (requires NEST) * a custom array of values, in which case one entry per node in `nodes` is required. other_attributes : str or list nodes : list, optional (default: all nodes) Nodes for which the attributes should be returned. ''' if not nonstring_container(other_attributes): other_attributes = [other_attributes] fig = plt.figure() # get reference data ref_data = reference_attribute if isinstance(reference_attribute, str): ref_data = node_attributes(network, reference_attribute, nodes=nodes) # plot the remaining attributes values = node_attributes(network, other_attributes, nodes=nodes) fig, axes = _set_new_plot(fignum=fig.number, names=other_attributes) for i, (attr, val) in enumerate(values.items()): # reference nodes axes[i].plot(val, ref_data, ls="", marker="o") axes[i].set_xlabel(attr[0].upper() + attr[1:]) axes[i].set_ylabel( reference_attribute[0].upper() + reference_attribute[1:]) axes[i].set_title("{}{} vs {} for each ".format( attr[0].upper(), attr[1:], reference_attribute, network.name) +\ "node in {}".format(network.name), loc='left', x=0., y=1.05) # adjust space, set title, and show _format_and_show(fig, 0, values, title, show)