Beispiel #1
0
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)
Beispiel #2
0
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)
Beispiel #3
0
def _sort_neurons(sort, gids, network, data=None, return_attr=False):
    '''
    Sort the neurons according to the `sort` property.

    If `sort` is "firing_rate" or "B2", then data must contain the `senders`
    and `times` list given by a NEST ``spike_recorder``.

    Parameters
    ----------
    sort : str or array
        Sorting method or indices
    gids : array-like
        NEST gids
    network : the network
    data : numpy.array of shape (N, 2)
        Senders on column 1, times on column 2.

    Returns
    -------
    For N neurons, labeled from ``GID_MIN`` to ``GID_MAX``, returns a`sorting`
    array of size ``GID_MAX``, where ``sorting[gids]`` gives the sorted ids of
    the neurons, i.e. an integer between 1 and N.
    '''
    from nngt.analysis import node_attributes, get_b2
    min_nest_gid = network.nest_gid.min()
    max_nest_gid = network.nest_gid.max()
    sorting = np.zeros(max_nest_gid + 1)
    attribute = None
    sorted_ids = None
    if isinstance(sort, str):
        if sort == "firing_rate":
            # compute number of spikes per neuron
            spikes = np.bincount(data[:, 0].astype(int))
            if spikes.shape[0] < max_nest_gid:  # one entry per neuron
                spikes.resize(max_nest_gid)
            # sort them (neuron with least spikes arrives at min_nest_gid)
            sorted_ids = np.argsort(spikes)[min_nest_gid:] - min_nest_gid
            # get attribute
            idx_min = int(np.min(data[:, 0]))
            attribute = spikes[idx_min:] \
                        / (np.max(data[:, 1]) - np.min(data[:, 1]))
        elif sort.lower() == "b2":
            attribute = get_b2(network, data=data, nodes=gids)
            sorted_ids = np.argsort(attribute)
            # check for non-spiking neurons
            num_b2 = attribute.shape[0]
            if num_b2 < network.node_nb():
                spikes = np.bincount(data[:, 0])
                non_spiking = np.where(spikes[min_nest_gid] == 0)[0]
                sorted_ids.resize(network.node_nb())
                for i, n in enumerate(non_spiking):
                    sorted_ids[sorted_ids >= n] += 1
                    sorted_ids[num_b2 + i] = n
        else:
            attribute = node_attributes(network, sort)
            sorted_ids = np.argsort(attribute)
    else:
        sorted_ids = np.argsort(sort)
    num_sorted = 1
    for group in network.population.values():
        gids = network.nest_gid[group.ids]
        order = np.argsort(np.argsort(np.argsort(sorted_ids)[group.ids]))
        sorting[gids] = num_sorted + order
        num_sorted += len(group.ids)
    if return_attr:
        return sorting.astype(int), attribute
    else:
        return sorting.astype(int)
Beispiel #4
0
def compare_population_attributes(network, attributes, nodes=None,
                                  reference_nodes=None, num_bins=50,
                                  reference_color="gray", title=None,
                                  show=True):
    '''
    Compare node `attributes` between two sets of nodes. Since number of nodes
    can vary, normalized distributions are used.
    
    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"
        * "b2" (requires NEST)
        * "firing_rate" (requires NEST)
    nodes : list, optional (default: all nodes)
        Nodes for which the attributes should be returned.
    reference_nodes : list, optional (default: all nodes)
        Reference nodes for which the attributes should be returned in order
        to compare with `nodes`.
    num_bins : int or list, optional (default: 50)
        Number of bins to plot the distributions. If only one int is provided,
        it is used for all attributes, otherwize a list containing one int per
        attribute in `attributes` is required.
    '''
    if not nonstring_container(attributes):
        attributes = [attributes]
    else:
        attributes = [name for name in attributes]
    if isinstance(reference_color, str):
        reference_color = [reference_color]
    else:
        raise InvalidArgument("`reference_color` must be a valid matplotlib "
                              "color string.")
    if nonstring_container(num_bins):
        assert len(num_bins) == len(attributes), "One entry per attribute " +\
            "required for `num_bins`."
    else:
        num_bins = [num_bins for _ in range(len(attributes))]
    fig = plt.figure()
    num_plot = 0
    # plot degrees if required
    degrees = []
    for name in attributes:
        if "degree" in name.lower():
            degrees.append(name[:name.find("-")])
    if degrees:
        indices = []
        for i, name in enumerate(attributes):
            if "degree" in name:
                indices.append(i)
        indices.sort()
        deg_bin = int(np.average(np.array(num_bins)[indices]))
        for idx in indices[::-1]:
            del num_bins[idx]
            del attributes[idx]
        # reference nodes
        degree_distribution(
            network, deg_type=degrees, nodes=reference_nodes, num_bins=deg_bin,
            fignum=fig.number, colors=reference_color*len(degrees), norm=True,
            show=False)
        # nodes
        degree_distribution(
            network, deg_type=degrees, nodes=nodes, num_bins=deg_bin,
            fignum=fig.number, axis_num=0, norm=True, show=False)
        # set legend
        lines = fig.get_axes()[num_plot].get_lines()
        for i in range(int(len(lines) / 2)):
            lines[i].set_label("{}-degree reference".format(degrees[i]))
            lines[i+len(degrees)].set_label(
                "{}-degree nodes".format(degrees[i]))
        plt.legend(
            loc='center', bbox_to_anchor=[0.9, 1.], ncol=1, frameon=True)
        num_plot += 1
    # plot betweenness if needed
    if "betweenness" in attributes:
        idx = attributes.index("betweenness")
        # reference nodes
        betweenness_distribution(
            network, btype="node", nodes=reference_nodes, fignum=fig.number,
            colors=2*reference_color, norm=True, show=False)
        # nodes
        betweenness_distribution(
            network, btype="node", nodes=nodes, fignum=fig.number,
            axis_num=(1,), norm=True, show=False)
        # set legend
        lines = fig.get_axes()[num_plot].get_lines()
        lines[0].set_label("reference")
        lines[1].set_label("nodes")
        plt.legend(
            loc='center', bbox_to_anchor=[0.9, 1.], ncol=1, frameon=True)
        num_plot += 1
        del attributes[idx]
        del num_bins[idx]
    # plot the remaining attributes
    values = node_attributes(network, attributes, nodes=nodes)
    values_ref = node_attributes(network, attributes, nodes=reference_nodes)
    fig, axes = _set_new_plot(fignum=fig.number, names=attributes)
    for i, (attr, val) in enumerate(values.items()):
        counts, bins = np.histogram(val, num_bins[i])
        bins = bins[:-1] + 0.5*np.diff(bins)
        counts_ref, bins_ref = np.histogram(values_ref[attr], num_bins[i])
        bins_ref = bins_ref[:-1] + 0.5*np.diff(bins_ref)
        # normalize
        counts = counts / float(np.sum(counts))
        counts_ref = counts_ref / float(np.sum(counts_ref))
        # reference nodes
        axes[i].plot(
            bins_ref, counts_ref, ls="--", c=reference_color[0], marker="o",
            label="reference")
        # nodes
        axes[i].plot(bins, counts, ls="--", marker="o", label="nodes")
        axes[i].set_xlabel(attr[0].upper() + attr[1:])
        axes[i].set_ylabel("Node count")
        axes[i].set_title("{}{} distribution for {}".format(
            attr[0].upper(), attr[1:], network.name), loc='left', x=0., y=1.05)
        axes[i].legend(loc='center', bbox_to_anchor=[0.9, 1.], ncol=1,
                   frameon=True)
    # adjust space, set title, and show
    _format_and_show(fig, num_plot, values, title, show)
Beispiel #5
0
def node_attributes_distribution(network, attributes, nodes=None, num_bins=50,
                                 show=True):
    '''
    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"
        * "b2" (requires NEST)
        * "firing_rate" (requires NEST)
    nodes : list, optional (default: all nodes)
        Nodes for which the attributes should be returned.
    num_bins : int or list, optional (default: 50)
        Number of bins to plot the distributions. If only one int is provided,
        it is used for all attributes, otherwize a list containing one int per
        attribute in `attributes` is required.
    '''
    if not nonstring_container(attributes):
        attributes = [attributes]
    else:
        attributes = [name for name in attributes]
    if nonstring_container(num_bins):
        assert len(num_bins) == len(attributes), "One entry per attribute " +\
            "required for `num_bins`."
    else:
        num_bins = [num_bins for _ in range(len(attributes))]
    fig = plt.figure()
    num_plot = 0
    # plot degrees if required
    degrees = []
    for name in attributes:
        if "degree" in name.lower():
            degrees.append(name[:name.find("-")])
    if degrees:
        indices = []
        for i, name in enumerate(attributes):
            if "degree" in name:
                indices.append(i)
        indices.sort()
        deg_bin = int(np.average(np.array(num_bins)[indices]))
        for idx in indices[::-1]:
            del num_bins[idx]
            del attributes[idx]
        degree_distribution(
            network, deg_type=degrees, nodes=nodes, num_bins=deg_bin,
            fignum=fig.number, show=False)
        num_plot += 1
    # plot betweenness if needed
    if "betweenness" in attributes:
        idx = attributes.index("betweenness")
        betweenness_distribution(
            network, btype="node", nodes=nodes, fignum=fig.number, show=False)
        del attributes[idx]
        del num_bins[idx]
        num_plot += 1
    # plot the remaining attributes
    values = node_attributes(network, attributes, nodes=None)
    fig, axes = _set_new_plot(fignum=fig.number, names=attributes)
    for i, (attr, val) in enumerate(values.items()):
        counts, bins = np.histogram(val, num_bins[i])
        bins = bins[:-1] + 0.5*np.diff(bins)
        axes[i].plot(bins, counts, ls="--", marker="o")
        axes[i].set_title("{}{} distribution for {}".format(
            attr[0].upper(), attr[1:], network.name), x=0., y=1.05)
    # adjust space, set title, and show
    _format_and_show(fig, num_plot, values, title, show)
Beispiel #6
0
def node_attributes_distribution(network,
                                 attributes,
                                 nodes=None,
                                 num_bins='auto',
                                 logx=False,
                                 logy=False,
                                 norm=False,
                                 title=None,
                                 colors=None,
                                 show=True,
                                 **kwargs):
    '''
    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"
        * "b2" (requires NEST)
        * "firing_rate" (requires NEST)
    nodes : list, optional (default: all nodes)
        Nodes for which the attributes should be returned.
    num_bins : int or list, optional (default: 'auto')
        Number of bins to plot the distributions. If only one int is provided,
        it is used for all attributes, otherwize a list containing one int per
        attribute in `attributes` is required. Defaults to unsupervised
        Bayesian blocks method.
    logx : bool or list, optional (default: False)
        Use log-spaced bins.
    logy : bool or list, optional (default: False)
        use logscale for the node count.
    '''
    import matplotlib.pyplot as plt
    if not nonstring_container(attributes):
        attributes = [attributes]
    else:
        attributes = [name for name in attributes]
    num_attr = len(attributes)
    num_bins = _format_arg(num_bins, num_attr, 'num_bins')
    colors = _format_arg(colors, num_attr, 'num_bins')
    logx = _format_arg(logx, num_attr, 'logx')
    logy = _format_arg(logy, num_attr, 'logy')
    num_plot = 0
    # kwargs that will not be passed:
    ignore = ["degree", "betweeness"] + attributes
    new_kwargs = {k: v for k, v in kwargs.items() if k not in ignore}
    fig = None
    if new_kwargs == kwargs:
        fig = plt.figure()
    else:
        fig = plt.figure(plt.get_fignums()[-1])
    fig.patch.set_visible(False)
    # plot degrees if required
    degrees = []
    for name in attributes:
        if "degree" in name.lower():
            degrees.append(name[:name.find("-")])
    if degrees:
        # get the indices where a degree-related attribute is required
        indices, colors_deg, logx_deg, logy_deg = [], [], 0, 0
        for i, name in enumerate(attributes):
            if "degree" in name:
                indices.append(i)
                if colors is not None:
                    colors_deg.append(colors[i])
                logx_deg += logx[i]
                logy_deg += logy[i]
        colors_deg = None if colors is None else colors_deg
        indices.sort()
        deg_bin = [num_bins[i] for i in indices]
        for idx in indices[::-1]:
            del num_bins[idx]
            del attributes[idx]
            del logx[idx]
            del logy[idx]
            if colors is not None:
                del colors[idx]
        if "degree" in kwargs:
            degree_distribution(network,
                                deg_type=degrees,
                                nodes=nodes,
                                num_bins=deg_bin,
                                logx=logx_deg,
                                logy=logy_deg,
                                norm=norm,
                                axis=kwargs["degree"],
                                colors=colors_deg,
                                show=False,
                                **new_kwargs)
        else:
            fig, ax = _set_new_plot(fignum=fig.number,
                                    num_new_plots=1,
                                    names=['Degree distribution'])
            degree_distribution(network,
                                deg_type=degrees,
                                nodes=nodes,
                                num_bins=deg_bin,
                                logx=logx_deg,
                                logy=logy_deg,
                                axis=ax[0],
                                colors=colors_deg,
                                norm=norm,
                                show=False)
        num_plot += 1
    # plot betweenness if needed
    if "betweenness" in attributes:
        idx = attributes.index("betweenness")
        if "betweenness" in kwargs:
            betweenness_distribution(network,
                                     btype="node",
                                     nodes=nodes,
                                     logx=logx[idx],
                                     logy=logy[idx],
                                     axes=kwargs["betweenness"],
                                     colors=[colors[idx]],
                                     norm=norm,
                                     show=False,
                                     **new_kwargs)
        else:
            fig, axes = _set_new_plot(fignum=fig.number,
                                      num_new_plots=1,
                                      names=['Betweenness distribution'])
            betweenness_distribution(network,
                                     btype="node",
                                     nodes=nodes,
                                     logx=logx[idx],
                                     logy=logy[idx],
                                     norm=norm,
                                     axes=axes,
                                     show=False)
        del attributes[idx]
        del num_bins[idx]
        del logx[idx]
        del logy[idx]
        if colors is not None:
            del colors[idx]
        num_plot += 1
    # plot the remaining attributes
    values = node_attributes(network, attributes, nodes=nodes)
    for i, (attr, val) in enumerate(values.items()):
        if attr in kwargs:
            new_kwargs['color'] = colors[i]
            counts, bins = _hist(val, num_bins[i], norm, logx[i], attr,
                                 kwargs[attr], **new_kwargs)
        else:
            fig, ax = _set_new_plot(fignum=fig.number, names=[attr])
            counts, bins = _hist(val, num_bins[i], norm, logx[i], attr, ax[0],
                                 **kwargs)
            end_attr = attr[1:]
            if nngt._config["use_tex"]:
                end_attr = end_attr.replace("_", "\\_")
            ax[0].set_title("{}{} distribution for {}".format(
                attr[0].upper(), end_attr, network.name),
                            y=1.05)
            ax[0].set_ylabel("Node count")
            ax[0].set_xlabel(attr[0].upper() + end_attr)
            _set_scale(ax[0], bins.max(), bins.min(), counts.max(), logx[i],
                       logy[i])
        num_plot += 1
    # adjust space, set title, and show
    _format_and_show(fig, num_plot, values, title, show)
Beispiel #7
0
def correlation_to_attribute(network,
                             reference_attribute,
                             other_attributes,
                             attribute_type="node",
                             nodes=None,
                             edges=None,
                             fig=None,
                             title=None,
                             show=True):
    '''
    For each node plot the value of `reference_attributes` against each of the
    `other_attributes` to check for correlations.

    .. versionchanged :: 2.0
        Added `fig` argument.

    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"
        * "in-strength", "out-strength", "total-strength"
        * "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.
    attribute_type : str, optional (default: 'node')
        Whether we are dealing with 'node' or 'edge' attributes
    nodes : list, optional (default: all nodes)
        Nodes for which the attributes should be returned.
    edges : list, optional (default: all edges)
        Edges for which the attributes should be returned.
    fig : :class:`matplotlib.figure.Figure`, optional (default: new Figure)
        Figure to which the plot should be added.
    title : str, optional (default: automatic).
        Custom title, use "" to remove the automatic title.
    show : bool, optional (default: True)
        Whether the plot should be displayed immediately.
    '''
    import matplotlib.pyplot as plt

    if not nonstring_container(other_attributes):
        other_attributes = [other_attributes]

    fig = plt.figure() if fig is None else fig
    fig.patch.set_visible(False)

    # get reference data
    ref_data = reference_attribute

    if isinstance(reference_attribute, str):
        if attribute_type == "node":
            ref_data = node_attributes(network,
                                       reference_attribute,
                                       nodes=nodes)
        else:
            ref_data = network.get_edge_attributes(edges=edges,
                                                   name=reference_attribute)
    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 = {}

    if attribute_type == "node":
        values = node_attributes(network, other_attributes, nodes=nodes)
    else:
        for name in other_attributes:
            values[name] = network.get_edge_attributes(edges=edges, name=name)

    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 {}".format(
                reference_attribute[0].upper(), end_ref_attr, attr[0] + \
                end_attr),
            loc='left', x=0., y=1.05)

    fig.suptitle(network.name)

    plt.tight_layout()

    # adjust space, set title, and show
    _format_and_show(fig, 0, values, title, show)
Beispiel #8
0
def node_attributes_distribution(network,
                                 attributes,
                                 nodes=None,
                                 num_bins='auto',
                                 logx=False,
                                 logy=False,
                                 norm=False,
                                 title=None,
                                 axtitles=None,
                                 colors=None,
                                 axes=None,
                                 show=True,
                                 **kwargs):
    '''
    Return node `attributes` for a set of `nodes`.

    .. versionchanged :: 2.5.0
        Added `axtitles` and `axes` arguments.

    Parameters
    ----------
    network : :class:`~nngt.Graph`
        The graph where the `nodes` belong.
    attributes : str or list
        Attributes which should be returned, among:
        * any user-defined node attribute
        * "betweenness"
        * "clustering"
        * "closeness"
        * "in-degree", "out-degree", "total-degree"
        * "subgraph_centrality"
        * "b2" (requires NEST)
        * "firing_rate" (requires NEST)
    nodes : list, optional (default: all nodes)
        Nodes for which the attributes should be returned.
    num_bins : int or list, optional (default: 'auto')
        Number of bins to plot the distributions. If only one int is provided,
        it is used for all attributes, otherwise a list containing one int per
        attribute in `attributes` is required. Defaults to unsupervised
        Bayesian blocks method.
    logx : bool or list, optional (default: False)
        Use log-spaced bins.
    logy : bool or list, optional (default: False)
        use logscale for the node count.
    norm : bool, optional (default: False)
        Whether the histogram should be normed such that the sum of the counts
        is 1.
    title : str, optional (default: no title)
        Title of the figure.
    axtitles : list of str, optional (default: auto-generated)
        Titles of the axes. Use "" or False to turn them of.
    colors : (list of) matplotlib colors, optional (default: from palette)
        Colors associated to each degree type.
    axes : list of :class:`matplotlib.axis.Axis`, optional (default: new ones)
        Axess which should be used to plot the histograms, if None, a new axis
        is created for each attribute.
    show : bool, optional (default: True)
        Show the Figure right away if True, else keep it warm for later use.
    **kwargs : keyword arguments for :func:`matplotlib.axes.Axes.bar`.
    '''
    import matplotlib.pyplot as plt

    if not nonstring_container(attributes):
        attributes = [attributes]
    else:
        attributes = [name for name in attributes]

    if axtitles in ("", False):
        axtitles = [""] * len(attributes)
    elif nonstring_container(axtitles):
        assert len(axtitles) == len(attributes), \
            "One entry per attribute is required for `axtitles`."

    num_attr = len(attributes)
    num_bins = _format_arg(num_bins, num_attr, 'num_bins')
    colors = _format_arg(colors, num_attr, 'num_bins')
    logx = _format_arg(logx, num_attr, 'logx')
    logy = _format_arg(logy, num_attr, 'logy')
    num_plot = 0

    # kwargs that will not be passed:
    ignore = ["degree", "betweenness"] + attributes
    new_kwargs = {k: v for k, v in kwargs.items() if k not in ignore}

    fig = None

    if axes is None:
        if new_kwargs == kwargs:
            fig = plt.figure()
            fig.patch.set_visible(False)
        else:
            fig = plt.figure(plt.get_fignums()[-1])
    else:
        if not nonstring_container(axes):
            axes = [axes]

        fig = axes[0].get_figure()

        assert len(axes) == len(attributes), \
            "One entry per attribute is required."

    # plot degrees if required
    degrees = []

    for name in attributes:
        if "degree" in name.lower():
            degrees.append(name[:name.find("-")])

    if degrees:
        # get the indices where a degree-related attribute is required
        indices, colors_deg, logx_deg, logy_deg = [], [], 0, 0

        for i, name in enumerate(attributes):
            if "degree" in name:
                indices.append(i)
                if colors is not None:
                    colors_deg.append(colors[i])
                logx_deg += logx[i]
                logy_deg += logy[i]

        colors_deg = None if colors is None else colors_deg

        indices.sort()

        deg_bin = [num_bins[i] for i in indices]

        for idx in indices[::-1]:
            del num_bins[idx]
            del attributes[idx]
            del logx[idx]
            del logy[idx]

            if colors is not None:
                del colors[idx]

        if "degree" in kwargs:
            degree_distribution(network,
                                deg_type=degrees,
                                nodes=nodes,
                                num_bins=deg_bin,
                                logx=logx_deg,
                                logy=logy_deg,
                                norm=norm,
                                axis=kwargs["degree"],
                                colors=colors_deg,
                                show=False,
                                **new_kwargs)
        else:
            ax = None

            if axes is None:
                fig, ax = _set_new_plot(fignum=fig.number,
                                        num_new_plots=1,
                                        names=['Degree distribution'])
            else:
                ax = [axes[indices[0]]]

                for idx in indices:
                    del axes[idx]

            degree_distribution(network,
                                deg_type=degrees,
                                nodes=nodes,
                                num_bins=deg_bin,
                                logx=logx_deg,
                                logy=logy_deg,
                                axis=ax[0],
                                colors=colors_deg,
                                norm=norm,
                                show=False)

        num_plot += 1

    # plot betweenness if needed
    if "betweenness" in attributes:
        idx = attributes.index("betweenness")

        if "betweenness" in kwargs:
            betweenness_distribution(network,
                                     btype="node",
                                     nodes=nodes,
                                     logx=logx[idx],
                                     logy=logy[idx],
                                     axes=kwargs["betweenness"],
                                     colors=[colors[idx]],
                                     norm=norm,
                                     show=False,
                                     **new_kwargs)
        else:
            ax = None

            if axes is None:
                fig, ax = _set_new_plot(fignum=fig.number,
                                        num_new_plots=1,
                                        names=['Betweenness distribution'])
            else:
                ax = [axes[idx]]

                del axes[idx]

            betweenness_distribution(network,
                                     btype="node",
                                     nodes=nodes,
                                     logx=logx[idx],
                                     axes=ax,
                                     logy=logy[idx],
                                     colors=[colors[idx]],
                                     norm=norm,
                                     show=False)

        del attributes[idx]
        del num_bins[idx]
        del logx[idx]
        del logy[idx]
        del colors[idx]

        num_plot += 1

    # plot the remaining attributes
    values = node_attributes(network, attributes, nodes=nodes)

    for i, (attr, val) in enumerate(values.items()):
        if attr in kwargs:
            new_kwargs['color'] = colors[i]
            counts, bins = _hist(val, num_bins[i], norm, logx[i], attr,
                                 kwargs[attr], **new_kwargs)
        else:
            ax = None

            if axes is None:
                fig, ax = _set_new_plot(fignum=fig.number, names=[attr])
                ax = ax[0]
            else:
                ax = axes[i]

            counts, bins = _hist(val,
                                 num_bins[i],
                                 norm,
                                 logx[i],
                                 attr,
                                 ax,
                                 color=colors[i],
                                 **kwargs)

            end_attr = attr[1:]

            if nngt._config["use_tex"]:
                end_attr = end_attr.replace("_", "\\_")

            ax.set_title("{}{} distribution for {}".format(
                attr[0].upper(), end_attr, network.name),
                         y=1.05)
            ax.set_ylabel("Node count")
            ax.set_xlabel(attr[0].upper() + end_attr)
            _set_scale(ax, bins, np.min(counts[counts > 0]), counts.max(),
                       logx[i], logy[i])

        num_plot += 1

    # adjust space, set title, and show
    _format_and_show(fig, num_plot, values, title, show)