最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Graph modularity implementation in Python - Stack Overflow

programmeradmin2浏览0评论

I am trying to implement my own version of the following modularity definition for a graph: Graph modularity metric

I am using graph-tool Python library to define a single layer graph structure and have defined several graphs using my singlelayer_graph method. Precisely, I have constructed several graphs from this paper (Figure 4): Clustering attributed graphs. My problem is that I am getting slighly different modularity values to those reported in the paper.

Here is my single graph construction method:

def singlelayer_graph(edges: Edges, directed: bool = True, edge_colors: dict[tuple[int, int]] = None) -> gt.Graph:
    """
    Initializes a single-layer directed graph with optional edge colors.


    Args:
        edges: A list of tuples of the form (source_id, target_id, weight).
        directed: Optional. A boolean flag that defines the directionality of edges (directed by default)
        edge_colors: Optional. A dictionary where keys are (source, target) tuples and values
                     are RGB color lists for the edges.

    Returns:
        A gt.Graph object with the following properties:
        - original_id (vertex property): The original node ID mapping.
        - weight (edge property): The weight of each edge.
        - edge_color (edge property, if provided): The color of each edge.
    """
    G = gt.Graph(directed=directed)

    # Define graph properties
    G.gp["num_edges"] = G.new_graph_property("int")
    G.gp["num_nodes"] = G.new_graph_property("int")

    G.gp["num_edges"] = len(edges)
    num_nodes = len(set(node for edge in edges for node in edge[:2]))
    G.gp["num_nodes"] = num_nodes

    # Define vertex properties
    G.vp["original_id"] = G.new_vertex_property("int")  # Store original IDs
    G.ep["weight"] = G.new_edge_property("int")

    # Add edge color property if edge_colors is provided
    if edge_colors:
        G.ep["edge_color"] = G.new_edge_property("vector<double>")

    # Create a mapping from original node IDs to graph vertices
    node_map = {}

    for source, target, weight in edges:
        # Ensure both source and target nodes exist in the graph
        if source not in node_map:
            v_source = G.add_vertex()
            G.vp["original_id"][v_source] = source
            node_map[source] = v_source

        if target not in node_map:
            v_target = G.add_vertex()
            G.vp["original_id"][v_target] = target
            node_map[target] = v_target

        # Add edge using mapped vertices
        edge = G.add_edge(node_map[source], node_map[target])
        G.ep["weight"][edge] = weight  # Assign weight

        # Assign edge color if edge_colors is provided
        if edge_colors and (source, target) in edge_colors:
            G.ep["edge_color"][edge] = edge_colors[(source, target)]  # Assign color

    return G

Here is my graph definition:

def bothorel_graph_3():

    es = [(1, 2, 1),
          (1, 3, 1),
          (2, 3, 1),
          (3, 4, 1),
          (2, 4, 1),
          (4, 5, 1),
          (5, 6, 1),
          (4, 7, 1),
          (6, 7, 1),
          (7, 8, 1)]

    # Create  graph
    G = singlelayer_graph(edges=es, directed=False)

    # Assign communities by (original) node ID
    communities = {
        1: 0, 2: 0, 3: 0,
        4: 1, 5: 1, 6: 1, 7: 1, 8: 1
    }

    # Create a new vertex property for communities
    G.vp["community"] = G.new_vertex_property("int")

    # Assign communities based on original IDs
    for v in G.vertices():
        G.vp["community"][v] = communities[G.vp["original_id"][v]]

    return G

And my modularity function:

def graph_modularity(graph: gt.Graph) -> float:
    """
    Computes the modularity of the input graph (supports both directed and undirected graphs).

    Args:
        graph (gt.Graph): The graph-tool Graph object.

    Returns:
        float: The modularity score of the graph.
    """
    # Ensure the graph has community assignments
    if "community" not in graph.vp:
        raise ValueError("Community assignment missing. Ensure 'community' vertex property is set.")

    # Check if the graph is directed
    is_directed = graph.is_directed()

    print(f"Graph is directed: {is_directed}")

    # Set m to the sum of weights (total number of edges in an undirected graph)
    # Convert edge weight sum to Decimal
    m = Decimal(sum(Decimal(graph.ep["weight"][e]) for e in graph.edges()))

    print(f"Total edge weight (m): {m}")

    modularity = Decimal(0)

    # List of all vertices and their corresponding communities
    all_vertices = list(graph.vertices())

    # Compute modularity contribution for all pairs (i, j)
    for i in range(len(all_vertices)):
        for j in range(len(all_vertices)):  # Consider all pairs for directed graphs

            if i == j:
                continue  # Skip self-loops

            vi, vj = all_vertices[i], all_vertices[j]
            ci, cj = graph.vp["community"][vi], graph.vp["community"][vj]

            # Check if there is an edge between vi and vj
            edge = graph.edge(vi, vj)
            A_ij = Decimal(graph.ep["weight"][edge]) if edge is not None else Decimal(0)  # Edge weight or 0 if no edge

            # Compute degree based on directed or undirected graph
            if is_directed:
                k_i_out = sum(Decimal(graph.ep["weight"][e]) for e in vi.out_edges())  # Out-degree
                k_j_in = sum(Decimal(graph.ep["weight"][e]) for e in vj.in_edges())    # In-degree
                expected_weight = (k_i_out * k_j_in) / m
            else:

                k_i = sum(Decimal(graph.ep["weight"][e]) for e in vi.all_edges())
                k_j = sum(Decimal(graph.ep["weight"][e]) for e in vj.all_edges())
                expected_weight = (k_i * k_j) / (2 * m)

            if ci == cj:
                contribution = A_ij - expected_weight
            else:
                contribution = Decimal(0)

            modularity += contribution

            # Debugging prints for each pair (optional)
            # Print detailed debug info
            print(f"Pair ({graph.vp["original_id"][i]}, {graph.vp["original_id"][j]}):")
            print(f"  Community of {graph.vp["original_id"][i]}: {ci}, Community of {graph.vp["original_id"][j]}: {cj}")
            print(f"  A_ij: {A_ij}, k_i: {k_i}, k_j: {k_j}")
            print(f"  Expected Weight: {expected_weight:.6f}")
            print(f"  Contribution: {contribution:.6f}")
            print(f"  Modularity So Far: {modularity:.6f}\n")
    if is_directed:
        # Normalize and return modularity with precision
        modularity = (modularity / m).quantize(Decimal("0.000001"), rounding=ROUND_HALF_UP)
    else:
        # Normalize and return modularity with precision
        modularity = (modularity / (2 * m)).quantize(Decimal("0.000001"), rounding=ROUND_HALF_UP)

    return modularity

I am getting modularity value 0.42 while the paper reports a value of 0.28. I do not understand what is wrong with my implementation. See full code output below.

Graph is directed: False
Total edge weight (m): 10
Pair (1, 2):
  Community of 1: 0, Community of 2: 0
  A_ij: 1, k_i: 2, k_j: 3
  Expected Weight: 0.300000
  Contribution: 0.700000
  Modularity So Far: 0.700000

Pair (1, 3):
  Community of 1: 0, Community of 3: 0
  A_ij: 1, k_i: 2, k_j: 3
  Expected Weight: 0.300000
  Contribution: 0.700000
  Modularity So Far: 1.400000

Pair (1, 4):
  Community of 1: 0, Community of 4: 1
  A_ij: 0, k_i: 2, k_j: 4
  Expected Weight: 0.400000
  Contribution: 0.000000
  Modularity So Far: 1.400000

Pair (1, 5):
  Community of 1: 0, Community of 5: 1
  A_ij: 0, k_i: 2, k_j: 2
  Expected Weight: 0.200000
  Contribution: 0.000000
  Modularity So Far: 1.400000

Pair (1, 6):
  Community of 1: 0, Community of 6: 1
  A_ij: 0, k_i: 2, k_j: 2
  Expected Weight: 0.200000
  Contribution: 0.000000
  Modularity So Far: 1.400000

Pair (1, 7):
  Community of 1: 0, Community of 7: 1
  A_ij: 0, k_i: 2, k_j: 3
  Expected Weight: 0.300000
  Contribution: 0.000000
  Modularity So Far: 1.400000

Pair (1, 8):
  Community of 1: 0, Community of 8: 1
  A_ij: 0, k_i: 2, k_j: 1
  Expected Weight: 0.100000
  Contribution: 0.000000
  Modularity So Far: 1.400000

Pair (2, 1):
  Community of 2: 0, Community of 1: 0
  A_ij: 1, k_i: 3, k_j: 2
  Expected Weight: 0.300000
  Contribution: 0.700000
  Modularity So Far: 2.100000

Pair (2, 3):
  Community of 2: 0, Community of 3: 0
  A_ij: 1, k_i: 3, k_j: 3
  Expected Weight: 0.450000
  Contribution: 0.550000
  Modularity So Far: 2.650000

Pair (2, 4):
  Community of 2: 0, Community of 4: 1
  A_ij: 1, k_i: 3, k_j: 4
  Expected Weight: 0.600000
  Contribution: 0.000000
  Modularity So Far: 2.650000

Pair (2, 5):
  Community of 2: 0, Community of 5: 1
  A_ij: 0, k_i: 3, k_j: 2
  Expected Weight: 0.300000
  Contribution: 0.000000
  Modularity So Far: 2.650000

Pair (2, 6):
  Community of 2: 0, Community of 6: 1
  A_ij: 0, k_i: 3, k_j: 2
  Expected Weight: 0.300000
  Contribution: 0.000000
  Modularity So Far: 2.650000

Pair (2, 7):
  Community of 2: 0, Community of 7: 1
  A_ij: 0, k_i: 3, k_j: 3
  Expected Weight: 0.450000
  Contribution: 0.000000
  Modularity So Far: 2.650000

Pair (2, 8):
  Community of 2: 0, Community of 8: 1
  A_ij: 0, k_i: 3, k_j: 1
  Expected Weight: 0.150000
  Contribution: 0.000000
  Modularity So Far: 2.650000

Pair (3, 1):
  Community of 3: 0, Community of 1: 0
  A_ij: 1, k_i: 3, k_j: 2
  Expected Weight: 0.300000
  Contribution: 0.700000
  Modularity So Far: 3.350000

Pair (3, 2):
  Community of 3: 0, Community of 2: 0
  A_ij: 1, k_i: 3, k_j: 3
  Expected Weight: 0.450000
  Contribution: 0.550000
  Modularity So Far: 3.900000

Pair (3, 4):
  Community of 3: 0, Community of 4: 1
  A_ij: 1, k_i: 3, k_j: 4
  Expected Weight: 0.600000
  Contribution: 0.000000
  Modularity So Far: 3.900000

Pair (3, 5):
  Community of 3: 0, Community of 5: 1
  A_ij: 0, k_i: 3, k_j: 2
  Expected Weight: 0.300000
  Contribution: 0.000000
  Modularity So Far: 3.900000

Pair (3, 6):
  Community of 3: 0, Community of 6: 1
  A_ij: 0, k_i: 3, k_j: 2
  Expected Weight: 0.300000
  Contribution: 0.000000
  Modularity So Far: 3.900000

Pair (3, 7):
  Community of 3: 0, Community of 7: 1
  A_ij: 0, k_i: 3, k_j: 3
  Expected Weight: 0.450000
  Contribution: 0.000000
  Modularity So Far: 3.900000

Pair (3, 8):
  Community of 3: 0, Community of 8: 1
  A_ij: 0, k_i: 3, k_j: 1
  Expected Weight: 0.150000
  Contribution: 0.000000
  Modularity So Far: 3.900000

Pair (4, 1):
  Community of 4: 1, Community of 1: 0
  A_ij: 0, k_i: 4, k_j: 2
  Expected Weight: 0.400000
  Contribution: 0.000000
  Modularity So Far: 3.900000

Pair (4, 2):
  Community of 4: 1, Community of 2: 0
  A_ij: 1, k_i: 4, k_j: 3
  Expected Weight: 0.600000
  Contribution: 0.000000
  Modularity So Far: 3.900000

Pair (4, 3):
  Community of 4: 1, Community of 3: 0
  A_ij: 1, k_i: 4, k_j: 3
  Expected Weight: 0.600000
  Contribution: 0.000000
  Modularity So Far: 3.900000

Pair (4, 5):
  Community of 4: 1, Community of 5: 1
  A_ij: 1, k_i: 4, k_j: 2
  Expected Weight: 0.400000
  Contribution: 0.600000
  Modularity So Far: 4.500000

Pair (4, 6):
  Community of 4: 1, Community of 6: 1
  A_ij: 0, k_i: 4, k_j: 2
  Expected Weight: 0.400000
  Contribution: -0.400000
  Modularity So Far: 4.100000

Pair (4, 7):
  Community of 4: 1, Community of 7: 1
  A_ij: 1, k_i: 4, k_j: 3
  Expected Weight: 0.600000
  Contribution: 0.400000
  Modularity So Far: 4.500000

Pair (4, 8):
  Community of 4: 1, Community of 8: 1
  A_ij: 0, k_i: 4, k_j: 1
  Expected Weight: 0.200000
  Contribution: -0.200000
  Modularity So Far: 4.300000

Pair (5, 1):
  Community of 5: 1, Community of 1: 0
  A_ij: 0, k_i: 2, k_j: 2
  Expected Weight: 0.200000
  Contribution: 0.000000
  Modularity So Far: 4.300000

Pair (5, 2):
  Community of 5: 1, Community of 2: 0
  A_ij: 0, k_i: 2, k_j: 3
  Expected Weight: 0.300000
  Contribution: 0.000000
  Modularity So Far: 4.300000

Pair (5, 3):
  Community of 5: 1, Community of 3: 0
  A_ij: 0, k_i: 2, k_j: 3
  Expected Weight: 0.300000
  Contribution: 0.000000
  Modularity So Far: 4.300000

Pair (5, 4):
  Community of 5: 1, Community of 4: 1
  A_ij: 1, k_i: 2, k_j: 4
  Expected Weight: 0.400000
  Contribution: 0.600000
  Modularity So Far: 4.900000

Pair (5, 6):
  Community of 5: 1, Community of 6: 1
  A_ij: 1, k_i: 2, k_j: 2
  Expected Weight: 0.200000
  Contribution: 0.800000
  Modularity So Far: 5.700000

Pair (5, 7):
  Community of 5: 1, Community of 7: 1
  A_ij: 0, k_i: 2, k_j: 3
  Expected Weight: 0.300000
  Contribution: -0.300000
  Modularity So Far: 5.400000

Pair (5, 8):
  Community of 5: 1, Community of 8: 1
  A_ij: 0, k_i: 2, k_j: 1
  Expected Weight: 0.100000
  Contribution: -0.100000
  Modularity So Far: 5.300000

Pair (6, 1):
  Community of 6: 1, Community of 1: 0
  A_ij: 0, k_i: 2, k_j: 2
  Expected Weight: 0.200000
  Contribution: 0.000000
  Modularity So Far: 5.300000

Pair (6, 2):
  Community of 6: 1, Community of 2: 0
  A_ij: 0, k_i: 2, k_j: 3
  Expected Weight: 0.300000
  Contribution: 0.000000
  Modularity So Far: 5.300000

Pair (6, 3):
  Community of 6: 1, Community of 3: 0
  A_ij: 0, k_i: 2, k_j: 3
  Expected Weight: 0.300000
  Contribution: 0.000000
  Modularity So Far: 5.300000

Pair (6, 4):
  Community of 6: 1, Community of 4: 1
  A_ij: 0, k_i: 2, k_j: 4
  Expected Weight: 0.400000
  Contribution: -0.400000
  Modularity So Far: 4.900000

Pair (6, 5):
  Community of 6: 1, Community of 5: 1
  A_ij: 1, k_i: 2, k_j: 2
  Expected Weight: 0.200000
  Contribution: 0.800000
  Modularity So Far: 5.700000

Pair (6, 7):
  Community of 6: 1, Community of 7: 1
  A_ij: 1, k_i: 2, k_j: 3
  Expected Weight: 0.300000
  Contribution: 0.700000
  Modularity So Far: 6.400000

Pair (6, 8):
  Community of 6: 1, Community of 8: 1
  A_ij: 0, k_i: 2, k_j: 1
  Expected Weight: 0.100000
  Contribution: -0.100000
  Modularity So Far: 6.300000

Pair (7, 1):
  Community of 7: 1, Community of 1: 0
  A_ij: 0, k_i: 3, k_j: 2
  Expected Weight: 0.300000
  Contribution: 0.000000
  Modularity So Far: 6.300000

Pair (7, 2):
  Community of 7: 1, Community of 2: 0
  A_ij: 0, k_i: 3, k_j: 3
  Expected Weight: 0.450000
  Contribution: 0.000000
  Modularity So Far: 6.300000

Pair (7, 3):
  Community of 7: 1, Community of 3: 0
  A_ij: 0, k_i: 3, k_j: 3
  Expected Weight: 0.450000
  Contribution: 0.000000
  Modularity So Far: 6.300000

Pair (7, 4):
  Community of 7: 1, Community of 4: 1
  A_ij: 1, k_i: 3, k_j: 4
  Expected Weight: 0.600000
  Contribution: 0.400000
  Modularity So Far: 6.700000

Pair (7, 5):
  Community of 7: 1, Community of 5: 1
  A_ij: 0, k_i: 3, k_j: 2
  Expected Weight: 0.300000
  Contribution: -0.300000
  Modularity So Far: 6.400000

Pair (7, 6):
  Community of 7: 1, Community of 6: 1
  A_ij: 1, k_i: 3, k_j: 2
  Expected Weight: 0.300000
  Contribution: 0.700000
  Modularity So Far: 7.100000

Pair (7, 8):
  Community of 7: 1, Community of 8: 1
  A_ij: 1, k_i: 3, k_j: 1
  Expected Weight: 0.150000
  Contribution: 0.850000
  Modularity So Far: 7.950000

Pair (8, 1):
  Community of 8: 1, Community of 1: 0
  A_ij: 0, k_i: 1, k_j: 2
  Expected Weight: 0.100000
  Contribution: 0.000000
  Modularity So Far: 7.950000

Pair (8, 2):
  Community of 8: 1, Community of 2: 0
  A_ij: 0, k_i: 1, k_j: 3
  Expected Weight: 0.150000
  Contribution: 0.000000
  Modularity So Far: 7.950000

Pair (8, 3):
  Community of 8: 1, Community of 3: 0
  A_ij: 0, k_i: 1, k_j: 3
  Expected Weight: 0.150000
  Contribution: 0.000000
  Modularity So Far: 7.950000

Pair (8, 4):
  Community of 8: 1, Community of 4: 1
  A_ij: 0, k_i: 1, k_j: 4
  Expected Weight: 0.200000
  Contribution: -0.200000
  Modularity So Far: 7.750000

Pair (8, 5):
  Community of 8: 1, Community of 5: 1
  A_ij: 0, k_i: 1, k_j: 2
  Expected Weight: 0.100000
  Contribution: -0.100000
  Modularity So Far: 7.650000

Pair (8, 6):
  Community of 8: 1, Community of 6: 1
  A_ij: 0, k_i: 1, k_j: 2
  Expected Weight: 0.100000
  Contribution: -0.100000
  Modularity So Far: 7.550000

Pair (8, 7):
  Community of 8: 1, Community of 7: 1
  A_ij: 1, k_i: 1, k_j: 3
  Expected Weight: 0.150000
  Contribution: 0.850000
  Modularity So Far: 8.400000

Modularity for graph 3: 0.420000
发布评论

评论列表(0)

  1. 暂无评论