Mathew K Analytics

Lesson 31 · Python for Banking and Finance

Analyzing Transaction Trends and Seasonality in Finance Using Python

In this lesson, we will explore how to analyze real-world banking transaction data to detect trends and seasonality. Understanding transaction trends helps…

⬇ Download notebookOpen in Colab ↗

What you'll learn

Data

No separate download needed — the notebook creates or downloads everything it uses.

📓 Full notebook

Download .ipynb

Transaction Trends and Seasonality in Banking Data#

  • In this lesson, we will explore how to analyze real-world banking transaction data to detect trends and seasonality.
  • Understanding transaction trends helps banks optimize services and detect anomalies early.
  • Seasonality gives insight into customer behavior across different periods.
  • You will use Python to visualize, summarize, and model banking transaction patterns.
  • By the end, you will be able to identify patterns, spikes, and cycles in transaction data over time.
  • No prior experience with time series data is required.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
np.random.seed(42)

Understanding the Data for Trend and Seasonality#

  • Our transactions table contains events linked to customers with amounts, timestamps, and transaction types.
  • Common columns: transaction ID, customer ID, amount, type, channel, and date.
  • Time-based columns like date and hour are crucial for trend analysis.
  • Beginners often mistake random spikes for seasonalityalways visualize before concluding.
  • Missing or duplicate dates can corrupt analysis; always check your time columns.
# --- Synthetic Transaction Data Setup ---
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  

Checking Data Quality Before Analysis#

  • Always check number of nulls, duplicated rows, and correct column types before touching time series data.
  • Wrong or missing dates can break your seasonality analysis.
  • Aggregations work best after data types and formats are validated.
print('Nulls in each column:')
print(df.isnull().sum())
print('Duplicates:', df.duplicated().sum())
print('Data types:')
print(df.dtypes)
Nulls in each column:
transaction_id      0
customer_id         0
amount              0
transaction_type    0
channel             0
date                0
dtype: int64
Duplicates: 0
Data types:
transaction_id               int64
customer_id                 object
amount                     float64
transaction_type            object
channel                     object
date                datetime64[ns]
dtype: object
# --- Convert date to datetime (if needed) and make it the index ---
df['date'] = pd.to_datetime(df['date'])
df = df.set_index('date')
print(df.index.name)
print(df.iloc[:2, :])
date
                     transaction_id customer_id  amount transaction_type  \
date                                                                       
2024-01-01 00:00:00               1   CUST_0103  238.77           Credit   
2024-01-01 01:00:00               2   CUST_0180  269.17           Credit   

                    channel  
date                         
2024-01-01 00:00:00     ATM  
2024-01-01 01:00:00     POS  

Visualizing Overall Transaction Trends#

  • Visualizations help you spot trends or anomalies over time with ease.
  • Aggregate total transaction amounts per day to observe daily changes.
  • Line plots are great for revealing cycles or sudden changes.
# --- Beginner Example 1: Daily Sum of Transactions ---
daily_sum = df['amount'].resample('D').sum()
plt.figure(figsize=(10, 4))
plt.plot(daily_sum)
plt.title('Total Transaction Amount per Day')
plt.ylabel('Amount ($)')
plt.xlabel('Date')
plt.show()
No description has been provided for this image
# --- Beginner Example 2: Average Daily Transaction Value ---
daily_avg = df['amount'].resample('D').mean()
print(daily_avg.head(7))
date
2024-01-01    157.156667
2024-01-02    166.410417
2024-01-03    157.413333
2024-01-04    164.445417
2024-01-05    145.397083
2024-01-06    151.339167
2024-01-07    145.082500
Freq: D, Name: amount, dtype: float64
# --- Beginner Example 3: Count of Transactions Per Day ---
daily_count = df['amount'].resample('D').count()
plt.figure(figsize=(10, 3))
plt.bar(daily_count.index, daily_count.values)
plt.title('Number of Transactions Per Day')
plt.ylabel('Count')
plt.xlabel('Date')
plt.tight_layout()
plt.show()
No description has been provided for this image
# --- Intermediate Example 1: Weekly Trend Visualization ---
weekly_sum = df['amount'].resample('W').sum()
plt.figure(figsize=(10, 4))
plt.plot(weekly_sum, marker='o')
plt.title('Total Transaction Amount per Week')
plt.ylabel('Amount ($)')
plt.xlabel('Week Starting')
plt.grid(True)
plt.show()
No description has been provided for this image
# --- Intermediate Example 2: Detecting Seasonality by Day of Week ---
df['weekday'] = df.index.day_name()
weekday_avg = df.groupby('weekday')['amount'].mean().reindex(['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'])
plt.figure(figsize=(8, 4))
sns.barplot(x=weekday_avg.index, y=weekday_avg.values)
plt.title('Average Transaction Amount by Weekday')
plt.ylabel('Average Amount ($)')
plt.xlabel('Day of Week')
plt.show()
No description has been provided for this image
# --- Intermediate Example 3: Rolling 7-Day Total Moving Average ---
rolling_total = daily_sum.rolling(window=7).mean()
plt.figure(figsize=(10, 4))
plt.plot(daily_sum, label='Daily Total')
plt.plot(rolling_total, color='red', label='7-Day Moving Average')
plt.title('7-Day Moving Average of Daily Transaction Amount')
plt.ylabel('Amount ($)')
plt.xlabel('Date')
plt.legend()
plt.show()
No description has been provided for this image
# --- Advanced Example 1: Outlier Detection in Transaction Amounts ---
q1 = daily_sum.quantile(0.25)
q3 = daily_sum.quantile(0.75)
iqr = q3 - q1
outlier_upper = q3 + 1.5 * iqr
outlier_lower = q1 - 1.5 * iqr
outlier_days = daily_sum[(daily_sum > outlier_upper) | (daily_sum < outlier_lower)]
print('Outlier Days:')
print(outlier_days)
Outlier Days:
Series([], Freq: D, Name: amount, dtype: float64)
# --- Advanced Example 2: Heatmap of Transactions by Day and Hour ---
df['hour'] = df.index.hour
pivot = pd.pivot_table(df, values='amount', index='weekday', columns='hour', aggfunc='mean').reindex(['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'])
plt.figure(figsize=(14,4))
sns.heatmap(pivot, cmap='YlGnBu')
plt.title('Average Transaction Amount: Day of Week vs Hour of Day')
plt.ylabel('Day of Week')
plt.xlabel('Hour of Day')
plt.show()
No description has been provided for this image
# --- Advanced Example 3: Decompose Trend and Seasonality Components ---
from statsmodels.tsa.seasonal import seasonal_decompose
decompose_result = seasonal_decompose(daily_sum, model='additive', period=7)
decompose_result.plot()
plt.suptitle('Trend, Seasonal, Residual: Daily Transaction Totals')
plt.show()
No description has been provided for this image
# --- Error Handling: Missing Dates Example ---
broken_df = df.copy()
broken_df = broken_df.drop(broken_df.index[0])  # simulate a missing date
try:
    broken_df['amount'].resample('D').sum()
except Exception as e:
    print('Error:', e)
# --- Debugging: Detecting Duplicate Transactions ---
duplicate_count = df.duplicated().sum()
print(f'Duplicate transaction rows: {duplicate_count}')
Duplicate transaction rows: 0
# --- Debugging: Ensuring Datetime Index is Sorted ---
if not df.index.is_monotonic_increasing:
    print('Index out of order; sorting...')
    df = df.sort_index()
else:
    print('Index already sorted!')
Index already sorted!

Best Practices for Transaction Trend Analysis#

  • Always validate time columns and sort indexes before exploring trends.
  • Visualize your data before applying models to spot seasonality or anomalies.
  • Choose aggregation windows (e.g., daily, weekly) that match your business questions.
  • Be wary of missing weekends or holidays in test data.
  • Remove duplicates and fill or explain missing periods for reliable results.
  • Compare year-over-year or month-on-month changes for deeper business value.
# --- End-to-End Banking Problem: Identify Peak Transaction Day and Segment Behavior ---
peak_day = daily_sum.idxmax()
peak_value = daily_sum.max()
print(f'Highest transaction volume day: {peak_day.date()} (${peak_value:.2f})')

# Analyze which transaction type was most common on peak day:
df_peak = df[df.index.date == peak_day.date()]
type_counts = df_peak['transaction_type'].value_counts()
print('Transaction Types on Peak Day:')
print(type_counts)

# Plot hour-by-hour transaction counts for the peak day:
hourly_counts = df_peak.groupby(df_peak.index.hour)['amount'].count()
plt.figure(figsize=(8,3))
plt.bar(hourly_counts.index, hourly_counts.values)
plt.title('Hourly Transaction Counts (Peak Day)')
plt.xlabel('Hour')
plt.ylabel('Count')
plt.tight_layout()
plt.show()
Highest transaction volume day: 2024-01-21 ($4348.06)
Transaction Types on Peak Day:
transaction_type
Debit     14
Credit    10
Name: count, dtype: int64
No description has been provided for this image

Try More: Next Steps#

  • Repeat these analyses on other customer segments or transaction channels.
  • Build simple alerts for unusual peaks using thresholds.
  • Share your visualizations with a team member and discuss likely business causes.
  • Watch our YouTube channel for more Python banking tutorials!

Found this useful?

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