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




















