SECTION 1: LEARNING OBJECTIVES
By the end of this lesson, you will be able to:
-
Understand the principles of effective data visualisation – clarity, accuracy, efficiency, and aesthetics.
-
Apply the grammar of graphics to create layered, customised visualisations.
-
Select the appropriate chart type for different financial data and narratives.
-
Create interactive dashboards using Plotly Dash or Streamlit.
-
Use storytelling techniques to communicate insights effectively to stakeholders.
-
Integrate visualisations with financial models for dynamic reporting.
-
Implement time-series visualisations – candlestick charts, heatmaps, and animated plots.
-
Visualise risk metrics – VaR, ES, volatility surfaces, and correlation matrices.
-
Design executive dashboards with KPIs, trends, and alerts.
-
Apply best practices for colour, annotation, and layout to enhance comprehension.
SECTION 2: PRINCIPLES OF EFFECTIVE DATA VISUALISATION
2.1 The Visualisation Hierarchy
| Level | Goal | Example |
|---|---|---|
| Level 1: Exploratory | Understand the data. | Histograms, scatter plots, box plots. |
| Level 2: Explanatory | Communicate insights. | Bar charts, line charts, bullet charts. |
| Level 3: Decision | Drive action. | Dashboards with KPIs, alerts, and drill-down. |
2.2 Key Principles
| Principle | Description | Financial Example |
|---|---|---|
| Clarity | Remove clutter; focus on the message. | Clean line chart of a stock price. |
| Accuracy | Ensure scales, labels, and axes are truthful. | No truncated axes; proper baseline. |
| Efficiency | Use pre-attentive attributes (colour, size, position). | Colour-coded profit/loss. |
| Consistency | Use consistent colours, fonts, and scales. | Uniform palette across dashboards. |
| Context | Provide annotations and comparisons. | Add benchmarks or targets. |
| Emotion | Use storytelling to connect with the audience. | Narrative around a financial crisis. |
2.3 Choosing the Right Chart
| Data Type | Chart Type | Financial Use |
|---|---|---|
| Time Series | Line chart, area chart | Stock prices, yields, GDP. |
| Comparison | Bar chart, column chart | Revenue by segment, expense breakdown. |
| Distribution | Histogram, box plot | Returns distribution, VaR. |
| Relationship | Scatter plot, bubble chart | Correlation, risk-return trade-off. |
| Composition | Pie chart, stacked bar, waterfall | Portfolio allocation, profit composition. |
| Geospatial | Choropleth map | Regional revenue, branch locations. |
| Correlation | Heatmap | Correlation matrix, volatility surface. |
| Candlestick | OHLC chart | Stock price movement. |
SECTION 3: THE GRAMMAR OF GRAPHICS
Grammar of Graphics is a framework for describing visualisations in terms of layers:
-
Data: The dataset.
-
Aesthetics: Mapping variables to visual properties (x, y, colour, size, shape).
-
Geometric objects (geoms): Points, lines, bars, areas.
-
Facets: Subplots by category.
-
Coordinates: Cartesian, polar, etc.
-
Scales: Axes, colour scales.
-
Themes: Background, grid, font, legend.
Implementation in Python (Plotly/Matplotlib/Seaborn):
import plotly.express as px # Grammar of Graphics with Plotly fig = px.scatter( data_frame=df, x='Return', y='Risk', color='Sector', size='Market_Cap', hover_data=['Company'], title='Risk-Return Trade-off' ) fig.show()
SECTION 4: INTERACTIVE DASHBOARDS WITH PLOTLY DASH
Dash is a Python framework for building interactive web applications.
Key Components:
-
Layout: HTML components, graphs, sliders, dropdowns.
-
Callbacks: Python functions that update the UI based on user interactions.
Example Structure:
import dash from dash import dcc, html, Input, Output import plotly.express as px app = dash.Dash(__name__) app.layout = html.Div([ html.H1('Portfolio Dashboard'), dcc.Dropdown(id='sector-dropdown', options=[...], value='All'), dcc.Graph(id='portfolio-chart') ]) @app.callback( Output('portfolio-chart', 'figure'), Input('sector-dropdown', 'value') ) def update_chart(selected_sector): filtered_df = df if selected_sector == 'All' else df[df['Sector'] == selected_sector] fig = px.scatter(filtered_df, x='Return', y='Risk', color='ESG_Score') return fig if __name__ == '__main__': app.run_server(debug=True)
SECTION 5: STORYTELLING WITH DATA
Effective data storytelling combines data, visuals, and narrative.
Key Elements:
-
Context: Why is this important? What is the background?
-
Insight: What did you discover? What are the key takeaways?
-
Action: What should the audience do? What decisions should be made?
The Pyramid of Storytelling:
Message
/ \
Insights Insights
/ \ / \
Data Data Data Data
Example: Telling the Story of a Market Downturn
-
Data: Stock prices, volatility, trading volume, news sentiment.
-
Visuals: Line chart of the index, shaded recession periods, annotations of key events.
-
Narrative: “The 2022 bear market was driven by inflation, rising rates, and geopolitical tensions. However, defensive sectors outperformed, and opportunities emerged in energy and commodities.”
SECTION 6: IMPLEMENTATION IN PYTHON – FINANCIAL DASHBOARD
# =================================================================== # BONUS LESSON 8: ADVANCED DATA VISUALISATION AND STORYTELLING # =================================================================== import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import plotly.express as px import plotly.graph_objects as go from plotly.subplots import make_subplots import warnings warnings.filterwarnings('ignore') # Set style sns.set_style("whitegrid") np.random.seed(42) print("="*70) print("ADVANCED DATA VISUALISATION AND STORYTELLING FOR FINANCE") print("="*70) # ---------------------------------------------------------------- # PART A: GENERATE FINANCIAL DATA # ---------------------------------------------------------------- print("\n" + "-"*60) print("PART A: Generating Financial Data for Visualisation") print("-"*60) # Stock price simulation (random walk) n_days = 252 dates = pd.date_range(start='2023-01-01', periods=n_days, freq='B') price = 100 * np.exp(np.cumsum(np.random.normal(0.0005, 0.015, n_days))) volume = np.random.gamma(2, 1000, n_days).clip(100, 10000) # Create OHLC data df_stock = pd.DataFrame({ 'Date': dates, 'Open': price * (1 + np.random.normal(0, 0.005, n_days)), 'High': price * (1 + np.random.normal(0.01, 0.01, n_days)), 'Low': price * (1 + np.random.normal(-0.01, 0.01, n_days)), 'Close': price, 'Volume': volume }) # Ensure High is max of Open/Close and Low is min df_stock['High'] = df_stock[['Open', 'High', 'Close']].max(axis=1) df_stock['Low'] = df_stock[['Open', 'Low', 'Close']].min(axis=1) print("Generated stock data:") print(df_stock.head()) # Generate portfolio data n_assets = 5 asset_returns = np.random.multivariate_normal( [0.0005] * n_assets, np.random.uniform(0.01, 0.03, (n_assets, n_assets)) * 0.5 + np.diag(np.random.uniform(0.015, 0.03, n_assets)), n_days ) asset_names = ['Tech', 'Finance', 'Healthcare', 'Energy', 'Consumer'] df_returns = pd.DataFrame(asset_returns, columns=asset_names) df_prices = 100 * (1 + df_returns.cumsum()) # ---------------------------------------------------------------- # PART B: CANDLESTICK CHART (PLOTLY) # ---------------------------------------------------------------- print("\n" + "-"*60) print("PART B: Candlestick Chart") print("-"*60) # Create candlestick chart fig = go.Figure(data=[go.Candlestick( x=df_stock['Date'], open=df_stock['Open'], high=df_stock['High'], low=df_stock['Low'], close=df_stock['Close'], name='Stock' )]) # Add moving average df_stock['MA_20'] = df_stock['Close'].rolling(20).mean() fig.add_trace(go.Scatter( x=df_stock['Date'], y=df_stock['MA_20'], line=dict(color='orange', width=2), name='MA 20' )) # Add volume as subplot (secondary y-axis) fig.add_trace(go.Bar( x=df_stock['Date'], y=df_stock['Volume'], name='Volume', yaxis='y2', marker_color='lightblue', opacity=0.3 )) # Update layout fig.update_layout( title='Stock Price with Candlesticks and Moving Average', yaxis_title='Price ($)', yaxis2=dict(title='Volume', overlaying='y', side='right'), xaxis_title='Date', template='plotly_white', height=600 ) # Show (in notebook, would use fig.show()) # For this script, save as HTML fig.write_html('candlestick_chart.html') print("Candlestick chart saved as 'candlestick_chart.html'") # ---------------------------------------------------------------- # PART C: PORTFOLIO RISK-REWARD SCATTER PLOT # ---------------------------------------------------------------- print("\n" + "-"*60) print("PART C: Portfolio Risk-Reward Scatter Plot") print("-"*60) # Calculate returns and risk for each asset asset_returns_mean = df_returns.mean() * 252 # annualised asset_returns_std = df_returns.std() * np.sqrt(252) # Create a DataFrame for visualisation df_assets = pd.DataFrame({ 'Asset': asset_names, 'Return': asset_returns_mean, 'Risk': asset_returns_std, 'Sharpe': asset_returns_mean / asset_returns_std }) # Plotly scatter fig = px.scatter( df_assets, x='Risk', y='Return', text='Asset', size='Sharpe', color='Sharpe', color_continuous_scale='RdYlGn', title='Risk-Return Trade-off by Asset Class', labels={'Risk': 'Annualised Volatility', 'Return': 'Annualised Return'}, hover_data={'Sharpe': ':.3f'} ) fig.update_traces(textposition='top center') fig.write_html('risk_reward_scatter.html') print("Risk-reward scatter plot saved as 'risk_reward_scatter.html'") # ---------------------------------------------------------------- # PART D: CORRELATION HEATMAP # ---------------------------------------------------------------- print("\n" + "-"*60) print("PART D: Correlation Heatmap") print("-"*60) corr = df_returns.corr() fig = go.Figure(data=go.Heatmap( z=corr.values, x=corr.columns, y=corr.columns, colorscale='RdBu_r', zmin=-1, zmax=1, text=corr.values.round(2), texttemplate='%{text}', textfont={"size": 12} )) fig.update_layout( title='Asset Return Correlation Matrix', xaxis_title='Asset', yaxis_title='Asset', height=500, width=600 ) fig.write_html('correlation_heatmap.html') print("Correlation heatmap saved as 'correlation_heatmap.html'") # ---------------------------------------------------------------- # PART E: TIME-SERIES WITH ANNOTATIONS (STORYTELLING) # ---------------------------------------------------------------- print("\n" + "-"*60) print("PART E: Time-Series with Annotations") print("-"*60) # Simulate a market index with events index = 1000 * np.exp(np.cumsum(np.random.normal(0.0002, 0.01, n_days))) # Add some events event_dates = ['2023-03-15', '2023-06-30', '2023-09-15', '2023-12-20'] event_labels = ['Fed Rate Hike', 'Earnings Season', 'Inflation Report', 'Year-End Rally'] # Create Plotly figure fig = go.Figure() fig.add_trace(go.Scatter( x=df_stock['Date'], y=index, mode='lines', name='Market Index', line=dict(color='navy', width=2) )) # Add shaded recession (example) fig.add_vrect( x0="2023-06-15", x1="2023-08-15", fillcolor="lightgray", opacity=0.3, layer="below", line_width=0, annotation_text="Market Correction", annotation_position="top left" ) # Add annotations for events for date, label in zip(event_dates, event_labels): # Find the closest date in the index idx = df_stock['Date'].searchsorted(pd.to_datetime(date)) if idx < len(df_stock): y_val = index[idx] fig.add_annotation( x=date, y=y_val, text=label, showarrow=True, arrowhead=2, ax=0, ay=-40, font=dict(size=10) ) fig.update_layout( title='Market Index with Key Events Annotated', xaxis_title='Date', yaxis_title='Index Level', template='plotly_white', height=500 ) fig.write_html('time_series_annotated.html') print("Annotated time-series saved as 'time_series_annotated.html'") # ---------------------------------------------------------------- # PART F: INTERACTIVE DASHBOARD (CONCEPTUAL) # ---------------------------------------------------------------- print("\n" + "-"*60) print("PART F: Interactive Dashboard Concept") print("-"*60) print(""" Interactive Dashboard Components: 1. KPIs: - Total Portfolio Value: $1.2B - YTD Return: 8.5% - Volatility: 12.3% - Sharpe Ratio: 1.2 2. Time-Series Chart: - Portfolio performance vs benchmark (S&P 500) - Filter by date range (slider) 3. Sector Allocation: - Donut chart showing sector weights - Drill-down to individual holdings 4. Risk Metrics: - VaR (95%, 99%) - daily, 10-day - Expected Shortfall - Stress test results 5. ESG Dashboard: - Overall ESG score - Carbon footprint - Controversies count 6. Scenario Analysis: - What-if sliders for interest rates, GDP growth, oil prices - Impact on portfolio value 7. Alerts: - Recent news sentiment - Risk threshold breaches - Upcoming corporate events Technology Stack: - Frontend: Dash (Plotly) or Streamlit - Backend: Python data processing (Pandas, NumPy) - Data: Real-time APIs (Bloomberg, Reuters) or batch ETL - Hosting: Cloud (AWS, Azure) or on-premise """) # ---------------------------------------------------------------- # PART G: STORYTELLING WITH FINANCIAL DATA # ---------------------------------------------------------------- print("\n" + "-"*60) print("PART G: Storytelling with Financial Data") print("-"*60) print(""" Storytelling Framework for Finance: 1. Context Setting: - "The market has experienced significant volatility over the past year." - "Central banks are shifting policy, impacting asset prices." 2. Data Discovery: - "We analysed 50,000+ transactions across 10,000 customers." - "Our analysis reveals a 15% increase in delinquencies in the last quarter." 3. Visual Evidence: - "As shown in this chart, the increase is concentrated in the 30-45 age group." - "The correlation between credit score and default is strong (r = -0.75)." 4. Insight Generation: - "This suggests that rising rates are disproportionately affecting younger borrowers." - "We can mitigate risk by adjusting underwriting criteria for this segment." 5. Recommendation & Action: - "We propose tightening LTV requirements for younger applicants." - "Implement a targeted communication campaign to offer refinancing options." 6. Call to Action: - "Adopt the proposed changes by Q3." - "Monitor the segment monthly and report to the Risk Committee." Example Story: "In 2023, our consumer loan portfolio grew by 18%, but we observed a troubling trend: delinquency rates among millennials rose by 22%. Analysis of macroeconomic factors shows that rising rents and student debt are straining this cohort. Using predictive models, we identified that credit card utilisation above 70% is a key early warning signal. We recommend implementing a real-time alert system that triggers when customers exceed this threshold, allowing proactive outreach. This could reduce delinquencies by an estimated 15% and save $5M annually." """) # ---------------------------------------------------------------- # PART H: BEST PRACTICES SUMMARY # ---------------------------------------------------------------- print("\n" + "-"*60) print("PART H: Data Visualisation Best Practices") print("-"*60) best_practices = { "Colour": { "Guideline": "Use colour purposefully; avoid over-saturation.", "Do": "Use blue for stable trends, red for negative, green for positive.", "Don't": "Use rainbow colormaps; use colour-blind-friendly palettes." }, "Labels": { "Guideline": "Label axes and data points clearly; use titles.", "Do": "Include units, percentages, and currency symbols.", "Don't": "Leave axes unlabelled or use cryptic abbreviations." }, "Annotations": { "Guideline": "Add context with annotations and callouts.", "Do": "Highlight key events, outliers, and thresholds.", "Don't": "Clutter the chart with excessive text." }, "Scales": { "Guideline": "Use appropriate scales; avoid truncation.", "Do": "Start y-axis at zero for bar charts; use logarithmic for exponential data.", "Don't": "Manipulate scales to exaggerate differences." }, "Interactivity": { "Guideline": "Enable exploration with tooltips, zoom, and filtering.", "Do": "Use hover tooltips to show detailed values.", "Don't": "Overcomplicate with too many interactive elements." }, "Consistency": { "Guideline": "Maintain consistent design across dashboards.", "Do": "Use a uniform colour palette, font, and layout.", "Don't": "Mix different styles in the same report." } } for category, details in best_practices.items(): print(f"\n{category}:") print(f" Guideline: {details['Guideline']}") print(f" Do: {details['Do']}") print(f" Don't: {details['Don't']}") print("\n" + "="*70) print("END OF BONUS LESSON 8") print("="*70)
SECTION 7: SUMMARY FOR THE DATA PRACTITIONER
-
Effective visualisation is critical for communicating financial insights.
-
Principles: Clarity, accuracy, efficiency, consistency, context, and emotion.
-
Chart selection depends on data type and the story you want to tell.
-
The grammar of graphics provides a structured way to build visualisations.
-
Interactive dashboards enable exploration and decision-making.
-
Storytelling transforms data into a compelling narrative that drives action.
-
Best practices include thoughtful use of colour, labels, annotations, and consistency.
SECTION 8: RECOMMENDED NEXT STEPS
-
Build an interactive dashboard for a real financial dataset using Plotly Dash or Streamlit.
-
Practice creating different chart types for different financial use cases.
-
Learn advanced Plotly features (subplots, animations, custom layouts).
-
Study data storytelling techniques from experts (e.g., Cole Nussbaumer Knaflic).
-
Incorporate visualisation into your daily workflow – always visualise before analysing.