Mathew K Analytics

Lesson 3 · Python for Banking and Finance

Implementing Control Flow for Transaction Logic in Python: Banking and Finance Applications

In this lesson, we will learn how to use Python control flow to handle banking transactions. Control flow helps ensure rules are followed, like flagging…

⬇ Download notebookOpen in Colab ↗

📓 Full notebook

Download .ipynb

Control Flow for Transaction Logic#

  • In this lesson, we will learn how to use Python control flow to handle banking transactions.
  • Control flow helps ensure rules are followed, like flagging large withdrawals and preventing fraudulent activity.
  • You will work with real-world-style synthetic transaction data.
  • By the end, you will write logic to detect suspicious transactions, approve or reject transfers, and summarize customer activity.
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')

Understanding our banking transactions data#

  • The dataset represents real-world-like banking transactions.
  • Each row is a transaction involving a customer.
  • Important columns are: transaction_id, customer_id, amount, transaction_type, channel, date.
  • Beginners often forget to check if amounts are negative or miss if the type is 'Credit' or 'Debit'.
  • We will create synthetic data to practice.
np.random.seed(42)
n_transactions = 1000
n_customers = 200
df = pd.DataFrame({
    'transaction_id': range(1, n_transactions + 1),
    'customer_id': np.random.choice([f'CUST_{i:04d}' for i in range(1, n_customers + 1)], n_transactions),
    'amount': np.round(np.random.normal(150, 60, n_transactions), 2),
    'transaction_type': np.random.choice(['Debit', 'Credit'], n_transactions),
    'channel': np.random.choice(['ATM', 'Online', 'Branch', 'POS'], n_transactions),
    'date': pd.date_range(start='2024-01-01', periods=n_transactions, freq='h')
})
print(df.shape)
print(df.head(3))
(1000, 6)
   transaction_id customer_id  amount transaction_type channel  \
0               1   CUST_0103  238.77           Credit     ATM   
1               2   CUST_0180  269.17           Credit     POS   
2               3   CUST_0093   58.62           Credit  Online   

                 date  
0 2024-01-01 00:00:00  
1 2024-01-01 01:00:00  
2 2024-01-01 02:00:00  

Beginner: Basic if statement for transaction rules#

  • Control flow is used to separate 'Debit' and 'Credit' logic.
  • For example, you may want to flag transactions above a certain amount.
  • Let us look at a basic if statement applied to transaction amounts.
test_amt = 500
if test_amt > 400:
    print('Flag: Large transaction detected!')
Flag: Large transaction detected!
amt = 300
if amt > 400:
    print('This will not print.')
else:
    print('Transaction under threshold.')
Transaction under threshold.
txn_type = 'Credit'
if txn_type == 'Debit':
    print('This is a withdrawal.')
else:
    print('This is a deposit or payment.')
This is a deposit or payment.
row = df.loc[0]
if row['transaction_type'] == 'Debit' and row['amount'] > 200:
    print(f"Large withdrawal by {row['customer_id']}")
else:
    print("No flag needed.")
No flag needed.

Beginner: Looping over transactions#

  • Loops let you apply logic to each transaction.
  • For banking, this could mean checking every row for fraud flags.
  • Let us sum up all debit amounts using a for loop.
total_debits = 0
for i, row in df.iterrows():
    if row['transaction_type'] == 'Debit':
        total_debits += row['amount']
print(f'Total debits: {total_debits:.2f}')
Total debits: 75981.44
large_flags = []
for i, row in df.iterrows():
    if row['amount'] > 500:
        large_flags.append(row['transaction_id'])
print(f'Number of large transactions: {len(large_flags)}')
Number of large transactions: 0

Intermediate: Multiple rules and nested control flow#

  • Sometimes, you must check more than one condition.
  • Use elif and nested ifs to tune your logic for banking needs.
  • Example: Flag a transaction only if it is an online debit over 300.
flagged = []
for i, row in df.iterrows():
    if row['transaction_type'] == 'Debit':
        if row['channel'] == 'Online' and row['amount'] > 300:
            flagged.append(row['transaction_id'])
print(f'Number of online debits over 300: {len(flagged)}')
Number of online debits over 300: 2
for i, row in df.iterrows():
    if row['amount'] < 0:
        print(f"Warning: Negative amount in transaction {row['transaction_id']}.")
        break
Warning: Negative amount in transaction 95.
channels = set(df['channel'])
for c in channels:
    count = df[(df['channel'] == c) & (df['amount'] > 400)].shape[0]
    print(f'Channel: {c}, Transactions >400: {count}')
Channel: ATM, Transactions >400: 0
Channel: POS, Transactions >400: 0
Channel: Branch, Transactions >400: 0
Channel: Online, Transactions >400: 0

Intermediate: Functions for transaction checks#

  • Functions help package checks for reuse.
  • You can write a function to flag suspicious transactions using your logic.
  • Let us define a function to apply consistent approval rules.
def approve_transaction(row, amount_limit=800):
    if row['amount'] < 0:
        return 'Error: Invalid amount'
    if row['amount'] > amount_limit:
        return 'Flag: Over limit'
    if row['transaction_type'] == 'Debit' and row['amount'] > 500:
        return 'Flag: Large debit'
    return 'Approved'
df['approval_status'] = df.apply(approve_transaction, axis=1)
print(df[['transaction_id', 'amount', 'transaction_type', 'approval_status']].head(5))
   transaction_id  amount transaction_type approval_status
0               1  238.77           Credit        Approved
1               2  269.17           Credit        Approved
2               3   58.62           Credit        Approved
3               4   81.85            Debit        Approved
4               5  163.56           Credit        Approved

Intermediate: List comprehensions for quick checks#

  • Python offers compact ways to apply ifs using list comprehensions.
  • Let us count quickly how many approved transactions we have.
num_approved = len([a for a in df['approval_status'] if a == 'Approved'])
print(f'Approved transactions: {num_approved}')
Approved transactions: 993

Advanced: Vectorized conditional logic with numpy#

  • pandas and numpy let you apply rules to every row without explicit loops.
  • Use np.where for fast logic over whole columns.
  • Example: Mark 'HighRisk' where debits are over 600.
df['high_risk'] = np.where((df['transaction_type'] == 'Debit') & (df['amount'] > 600), True, False)
print(df[['transaction_id', 'amount', 'transaction_type', 'high_risk']].head(6))
print(f"Total high risk transactions: {df['high_risk'].sum()}")
   transaction_id  amount transaction_type  high_risk
0               1  238.77           Credit      False
1               2  269.17           Credit      False
2               3   58.62           Credit      False
3               4   81.85            Debit      False
4               5  163.56           Credit      False
5               6  200.38            Debit      False
Total high risk transactions: 0

Advanced: Filtering with control flow expressions#

  • Sometimes, you need to extract only rows passing complex rules.
  • Filtering using & and | lets you chain multiple conditions.
  • Example: Find all 'ATM' withdrawals above average amount.
avg_amt = df['amount'].mean()
atm_large = df[(df['channel'] == 'ATM') & (df['amount'] > avg_amt) & (df['transaction_type'] == 'Debit')]
print(f'ATM withdrawals over average: {atm_large.shape[0]}')
print(atm_large[['transaction_id', 'amount']].head(3))
ATM withdrawals over average: 72
    transaction_id  amount
21              22  179.67
22              23  188.43
41              42  159.57

Advanced: Using dictionaries for decision logic#

  • Dictionaries let you map control flow results to actions, making logic easier to maintain.
  • Example: Assign transaction category using a mapping dict.
channel_map = {'ATM': 'Cash', 'Online': 'Digital', 'Branch': 'Personal', 'POS': 'Retail'}
df['category'] = df['channel'].map(channel_map)
print(df[['channel', 'category']].drop_duplicates())
  channel  category
0     ATM      Cash
1     POS    Retail
2  Online   Digital
3  Branch  Personal

Error handling: Catching and logging problems#

  • Transaction logic should never crash your pipeline.
  • Use try/except to catch and report invalid inputs or missing values.
  • Example: Handle a missing 'amount' safely without stopping the process.
def robust_approval(row):
    try:
        if row['amount'] is None or np.isnan(row['amount']):
            return 'Error: Missing amount'
        elif row['amount'] < 0:
            return 'Error: Negative'
        else:
            return 'Approved'
    except Exception as e:
        print(f"Exception in transaction {row['transaction_id']}: {e}")
        return 'Error: Unknown'
test_row = {'transaction_id': 9999, 'amount': None}
print(robust_approval(test_row))
Error: Missing amount

Best practices and patterns in transaction logic#

  • Always validate input types and values.
  • Use clear function names and comments for business rules.
  • Write concise, layered if-elif-else statements. Avoid deeply nested logic if you can.
  • Return errors, flags, or codes, not just print statements.
  • Log problems for tracing when things go wrong.
  • Use DataFrame vectorization for speed when working with big datasets.

End-to-end challenge: Find and summarize suspicious activity#

  • Goal: List all customers with over three flagged transactions and their total at-risk amount.
  • Solution steps:
    1. For each transaction, flag it if it is a debit over 400 or a credit over 1000.
    1. Summarize counts and sums per customer.
    1. Output customers with more than three flagged transactions.
df['flag'] = np.where(((df['transaction_type'] == 'Debit') & (df['amount'] > 400)) |
                     ((df['transaction_type'] == 'Credit') & (df['amount'] > 1000)), 1, 0)
flagged = df[df['flag'] == 1]
summary = flagged.groupby('customer_id')['amount'].agg(['count', 'sum']).reset_index()
risky_customers = summary[summary['count'] > 3]
print(risky_customers.sort_values('sum', ascending=False))
Empty DataFrame
Columns: [customer_id, count, sum]
Index: []
 

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.