SECTION 1: LEARNING OBJECTIVES

By the end of this lesson, you will be able to:

  1. Understand the fundamental concepts of descriptive statistics and their application in financial data analysis.

  2. Calculate and interpret measures of central tendency (mean, median, mode) for financial variables.

  3. Calculate and interpret measures of dispersion (variance, standard deviation, range, IQR) for risk assessment.

  4. Understand probability theory fundamentals and their application in financial decision-making.

  5. Apply probability rules (addition, multiplication, conditional probability) to financial scenarios.

  6. Calculate and interpret expected values for financial outcomes.

  7. Apply Bayes’ Theorem to update financial beliefs with new information.

  8. Use Python to calculate descriptive statistics and probabilities for banking data.


SECTION 2: MEASURES OF CENTRAL TENDENCY

2.1 The Mean (Arithmetic Average)

The mean is the sum of all values divided by the number of values. In banking, the mean is used for average transaction amounts, average account balances, and average credit scores.

python
# ============= MEASURES OF CENTRAL TENDENCY =============

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats

# Create sample banking data
np.random.seed(42)
n = 1000

# Generate realistic banking data
account_balances = np.random.lognormal(10, 1.2, n)  # Right-skewed
transaction_amounts = np.random.lognormal(4, 0.8, n)  # Right-skewed
credit_scores = np.random.normal(700, 50, n)  # Approximately normal
loan_amounts = np.random.lognormal(11, 0.5, n)  # Right-skewed

# Create DataFrame
banking_stats = pd.DataFrame({
    'account_balance': account_balances,
    'transaction_amount': transaction_amounts,
    'credit_score': credit_scores,
    'loan_amount': loan_amounts
})

print("="*60)
print("MEASURES OF CENTRAL TENDENCY")
print("="*60)

for col in banking_stats.columns:
    data = banking_stats[col]
    print(f"\n📊 {col.replace('_', ' ').title()}:")
    print(f"  Mean: ${data.mean():,.2f}" if 'balance' in col or 'amount' in col else f"  Mean: {data.mean():.2f}")
    print(f"  Median: ${data.median():,.2f}" if 'balance' in col or 'amount' in col else f"  Median: {data.median():.2f}")
    print(f"  Mode: ${data.mode().iloc[0]:,.2f}" if 'balance' in col or 'amount' in col else f"  Mode: {data.mode().iloc[0]:.2f}")
    print(f"  Skewness: {data.skew():.3f}")

# Visualize central tendency
fig, axes = plt.subplots(2, 2, figsize=(14, 10))

for i, col in enumerate(banking_stats.columns):
    row = i // 2
    col_idx = i % 2
    
    data = banking_stats[col]
    ax = axes[row, col_idx]
    
    # Histogram
    data.hist(bins=50, ax=ax, edgecolor='black', alpha=0.7)
    
    # Add vertical lines for mean and median
    ax.axvline(data.mean(), color='red', linestyle='--', linewidth=2, label=f'Mean: {data.mean():.2f}')
    ax.axvline(data.median(), color='blue', linestyle='--', linewidth=2, label=f'Median: {data.median():.2f}')
    
    ax.set_title(f'{col.replace("_", " ").title()}', fontsize=12)
    ax.set_xlabel('Value')
    ax.set_ylabel('Frequency')
    ax.legend()
    ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('central_tendency.png', dpi=300)
plt.show()

2.2 When to Use Mean vs Median

 
 
Situation Use Mean Use Median
Normal Distribution ✓ ✓
Skewed Distribution ✗ ✓
Outliers Present ✗ ✓
Financial Reporting Often Sometimes
Risk Assessment Sometimes Often

Banking Example: Average Account Balance

python
# ============= MEAN VS MEDIAN IN BANKING =============

# Create a dataset with outliers (high net worth individuals)
np.random.seed(42)
regular_balances = np.random.lognormal(9, 0.8, 1000)
high_net_worth = np.random.lognormal(13, 0.5, 50)

all_balances = np.concatenate([regular_balances, high_net_worth])

print("\n" + "="*60)
print("MEAN VS MEDIAN: ACCOUNT BALANCES")
print("="*60)

print(f"\n📊 Account Balance Statistics:")
print(f"  Mean: ${all_balances.mean():,.2f}")
print(f"  Median: ${np.median(all_balances):,.2f}")
print(f"  Difference: ${all_balances.mean() - np.median(all_balances):,.2f}")
print(f"  Skewness: {pd.Series(all_balances).skew():.3f}")
print(f"  High Net Worth Customers: {len(high_net_worth)} ({len(high_net_worth)/len(all_balances)*100:.1f}%)")

# Visualize
fig, ax = plt.subplots(figsize=(10, 6))
pd.Series(all_balances).hist(bins=50, ax=ax, edgecolor='black', alpha=0.7)
ax.axvline(np.mean(all_balances), color='red', linestyle='--', linewidth=2, label=f'Mean: ${np.mean(all_balances):,.0f}')
ax.axvline(np.median(all_balances), color='blue', linestyle='--', linewidth=2, label=f'Median: ${np.median(all_balances):,.0f}')
ax.set_title('Account Balance Distribution - Mean vs Median', fontsize=14)
ax.set_xlabel('Account Balance ($)')
ax.set_ylabel('Frequency')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('mean_vs_median.png', dpi=300)
plt.show()

print("\n💡 Insight:")
print("  • Mean is pulled upward by high-net-worth customers")
print("  • Median better represents the 'typical' customer")
print("  • For regulatory reporting, banks often use both")

SECTION 3: MEASURES OF DISPERSION

3.1 Variance and Standard Deviation

Variance and standard deviation measure how spread out data is. In banking, these are used to measure risk and volatility.

python
# ============= MEASURES OF DISPERSION =============

print("\n" + "="*60)
print("MEASURES OF DISPERSION")
print("="*60)

# Calculate dispersion measures
for col in banking_stats.columns:
    data = banking_stats[col]
    print(f"\n📊 {col.replace('_', ' ').title()}:")
    print(f"  Variance: {data.var():,.2f}")
    print(f"  Standard Deviation: {data.std():,.2f}")
    print(f"  Range: {data.max() - data.min():,.2f}")
    print(f"  IQR: {data.quantile(0.75) - data.quantile(0.25):,.2f}")
    print(f"  CV (Coefficient of Variation): {data.std() / data.mean():.3f}")

# Coefficient of Variation (CV) measures relative variability
# Useful for comparing risk across different scales

print("\n" + "="*60)
print("RISK ASSESSMENT USING DISPERSION")
print("="*60)

# Simulate two investment portfolios
np.random.seed(42)
portfolio_a_returns = np.random.normal(0.10, 0.15, 1000)  # Mean 10%, Std 15%
portfolio_b_returns = np.random.normal(0.08, 0.08, 1000)  # Mean 8%, Std 8%

portfolios = pd.DataFrame({
    'Portfolio A': portfolio_a_returns,
    'Portfolio B': portfolio_b_returns
})

print("\n📊 Portfolio Comparison:")
for col in portfolios.columns:
    data = portfolios[col]
    print(f"\n{col}:")
    print(f"  Mean Return: {data.mean():.2%}")
    print(f"  Std Deviation: {data.std():.2%}")
    print(f"  CV: {data.std() / data.mean():.3f}")
    print(f"  Range: {data.max() - data.min():.2%}")
    print(f"  VaR (5%): {data.quantile(0.05):.2%}")

# Visualize
fig, ax = plt.subplots(figsize=(10, 6))
portfolios.hist(bins=50, ax=ax, alpha=0.6, edgecolor='black')
ax.set_title('Portfolio Return Distributions', fontsize=14)
ax.set_xlabel('Return')
ax.set_ylabel('Frequency')
ax.axvline(0, color='black', linestyle='-', alpha=0.3)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('portfolio_comparison.png', dpi=300)
plt.show()

print("\n💡 Insight:")
print("  • Portfolio A has higher average return but higher volatility (CV: 1.50)")
print("  • Portfolio B has lower average return but much lower volatility (CV: 1.00)")
print("  • Risk-averse investors may prefer Portfolio B")

3.2 Interquartile Range (IQR)

IQR measures the spread of the middle 50% of data. It is robust to outliers.

python
# ============= IQR FOR OUTLIER DETECTION =============

def detect_outliers_iqr(data, multiplier=1.5):
    """Detect outliers using IQR method."""
    q1 = data.quantile(0.25)
    q3 = data.quantile(0.75)
    iqr = q3 - q1
    
    lower_bound = q1 - multiplier * iqr
    upper_bound = q3 + multiplier * iqr
    
    outliers = data[(data < lower_bound) | (data > upper_bound)]
    
    return outliers, lower_bound, upper_bound

# Test on transaction amounts
transactions = banking_stats['transaction_amount']
outliers, lower_bound, upper_bound = detect_outliers_iqr(transactions)

print("\n" + "="*60)
print("IQR - TRANSACTION AMOUNT OUTLIER DETECTION")
print("="*60)

print(f"\n📊 Transaction Amounts:")
print(f"  Q1 (25th percentile): ${transactions.quantile(0.25):.2f}")
print(f"  Q3 (75th percentile): ${transactions.quantile(0.75):.2f}")
print(f"  IQR: ${transactions.quantile(0.75) - transactions.quantile(0.25):.2f}")
print(f"  Normal Range: [${lower_bound:.2f}, ${upper_bound:.2f}]")
print(f"  Outliers: {len(outliers)} ({len(outliers)/len(transactions)*100:.1f}%)")
print(f"  Outlier Range: [${outliers.min():.2f}, ${outliers.max():.2f}]")

# Visualize IQR
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Box plot
ax1 = axes[0]
sns.boxplot(y=transactions, ax=ax1)
ax1.set_title('Box Plot - Transaction Amounts', fontsize=12)
ax1.set_ylabel('Amount ($)')
ax1.axhline(lower_bound, color='red', linestyle='--', label=f'Lower Bound: ${lower_bound:.2f}')
ax1.axhline(upper_bound, color='red', linestyle='--', label=f'Upper Bound: ${upper_bound:.2f}')
ax1.legend()

# Histogram with outlier boundaries
ax2 = axes[1]
transactions.hist(bins=50, ax=ax2, edgecolor='black', alpha=0.7)
ax2.axvline(lower_bound, color='red', linestyle='--', label=f'Lower Bound: ${lower_bound:.2f}')
ax2.axvline(upper_bound, color='red', linestyle='--', label=f'Upper Bound: ${upper_bound:.2f}')
ax2.set_title('Transaction Amount Distribution with Boundaries', fontsize=12)
ax2.set_xlabel('Amount ($)')
ax2.set_ylabel('Frequency')
ax2.legend()

plt.tight_layout()
plt.savefig('iqr_analysis.png', dpi=300)
plt.show()

SECTION 4: PROBABILITY FUNDAMENTALS

4.1 Basic Probability Concepts

Probability is the foundation of risk assessment, fraud detection, and financial modeling.

python
# ============= PROBABILITY FUNDAMENTALS =============

print("\n" + "="*60)
print("PROBABILITY FUNDAMENTALS")
print("="*60)

# Example: Loan default probability
# Historical data shows:
# - 5% of all loans default
# - 60% of defaults are from customers with credit score < 650
# - 20% of non-defaults are from customers with credit score < 650

# Calculate:
# P(Default) = 0.05
# P(Low Credit | Default) = 0.60
# P(Low Credit | No Default) = 0.20

# 1. Joint Probability
p_default = 0.05
p_no_default = 0.95
p_low_credit_given_default = 0.60
p_low_credit_given_no_default = 0.20

p_default_and_low_credit = p_default * p_low_credit_given_default
p_no_default_and_low_credit = p_no_default * p_low_credit_given_no_default
p_low_credit = p_default_and_low_credit + p_no_default_and_low_credit

print("\n📊 Loan Default Analysis:")
print(f"  P(Default) = {p_default:.2%}")
print(f"  P(Low Credit | Default) = {p_low_credit_given_default:.2%}")
print(f"  P(Low Credit | No Default) = {p_low_credit_given_no_default:.2%}")
print(f"  P(Default ∩ Low Credit) = {p_default_and_low_credit:.2%}")
print(f"  P(Low Credit) = {p_low_credit:.2%}")

# 2. Conditional Probability
# What is P(Default | Low Credit)?
p_default_given_low_credit = p_default_and_low_credit / p_low_credit

print(f"\n  P(Default | Low Credit) = {p_default_given_low_credit:.2%}")

# 3. Bayes' Theorem
print("\n📊 Bayes' Theorem Application:")
print(f"  P(Default | Low Credit) = P(Low Credit | Default) * P(Default) / P(Low Credit)")
print(f"  = {p_low_credit_given_default:.2%} * {p_default:.2%} / {p_low_credit:.2%}")
print(f"  = {p_default_given_low_credit:.2%}")

# Create a function for probability calculations
def calculate_conditional_probability(p_a, p_b_given_a, p_b_given_not_a):
    """Calculate P(A|B) using Bayes' Theorem."""
    p_a_and_b = p_a * p_b_given_a
    p_not_a = 1 - p_a
    p_b = p_a_and_b + p_not_a * p_b_given_not_a
    p_a_given_b = p_a_and_b / p_b
    return p_a_given_b

# Example: Credit card fraud detection
p_fraud = 0.01  # 1% of transactions are fraudulent
p_anomaly_given_fraud = 0.85  # 85% of fraud transactions are flagged
p_anomaly_given_legit = 0.05  # 5% of legit transactions are flagged

p_fraud_given_anomaly = calculate_conditional_probability(
    p_fraud, p_anomaly_given_fraud, p_anomaly_given_legit
)

print("\n" + "="*60)
print("FRAUD DETECTION EXAMPLE")
print("="*60)

print(f"\n📊 Credit Card Fraud Detection:")
print(f"  P(Fraud) = {p_fraud:.2%}")
print(f"  P(Anomaly | Fraud) = {p_anomaly_given_fraud:.2%}")
print(f"  P(Anomaly | Legit) = {p_anomaly_given_legit:.2%}")
print(f"  P(Fraud | Anomaly) = {p_fraud_given_anomaly:.2%}")

print("\n💡 Insight:")
print("  • Even with high detection rate (85%),")
print("  • Only {:.1f}% of flagged transactions are actually fraud".format(p_fraud_given_anomaly*100))
print("  • This is why banks combine multiple signals for fraud detection")

4.2 Probability Distributions

python
# ============= PROBABILITY DISTRIBUTIONS =============

from scipy.stats import norm, lognorm, binom, poisson, expon

print("\n" + "="*60)
print("PROBABILITY DISTRIBUTIONS IN FINANCE")
print("="*60)

# 1. Normal Distribution - Stock Returns
print("\n📊 Normal Distribution - Stock Returns:")
mu = 0.05  # Expected return
sigma = 0.15  # Volatility

# Calculate probabilities
x = np.linspace(mu - 4*sigma, mu + 4*sigma, 1000)
y = norm.pdf(x, mu, sigma)

print(f"  Mean Return: {mu:.2%}")
print(f"  Volatility: {sigma:.2%}")
print(f"  P(Return < 0) = {norm.cdf(0, mu, sigma):.2%}")
print(f"  P(Return > 0.10) = {1 - norm.cdf(0.10, mu, sigma):.2%}")
print(f"  P(Return between -5% and 15%) = {norm.cdf(0.15, mu, sigma) - norm.cdf(-0.05, mu, sigma):.2%}")

# 2. Log-normal Distribution - Asset Prices
print("\n📊 Log-normal Distribution - Asset Prices:")
price_mu = 100  # Current price
price_sigma = 0.20  # Volatility

# Calculate distribution parameters
log_mu = np.log(price_mu) - 0.5 * price_sigma**2

# Calculate probabilities
x_price = np.linspace(50, 200, 1000)
y_price = lognorm.pdf(x_price, price_sigma, scale=np.exp(log_mu))

print(f"  Current Price: ${price_mu:.2f}")
print(f"  Volatility: {price_sigma:.2%}")
print(f"  P(Price < $80) = {lognorm.cdf(80, price_sigma, scale=np.exp(log_mu)):.2%}")
print(f"  P(Price > $120) = {1 - lognorm.cdf(120, price_sigma, scale=np.exp(log_mu)):.2%}")

# 3. Binomial Distribution - Loan Defaults
print("\n📊 Binomial Distribution - Loan Defaults:")
n_loans = 100
p_default = 0.05

# Calculate probabilities
k = np.arange(0, 25)
y_binom = binom.pmf(k, n_loans, p_default)

print(f"  Number of Loans: {n_loans}")
print(f"  Default Probability: {p_default:.2%}")
print(f"  Expected Defaults: {n_loans * p_default:.1f}")
print(f"  P(0 Defaults) = {binom.pmf(0, n_loans, p_default):.2%}")
print(f"  P(5 Defaults) = {binom.pmf(5, n_loans, p_default):.2%}")
print(f"  P(>5 Defaults) = {1 - binom.cdf(5, n_loans, p_default):.2%}")

# 4. Poisson Distribution - Transaction Arrivals
print("\n📊 Poisson Distribution - Transaction Arrivals:")
lambda_rate = 10  # Average 10 transactions per minute

print(f"  Average Rate: {lambda_rate} transactions/minute")
print(f"  P(5 transactions) = {poisson.pmf(5, lambda_rate):.2%}")
print(f"  P(10 transactions) = {poisson.pmf(10, lambda_rate):.2%}")
print(f"  P(>15 transactions) = {1 - poisson.cdf(15, lambda_rate):.2%}")

# 5. Exponential Distribution - Time Between Events
print("\n📊 Exponential Distribution - Time Between Events:")
mean_time = 6  # Average 6 minutes between transactions

print(f"  Average Time: {mean_time} minutes")
print(f"  P(Time < 1 min) = {expon.cdf(1, scale=mean_time):.2%}")
print(f"  P(Time > 10 min) = {1 - expon.cdf(10, scale=mean_time):.2%}")

# Visualize distributions
fig, axes = plt.subplots(2, 3, figsize=(15, 10))

# 1. Normal Distribution
ax = axes[0, 0]
ax.plot(x, y, 'b-', linewidth=2)
ax.fill_between(x, 0, y, where=(x <= 0), color='red', alpha=0.3, label='Loss Region')
ax.set_title('Normal Distribution - Stock Returns', fontsize=12)
ax.set_xlabel('Return')
ax.set_ylabel('Density')
ax.axvline(0, color='black', linestyle='-', alpha=0.3)
ax.grid(True, alpha=0.3)
ax.legend()

# 2. Log-normal Distribution
ax = axes[0, 1]
ax.plot(x_price, y_price, 'b-', linewidth=2)
ax.axvline(price_mu, color='red', linestyle='--', label=f'Current Price: ${price_mu:.0f}')
ax.set_title('Log-normal Distribution - Asset Prices', fontsize=12)
ax.set_xlabel('Price ($)')
ax.set_ylabel('Density')
ax.grid(True, alpha=0.3)
ax.legend()

# 3. Binomial Distribution
ax = axes[0, 2]
ax.bar(k, y_binom, alpha=0.7, color='steelblue')
ax.set_title('Binomial Distribution - Loan Defaults', fontsize=12)
ax.set_xlabel('Number of Defaults')
ax.set_ylabel('Probability')
ax.grid(True, alpha=0.3)

# 4. Poisson Distribution
ax = axes[1, 0]
k_poisson = np.arange(0, 30)
y_poisson = poisson.pmf(k_poisson, lambda_rate)
ax.bar(k_poisson, y_poisson, alpha=0.7, color='green')
ax.set_title('Poisson Distribution - Transaction Arrivals', fontsize=12)
ax.set_xlabel('Number of Transactions')
ax.set_ylabel('Probability')
ax.axvline(lambda_rate, color='red', linestyle='--', label=f'Mean: {lambda_rate}')
ax.grid(True, alpha=0.3)
ax.legend()

# 5. Exponential Distribution
ax = axes[1, 1]
x_exp = np.linspace(0, 30, 1000)
y_exp = expon.pdf(x_exp, scale=mean_time)
ax.plot(x_exp, y_exp, 'b-', linewidth=2)
ax.set_title('Exponential Distribution - Time Between Events', fontsize=12)
ax.set_xlabel('Time (minutes)')
ax.set_ylabel('Density')
ax.axvline(mean_time, color='red', linestyle='--', label=f'Mean: {mean_time} min')
ax.grid(True, alpha=0.3)
ax.legend()

# 6. Comparison of distributions
ax = axes[1, 2]
# Generate samples from each distribution
normal_samples = np.random.normal(0, 1, 1000)
lognormal_samples = np.random.lognormal(0, 0.5, 1000)
poisson_samples = np.random.poisson(5, 1000)

ax.hist(normal_samples, bins=30, alpha=0.5, label='Normal', density=True)
ax.hist(lognormal_samples, bins=30, alpha=0.5, label='Log-normal', density=True)
ax.hist(poisson_samples, bins=30, alpha=0.5, label='Poisson', density=True)
ax.set_title('Distribution Comparison', fontsize=12)
ax.set_xlabel('Value')
ax.set_ylabel('Density')
ax.legend()
ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('probability_distributions.png', dpi=300)
plt.show()

SECTION 5: EXPECTED VALUE AND RISK METRICS

5.1 Expected Value

Expected value is the weighted average of possible outcomes. In banking, it’s used for pricing loans, insurance, and derivatives.

python
# ============= EXPECTED VALUE =============

print("\n" + "="*60)
print("EXPECTED VALUE IN FINANCE")
print("="*60)

# Example 1: Loan Expected Return
print("\n📊 Loan Expected Return:")
loan_amount = 100000
interest_rate = 0.08
default_prob = 0.05
recovery_rate = 0.40

# Outcomes
if_no_default = loan_amount * (1 + interest_rate)
if_default = loan_amount * recovery_rate

expected_return = (1 - default_prob) * if_no_default + default_prob * if_default
expected_profit = expected_return - loan_amount
expected_return_pct = expected_return / loan_amount - 1

print(f"  Loan Amount: ${loan_amount:,.0f}")
print(f"  Interest Rate: {interest_rate:.2%}")
print(f"  Default Probability: {default_prob:.2%}")
print(f"  Recovery Rate: {recovery_rate:.2%}")
print(f"  Expected Return: ${expected_return:,.2f}")
print(f"  Expected Profit: ${expected_profit:,.2f}")
print(f"  Expected Return %: {expected_return_pct:.2%}")

# Example 2: Credit Card Expected Revenue
print("\n📊 Credit Card Expected Revenue:")
avg_balance = 5000
annual_fee = 95
interchange_rate = 0.02
avg_spend = 12000
default_prob_card = 0.03
loss_given_default = 0.50

revenue_from_fees = annual_fee
revenue_from_interchange = avg_spend * interchange_rate
loss_from_default = avg_balance * default_prob_card * loss_given_default

expected_revenue = revenue_from_fees + revenue_from_interchange - loss_from_default

print(f"  Average Balance: ${avg_balance:,.0f}")
print(f"  Annual Fee: ${annual_fee:.0f}")
print(f"  Interchange Rate: {interchange_rate:.2%}")
print(f"  Average Annual Spend: ${avg_spend:,.0f}")
print(f"  Expected Revenue: ${expected_revenue:.2f}")
print(f"  Revenue Breakdown:")
print(f"    • Annual Fees: ${revenue_from_fees:.2f}")
print(f"    • Interchange: ${revenue_from_interchange:.2f}")
print(f"    • Expected Loss: -${loss_from_default:.2f}")

# Example 3: Investment Portfolio Expected Return
print("\n📊 Portfolio Expected Return:")
# Two-asset portfolio
weights = [0.60, 0.40]
expected_returns = [0.10, 0.06]
volatilities = [0.15, 0.08]
correlation = 0.30

portfolio_return = sum(w * r for w, r in zip(weights, expected_returns))

# Portfolio variance (2-asset case)
var1 = volatilities[0]**2
var2 = volatilities[1]**2
cov12 = correlation * volatilities[0] * volatilities[1]

portfolio_variance = (weights[0]**2 * var1 + 
                      weights[1]**2 * var2 + 
                      2 * weights[0] * weights[1] * cov12)
portfolio_volatility = np.sqrt(portfolio_variance)

print(f"  Asset 1: Expected Return {expected_returns[0]:.2%}, Volatility {volatilities[0]:.2%}")
print(f"  Asset 2: Expected Return {expected_returns[1]:.2%}, Volatility {volatilities[1]:.2%}")
print(f"  Correlation: {correlation:.2%}")
print(f"  Portfolio Expected Return: {portfolio_return:.2%}")
print(f"  Portfolio Volatility: {portfolio_volatility:.2%}")
print(f"  Sharpe Ratio (risk-free=2%): {(portfolio_return - 0.02) / portfolio_volatility:.3f}")

5.2 Value at Risk (VaR)

VaR measures the maximum expected loss over a given time period at a given confidence level.

python
# ============= VALUE AT RISK (VaR) =============

def calculate_var(returns, confidence_level=0.95, method='historical'):
    """Calculate Value at Risk using different methods."""
    
    if method == 'historical':
        # Historical VaR
        var = -np.percentile(returns, (1 - confidence_level) * 100)
        return var
    
    elif method == 'parametric':
        # Parametric VaR (assuming normal distribution)
        mu = returns.mean()
        sigma = returns.std()
        z_score = stats.norm.ppf(confidence_level)
        var = -(mu - z_score * sigma)
        return var
    
    elif method == 'monte_carlo':
        # Monte Carlo VaR (simplified)
        n_simulations = 10000
        mu = returns.mean()
        sigma = returns.std()
        simulated = np.random.normal(mu, sigma, n_simulations)
        var = -np.percentile(simulated, (1 - confidence_level) * 100)
        return var

# Generate sample returns
np.random.seed(42)
daily_returns = np.random.normal(0.0005, 0.02, 1000)  # Mean 0.05%, Std 2%
portfolio_value = 1000000

print("\n" + "="*60)
print("VALUE AT RISK (VaR) CALCULATION")
print("="*60)

print(f"\n📊 Portfolio Value: ${portfolio_value:,.0f}")
print(f"  Daily Returns: Mean={daily_returns.mean():.4%}, Std={daily_returns.std():.4%}")

# Calculate VaR using different methods
confidence_levels = [0.90, 0.95, 0.99]

print("\nVaR Estimates (1-day):")
for confidence in confidence_levels:
    var_historical = calculate_var(daily_returns, confidence, 'historical')
    var_parametric = calculate_var(daily_returns, confidence, 'parametric')
    var_mc = calculate_var(daily_returns, confidence, 'monte_carlo')
    
    print(f"\n  {confidence:.0%} Confidence Level:")
    print(f"    Historical VaR: ${var_historical * portfolio_value:,.2f} ({var_historical:.2%})")
    print(f"    Parametric VaR: ${var_parametric * portfolio_value:,.2f} ({var_parametric:.2%})")
    print(f"    Monte Carlo VaR: ${var_mc * portfolio_value:,.2f} ({var_mc:.2%})")

# Conditional VaR (Expected Shortfall)
def calculate_cvar(returns, confidence_level=0.95):
    """Calculate Conditional VaR (Expected Shortfall)."""
    var = -np.percentile(returns, (1 - confidence_level) * 100)
    cvar = -returns[returns < -var].mean()
    return cvar

cvar_95 = calculate_cvar(daily_returns, 0.95)
print(f"\n📊 Conditional VaR (Expected Shortfall) at 95%:")
print(f"  CVaR: ${cvar_95 * portfolio_value:,.2f} ({cvar_95:.2%})")
print(f"  This is the expected loss given VaR is exceeded")

SECTION 6: BUSINESS RISK & FINANCIAL IMPACT

6.1 Regulatory Context

python
# ============= REGULATORY CONTEXT =============

print("\n" + "="*60)
print("REGULATORY CONTEXT FOR STATISTICS")
print("="*60)

regulatory_requirements = {
    'BASEL III': {
        'description': 'Capital requirements based on risk metrics',
        'metrics': ['VaR', 'Expected Shortfall', 'Probability of Default']
    },
    'SR 11-7': {
        'description': 'Model risk management',
        'metrics': ['Mean', 'Variance', 'Confidence Intervals']
    },
    'IFRS 9': {
        'description': 'Expected credit loss calculation',
        'metrics': ['Probability of Default', 'Loss Given Default', 'Exposure at Default']
    },
    'CCAR': {
        'description': 'Stress testing requirements',
        'metrics': ['VaR', 'Expected Shortfall', 'Scenario Analysis']
    }
}

for reg, details in regulatory_requirements.items():
    print(f"\n📋 {reg}:")
    print(f"  {details['description']}")
    print(f"  Key Statistical Concepts: {', '.join(details['metrics'])}")

SECTION 7: SUMMARY FOR THE DATA PRACTITIONER

7.1 The 1-Minute Elevator Pitch

“Descriptive statistics and probability are the foundation of financial data analytics. We use measures of central tendency (mean, median) and dispersion (variance, standard deviation, IQR) to understand data distributions. Probability theory helps us quantify risk and uncertainty – from loan defaults to market movements. Expected value calculations guide pricing decisions, while VaR measures portfolio risk. These concepts are essential for regulatory compliance (BASEL III, SR 11-7) and informed financial decision-making.”

7.2 Key Takeaways

  1. Mean is sensitive to outliers; median is robust for skewed data.

  2. Standard deviation measures risk (volatility) in financial returns.

  3. IQR is robust for outlier detection and understanding data spread.

  4. Bayes’ Theorem updates probabilities with new information.

  5. Expected value guides pricing and investment decisions.

  6. VaR quantifies maximum expected loss at a confidence level.

  7. Normal distribution models returns; lognormal models prices.

  8. Binomial distribution models number of defaults; Poisson models arrivals.

  9. Regulatory compliance requires understanding of statistical concepts.

  10. Python enables calculation and visualization of statistics.

7.3 Recommended Next Steps

  1. Practice calculating statistics on your banking datasets

  2. Build risk models using VaR and expected value

  3. Apply Bayesian methods to fraud detection

  4. Understand regulatory requirements for statistics

  5. Create dashboards showing key statistical metrics


[END OF LESSON 1]

Â