liveBook · Manning

Graph

As explained in Financial AI in Practice

A structure representing entities as nodes and relationships between them as edges.

A fraud analyst investigating chargebacks needs to see more than isolated transaction rows. A graph represents financial entities as nodes and their relationships as edges, making shared merchants, devices, or IP addresses visible across transactions. section 7.4.1 section 9.2

Choose the representation around the relationship you need to investigate:

  • Nodes can represent users, accounts, merchants, devices, IP addresses, or transactions. section 9.2.1
  • Edges can represent transactions, ownership, transfers, or shared identifiers. An edge may carry a timestamp, amount, or count; a node may carry a creation date or risk score. section 9.2.1
  • Directed, undirected, weighted, and heterogeneous representations serve different fraud patterns; the choice determines which connections the graph preserves. section 9.6

From transaction table to network

With NetworkX, add each distinct user, IP address, device, phone number, and credit card as a typed node. Connect entities appearing in the same transaction, and use an edge weight to count how many transactions link each pair. In the illustrated run, 14,813 transactions produce 14,906 nodes and 30,567 edges. Check those counts after construction before applying community detection to seek densely connected groups. section 9.3.2

Listing 9.2 Building the graph from transaction data with parallel edge preparation
if transactions_df is not None:
    G = nx.Graph()            #1

    node_types_and_columns = {
        'user': 'user_id', 'ip': 'ip', 'device': 'device_id',
        'phone': 'phone_number', 'card': 'credit_card_number'
    }

    print("Preparing nodes for the graph...")
    for node_type, col_name in node_types_and_columns.items():
        for entity_id in transactions_df[col_name].unique():
            G.add_node(f"{node_type}_{entity_id}", type=node_type) 
    print(f"Initial nodes added. Total nodes: {G.number_of_nodes()}")

    edges_to_add_with_attributes = defaultdict(lambda: {'weight': 0, 'transactions': []})

    def process_transaction_row(index_row_tuple):
        index, row = index_row_tuple
        transaction_entities_in_row = []
        for node_type_key, col_name_val in node_types_and_columns.items():
            transaction_entities_in_row.append(f"{node_type_key}_{row[col_name_val]}")
        row_edges = []
        for node1, node2 in combinations(sorted(transaction_entities_in_row), 2):
            row_edges.append(((node1, node2), row['transaction_id']))
        return row_edges

    print("\nProcessing transactions in parallel to prepare edge data...")

    prepared_edges_nested_list = []
    with ThreadPoolExecutor(max_workers=8) as executor: # Using 8 worker threads
        futures = [executor.submit(process_transaction_row, item) for item in transactions_df.iterrows()]
        count = 0
        for future in as_completed(futures):
            prepared_edges_nested_list.extend(future.result())
            count += 1
            if count % 1000 == 0 and count > 0: # Print progress every 1000 transactions
                print(f"Processed {count} transactions for edge preparation...")

    print(f"All {len(transactions_df)} transactions processed for edge preparation.")

    print("\nAggregating edge data and adding edges to the graph...")
    for (node1, node2), transaction_id in prepared_edges_nested_list:
        edges_to_add_with_attributes[(node1, node2)]['weight'] += 1
        edges_to_add_with_attributes[(node1, node2)]['transactions'].append(transaction_id)

    for (node1, node2), attributes in edges_to_add_with_attributes.items():
        G.add_edge(node1, node2, weight=attributes['weight'], transactions=attributes['transactions'])

    print("\nGraph construction completed.")
    print(f"Number of nodes in the graph: {G.number_of_nodes()}")
    print(f"Number of edges in the graph: {G.number_of_edges()}")
else:
    print("transactions_df not loaded. Skipping graph construction.")
    G = None

The construction listing shows typed node IDs, transaction-derived edges, weights, and parallel edge preparation. section 9.3.2

A dense cluster or a device shared by many users can guide an investigation, not by itself settle whether fraud occurred. Community detection and visualization help surface such structures; Graph Neural Networks extend the approach by learning from node attributes and graph structure, but bring scaling and explainability challenges. section 9.6

How Financial AI in Practice covers it

How deep it goes
Defines nodes, edges, and properties, then details NetworkX construction; defers advanced GNN mathematics.
Examples
Illustrate chargeback users sharing merchants and a transaction-derived graph of users, IPs, devices, phones, and cards.
What it assumes
Start with tabular transaction data and Python tooling; learn graph terminology before constructing the network.
What you can do after
Build it from scratch with NetworkX using transaction tables, then inspect weighted connections and densely connected groups.
How it connects
Connect graphs to community detection, the Louvain method, visualization, and Graph Neural Networks.

Also explained in

Compare how 21 books explain graph →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Financial AI in Practice. The section links point to the parts of the book it draws on.