Risk Dashboard#

Comprehensive visualization framework for corporate social responsibility metrics#

Imports and Configuration#

Importing all required libraries and setting up configuration for the visualization system.

import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
import warnings
import os

# Suppress warnings for cleaner notebook output
warnings.filterwarnings('ignore')

Visual Design System#

Establishing a consistent visual design system with carefully selected colors, typography, and layouts for all visualizations.

# Design system: Color palette
COLOR_PALETTE = {
    "primary": "#2E5EAA",      # Primary blue for main elements
    "secondary": "#5E366E",    # Secondary purple for accents
    "accent": "#F08B33",       # Orange accent for highlights
    "background": "#F9F9F9",   # Light background
    "text": "#333333",         # Dark text
    "success": "#3AA655",      # Green for positive metrics
    "warning": "#FFC857",      # Yellow for medium risk
    "danger": "#D64045",       # Red for high risk
    "neutral": "#7D8491"       # Gray for neutral elements
}

# Industry-specific colors for consistent visualization
INDUSTRY_COLORS = {
    "Energy": "#E63946",
    "Technology": "#457B9D",
    "Manufacturing": "#F8961E", 
    "Financial Services": "#43AA8B",
    "Healthcare": "#577590",
    "Consumer Goods": "#90BE6D",
    "Others": "#9D4EDD"
}

# Typography settings
TITLE_FONT = {
    "family": "Arial, sans-serif",
    "size": 22,
    "color": COLOR_PALETTE["text"]
}

AXIS_FONT = {
    "family": "Arial, sans-serif",
    "size": 14,
    "color": COLOR_PALETTE["text"]
}

Sample Data Generator#

Creating a sample data generator to ensure the notebook can run even without access to the original data files.

def generate_sample_data(n_companies=50):
    """
    Generate sample data for demonstration purposes when actual data files are not available.
    
    Args:
        n_companies: Number of synthetic companies to generate
        
    Returns:
        DataFrame with synthetic data
    """
    # Set random seed for reproducibility
    np.random.seed(42)
    
    # Define industries
    industries = list(INDUSTRY_COLORS.keys())
    
    # Generate company data
    data = {
        'Company Name': [f"Company {i+1}" for i in range(n_companies)],
        'Industry': np.random.choice(industries, size=n_companies),
        'CO2_Emissions': np.random.exponential(scale=10000, size=n_companies),
        'Legal_Penalties': np.random.exponential(scale=10, size=n_companies),
        'Alive_Compliance': np.random.uniform(20, 100, size=n_companies),
        'Year': np.random.choice([2023, 2024], size=n_companies),
        'Patterns': np.random.uniform(1, 10, size=n_companies)
    }
    
    # Create DataFrame
    df = pd.DataFrame(data)
    
    # Calculate PSI (Penalty Severity Index)
    revenue = np.random.uniform(500, 5000, size=n_companies)
    df['Revenue'] = revenue
    epsilon = 1e-6
    df['PSI'] = np.log10((df['Legal_Penalties'] / revenue * 1e6) + epsilon)
    
    # Calculate SRI (Social Responsibility Index)
    compliance_weight = 0.4
    emissions_weight = 0.3
    penalties_weight = 0.3
    
    # Normalize emissions (higher values are worse, so invert)
    max_emissions = df['CO2_Emissions'].max()
    emissions_score = 100 - (df['CO2_Emissions'] / max_emissions * 100)
    
    # Normalize penalties (higher values are worse, so invert)
    max_penalties = df['Legal_Penalties'].max()
    penalties_score = 100 - (df['Legal_Penalties'] / max_penalties * 100)
    
    # Calculate SRI
    df['SRI'] = (
        compliance_weight * df['Alive_Compliance'] + 
        emissions_weight * emissions_score + 
        penalties_weight * penalties_score
    )
    
    # Add anomaly tag to 10% of companies
    n_anomalies = int(n_companies * 0.1)
    anomaly_indices = np.random.choice(n_companies, size=n_anomalies, replace=False)
    df['Anomaly'] = 'Normal'
    df.loc[anomaly_indices, 'Anomaly'] = 'Anomaly'
    
    return df

Data Loading#

Loading and preparing data with fallback to sample data if actual files are not available.

def load_data():
    """
    Load data from files or generate sample data if files are not available
    
    Returns:
        DataFrame with data for visualization and PDF insights text
    """
    excel_path = "Alive Analysis_WEF Partners.xlsx"
    pdf_path = "Alive Analysis One-Pager.pdf"
    
    try:
        # Try to read actual data from Excel file
        print(f"Attempting to load data from {excel_path}...")
        df = pd.read_excel(excel_path, sheet_name="WEF Partners")
        
        # Process numeric columns
        df["CO2_Emissions"] = pd.to_numeric(df["CO2 output per year 2023/24"], errors="coerce").fillna(0)
        df["Patterns"] = pd.to_numeric(df["Patterns detected numerical 2023/24"], errors="coerce").fillna(0)
        
        # Extract legal penalties assuming format like "$X.YM"
        df["Legal_Penalties"] = df["Legal Penalties and Fines in 2023 / 2024"].str.extract(r"\$(\d+\.?\d*)M").astype(float)
        
        # Extract Alive compliance score
        df["Alive_Compliance"] = pd.to_numeric(df["Alive Standards Compliance Score"], errors="coerce").fillna(50)
        
        # Add additional derived metrics
        # SRI calculation would go here
        df['SRI'] = df['Alive_Compliance']  # Simplified placeholder
        
        # Try to load PDF insights
        insights = "Corporate social responsibility insights"
        if os.path.exists(pdf_path):
            try:
                from PyPDF2 import PdfReader
                with open(pdf_path, "rb") as f:
                    reader = PdfReader(f)
                    insights = " ".join([page.extract_text() for page in reader.pages[:2]])
            except Exception as e:
                print(f"Error reading PDF: {e}. Using placeholder insights.")
        
        print("Real data loaded successfully.")
        return df, insights
        
    except Exception as e:
        print(f"Error loading real data: {e}")
        print("Generating sample data instead...")
        
        # Generate sample data
        df = generate_sample_data(50)
        
        # Sample insights text
        insights = """This analysis shows corporate social responsibility metrics across industries.
        The data highlights how various companies perform in terms of environmental impact,
        compliance with standards, and legal penalties. Key observations include significant
        variations across industries, with Energy showing higher emissions and Technology
        demonstrating better compliance rates overall."""
        
        print("Sample data generated successfully.")
        return df, insights
# Load data
df, insights = load_data()

Visualization Components#

Building visualization functions for different types of analysis.

def create_risk_matrix(df, selected_industries=None, year=2024):
    """
    Create an interactive risk matrix scatter plot
    
    Args:
        df: Processed dataframe with required columns
        selected_industries: List of industries to filter by (None for all)
        year: Year to filter data by
        
    Returns:
        Plotly figure object
    """
    # Filter data by selected industries and year
    filtered_df = df.copy()
    
    if selected_industries:
        filtered_df = filtered_df[filtered_df['Industry'].isin(selected_industries)]
    
    if 'Year' in filtered_df.columns:
        filtered_df = filtered_df[filtered_df['Year'] == year]
    
    # Create quadrant boundaries
    compliance_mid = 50
    penalty_mid = filtered_df['Legal_Penalties'].median()
    
    # Create the scatter plot
    fig = px.scatter(
        filtered_df,
        x='Alive_Compliance',
        y='Legal_Penalties',
        size='CO2_Emissions',
        color='Industry',
        color_discrete_map=INDUSTRY_COLORS,
        hover_name='Company Name',
        title=f'Social Responsibility Risk Matrix {year}',
        labels={
            'Alive_Compliance': 'Alive Standards Compliance Score (%)',
            'Legal_Penalties': 'Legal Penalties ($ Millions)',
            'CO2_Emissions': 'CO₂ Emissions (tons)',
            'Industry': 'Industry'
        }
    )
    
    # Add quadrant lines
    fig.add_shape(
        type="line", line=dict(dash="dash", color=COLOR_PALETTE["neutral"]),
        x0=compliance_mid, y0=0, x1=compliance_mid, y1=filtered_df['Legal_Penalties'].max()*1.1
    )
    fig.add_shape(
        type="line", line=dict(dash="dash", color=COLOR_PALETTE["neutral"]),
        x0=0, y0=penalty_mid, x1=100, y1=penalty_mid
    )
    
    # Add quadrant labels
    annotations = [
        dict(
            x=25, y=filtered_df['Legal_Penalties'].max()*0.9,
            text="Critical Zone (Q4):<br>Low compliance<br>High penalties",
            showarrow=False, font=dict(color=COLOR_PALETTE["danger"], size=12)
        ),
        dict(
            x=75, y=filtered_df['Legal_Penalties'].max()*0.9,
            text="Greenwashing Alert (Q2):<br>High compliance<br>High penalties",
            showarrow=False, font=dict(color=COLOR_PALETTE["warning"], size=12)
        ),
        dict(
            x=25, y=filtered_df['Legal_Penalties'].min()*1.5,
            text="Risk Potential (Q3):<br>Low compliance<br>Low penalties",
            showarrow=False, font=dict(color=COLOR_PALETTE["warning"], size=12)
        ),
        dict(
            x=75, y=filtered_df['Legal_Penalties'].min()*1.5,
            text="Leading Zone (Q1):<br>High compliance<br>Low penalties",
            showarrow=False, font=dict(color=COLOR_PALETTE["success"], size=12)
        )
    ]
    for annotation in annotations:
        fig.add_annotation(annotation)
    
    # Set axes ranges
    fig.update_xaxes(range=[0, 100])
    fig.update_yaxes(range=[0, filtered_df['Legal_Penalties'].max()*1.1])
    
    # Apply consistent styling
    fig.update_layout(
        template="plotly_white",
        paper_bgcolor=COLOR_PALETTE["background"],
        plot_bgcolor=COLOR_PALETTE["background"],
        font=TITLE_FONT,
        legend=dict(
            orientation="h",
            yanchor="bottom",
            y=1.02,
            xanchor="right",
            x=1
        ),
        margin=dict(l=60, r=30, t=80, b=60),
        hoverlabel=dict(
            bgcolor="white",
            font_size=14,
            font_family="Arial, sans-serif"
        )
    )
    
    return fig

def create_emissions_chart(df, selected_industries=None):
    """
    Create a bar chart comparing CO2 emissions by industry
    
    Args:
        df: Processed dataframe with required columns
        selected_industries: List of industries to filter by (None for all)
        
    Returns:
        Plotly figure object
    """
    # Filter data by selected industries
    filtered_df = df.copy()
    if selected_industries:
        filtered_df = filtered_df[filtered_df['Industry'].isin(selected_industries)]
    
    # Group by industry and calculate total emissions
    industry_emissions = filtered_df.groupby('Industry')['CO2_Emissions'].sum().reset_index()
    industry_emissions = industry_emissions.sort_values('CO2_Emissions', ascending=False)
    
    # Calculate the percentage of total emissions
    total_emissions = industry_emissions['CO2_Emissions'].sum()
    industry_emissions['Percentage'] = (industry_emissions['CO2_Emissions'] / total_emissions * 100).round(1)
    
    # Create the bar chart
    fig = go.Figure()
    
    # Add bars
    fig.add_trace(go.Bar(
        x=industry_emissions['Industry'],
        y=industry_emissions['CO2_Emissions'],
        text=industry_emissions['Percentage'].map('{}%'.format),
        textposition='auto',
        marker_color=[INDUSTRY_COLORS.get(industry, COLOR_PALETTE["neutral"]) 
                     for industry in industry_emissions['Industry']],
        hovertemplate='<b>%{x}</b><br>CO₂ Emissions: %{y:,.0f} tons<br>%{text} of total<extra></extra>'
    ))
    
    # Update layout
    fig.update_layout(
        title='CO₂ Emissions by Industry',
        xaxis=dict(title='Industry'),
        yaxis=dict(title='CO₂ Emissions (tons)'),
        template="plotly_white",
        paper_bgcolor=COLOR_PALETTE["background"],
        plot_bgcolor=COLOR_PALETTE["background"],
        font=TITLE_FONT,
        margin=dict(l=60, r=30, t=80, b=120),
    )
    
    # Rotate x-axis labels for better readability
    fig.update_xaxes(tickangle=45)
    
    return fig

Display Visualizations#

Creating and displaying the visualizations.

# Create risk matrix figure
risk_matrix_fig = create_risk_matrix(df)

# Display figure inline
risk_matrix_fig
# Create emissions chart
emissions_fig = create_emissions_chart(df)

# Display figure inline
emissions_fig

Interactive Dashboard (Static Preview)#

A preview of the dashboard that would be interactive when run in JupyterDash.

# Create interactive dashboard (static preview in the notebook)
print("""Interactive Dashboard Preview

This dashboard provides:
- Interactive filtering by industry and year
- Dynamic visualization updates
- Hover information for detailed data exploration
- Coordinated views across multiple visualizations

To run the interactive version, execute this notebook in an interactive Jupyter session or JupyterLab environment.
""")

# Filter by Energy and Technology sectors only
filtered_risk_matrix = create_risk_matrix(df, selected_industries=['Energy', 'Technology'])
filtered_risk_matrix