Applications Tutorial: Dynamic Correlations Across Research Domains#

This tutorial demonstrates how to apply dynamic correlation analysis across various research domains using the timecorr toolbox. We’ll explore applications in neuroscience, psychology, economics, climate science, and more.

Introduction#

Dynamic correlations are useful across many fields where time-varying relationships between variables are of interest. This tutorial shows how to adapt timecorr for different research contexts and data types.

Key Applications:#

  • Neuroscience: Brain connectivity analysis

  • Psychology: Behavioral pattern analysis

  • Economics: Market correlation analysis

  • Climate Science: Environmental variable relationships

  • Social Sciences: Network dynamics

  • Biology: Gene expression patterns

[ ]:
# Import necessary libraries
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats, signal
import warnings
warnings.filterwarnings('ignore')

# Import timecorr
import timecorr as tc

# Set random seed for reproducibility
np.random.seed(42)

# Set up plotting
plt.style.use('seaborn-v0_8')
sns.set_palette('husl')
%matplotlib inline

print("Libraries imported successfully!")
print(f"Timecorr version: {tc.__version__ if hasattr(tc, '__version__') else 'Unknown'}")

1. Neuroscience Application: Brain Network Connectivity#

In neuroscience, dynamic correlations can reveal how brain networks change over time during different cognitive states or tasks.

[ ]:
# Simulate brain network activity
def simulate_brain_networks(n_regions=20, n_timepoints=300, n_networks=3):
    """
    Simulate brain activity with multiple networks that show different
    connectivity patterns over time.
    """
    # Define network structure
    regions_per_network = n_regions // n_networks

    # Create base connectivity patterns
    brain_data = np.random.randn(n_timepoints, n_regions) * 0.5

    # Add network-specific patterns that change over time
    time = np.arange(n_timepoints)

    # Network 1: Default Mode Network - active during rest
    dmn_activity = np.sin(2 * np.pi * time / 100) * 0.8
    for i in range(regions_per_network):
        brain_data[:, i] += dmn_activity + np.random.randn(n_timepoints) * 0.2

    # Network 2: Task-Positive Network - active during tasks
    task_periods = (time % 50) < 25  # Task periods
    task_activity = np.where(task_periods, 1.0, 0.2)
    for i in range(regions_per_network, 2 * regions_per_network):
        brain_data[:, i] += task_activity + np.random.randn(n_timepoints) * 0.2

    # Network 3: Salience Network - transitions between states
    transition_points = np.where(np.diff(task_periods.astype(int)))[0]
    salience_activity = np.zeros(n_timepoints)
    for tp in transition_points:
        salience_activity[max(0, tp-5):min(n_timepoints, tp+5)] = 1.0
    for i in range(2 * regions_per_network, n_regions):
        brain_data[:, i] += salience_activity + np.random.randn(n_timepoints) * 0.2

    return brain_data

# Generate synthetic brain data
brain_data = simulate_brain_networks(n_regions=21, n_timepoints=300)  # 21 regions (7 per network)
region_names = [f'Region_{i+1}' for i in range(21)]
network_labels = ['DMN'] * 7 + ['Task'] * 7 + ['Salience'] * 7

print(f"Simulated brain data shape: {brain_data.shape}")
print(f"Networks: {set(network_labels)}")

# Visualize brain network activity
fig, axes = plt.subplots(3, 1, figsize=(15, 10))

# Plot activity for each network
colors = ['blue', 'red', 'green']
network_types = ['DMN', 'Task', 'Salience']

for i, (network, color) in enumerate(zip(network_types, colors)):
    network_indices = [j for j, label in enumerate(network_labels) if label == network]

    # Plot mean activity across regions in this network
    network_activity = brain_data[:, network_indices]
    mean_activity = np.mean(network_activity, axis=1)

    axes[i].plot(mean_activity, color=color, linewidth=2)
    axes[i].set_title(f'{network} Network Activity', fontsize=12, fontweight='bold')
    axes[i].set_ylabel('Activity')
    axes[i].grid(True, alpha=0.3)

    if i == 2:
        axes[i].set_xlabel('Time (TRs)')

plt.tight_layout()
plt.show()
[ ]:
# Compute dynamic functional connectivity
dynamic_fc = tc.timecorr(
    brain_data,
    weights_function=tc.gaussian_weights,
    weights_params={'var': 25}  # Moderate temporal smoothing
)

# Convert to matrix format for analysis
fc_matrices = tc.vec2mat(dynamic_fc)
print(f"Dynamic FC matrices shape: {fc_matrices.shape}")

# Analyze within-network and between-network connectivity
def analyze_network_connectivity(fc_matrices, network_labels):
    """
    Analyze connectivity within and between networks.
    """
    network_types = list(set(network_labels))
    n_timepoints = fc_matrices.shape[2]

    connectivity_measures = {}

    for net1 in network_types:
        for net2 in network_types:
            indices1 = [i for i, label in enumerate(network_labels) if label == net1]
            indices2 = [i for i, label in enumerate(network_labels) if label == net2]

            # Extract connectivity between these networks
            if net1 == net2:
                # Within-network connectivity (exclude diagonal)
                mask = np.triu(np.ones((len(indices1), len(indices1))), k=1)
                within_conn = []
                for t in range(n_timepoints):
                    submatrix = fc_matrices[np.ix_(indices1, indices1, [t])].squeeze()
                    within_conn.append(np.mean(submatrix[mask == 1]))
                connectivity_measures[f'{net1}_within'] = np.array(within_conn)
            elif net1 < net2:  # Avoid duplicates
                # Between-network connectivity
                between_conn = []
                for t in range(n_timepoints):
                    submatrix = fc_matrices[np.ix_(indices1, indices2, [t])].squeeze()
                    between_conn.append(np.mean(submatrix))
                connectivity_measures[f'{net1}_{net2}'] = np.array(between_conn)

    return connectivity_measures

# Analyze connectivity patterns
connectivity_measures = analyze_network_connectivity(fc_matrices, network_labels)

# Plot connectivity patterns
fig, axes = plt.subplots(2, 3, figsize=(18, 10))
axes = axes.flatten()

for i, (measure_name, connectivity) in enumerate(connectivity_measures.items()):
    if i < 6:  # Plot first 6 measures
        axes[i].plot(connectivity, linewidth=2)
        axes[i].set_title(f'{measure_name.replace("_", "-")} Connectivity',
                         fontsize=12, fontweight='bold')
        axes[i].set_xlabel('Time (TRs)')
        axes[i].set_ylabel('Connectivity')
        axes[i].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

2. Economic Application: Market Correlation Analysis#

In finance, dynamic correlations can reveal changing relationships between asset prices, market sectors, or economic indicators.

[ ]:
# Simulate financial market data
def simulate_market_data(n_assets=10, n_days=500, sectors=['Tech', 'Finance', 'Energy']):
    """
    Simulate stock price returns with sector-specific patterns and market events.
    """
    np.random.seed(42)

    # Create asset names and sector assignments
    assets_per_sector = n_assets // len(sectors)
    asset_names = []
    sector_labels = []

    for i, sector in enumerate(sectors):
        for j in range(assets_per_sector):
            asset_names.append(f'{sector}_{j+1}')
            sector_labels.append(sector)

    # Add remaining assets to first sector
    remaining = n_assets - len(asset_names)
    for j in range(remaining):
        asset_names.append(f'{sectors[0]}_{assets_per_sector + j + 1}')
        sector_labels.append(sectors[0])

    # Generate base returns
    returns = np.random.randn(n_days, n_assets) * 0.02  # 2% daily volatility

    # Add market-wide trends
    time = np.arange(n_days)
    market_trend = np.sin(2 * np.pi * time / 100) * 0.01

    # Add sector-specific patterns
    for i, sector in enumerate(sectors):
        sector_indices = [j for j, label in enumerate(sector_labels) if label == sector]

        if sector == 'Tech':
            # Tech sector: high volatility, growth trend
            tech_pattern = np.cumsum(np.random.randn(n_days) * 0.005) * 0.1
            for idx in sector_indices:
                returns[:, idx] += market_trend + tech_pattern + np.random.randn(n_days) * 0.01

        elif sector == 'Finance':
            # Finance sector: correlated with interest rates
            interest_rate_proxy = np.sin(2 * np.pi * time / 200) * 0.02
            for idx in sector_indices:
                returns[:, idx] += market_trend + interest_rate_proxy + np.random.randn(n_days) * 0.015

        elif sector == 'Energy':
            # Energy sector: volatile, commodity-driven
            commodity_shock = np.zeros(n_days)
            shock_points = np.random.choice(n_days, size=5, replace=False)
            for sp in shock_points:
                commodity_shock[max(0, sp-10):min(n_days, sp+10)] = np.random.randn() * 0.05
            for idx in sector_indices:
                returns[:, idx] += market_trend + commodity_shock + np.random.randn(n_days) * 0.02

    # Add market crash event
    crash_day = n_days // 2
    crash_magnitude = -0.1  # 10% drop
    crash_recovery = np.exp(-np.arange(20) / 5)  # Exponential recovery

    for i in range(min(20, n_days - crash_day)):
        returns[crash_day + i, :] += crash_magnitude * crash_recovery[i]

    return returns, asset_names, sector_labels

# Generate market data
market_returns, asset_names, sector_labels = simulate_market_data(n_assets=12, n_days=400)

print(f"Market data shape: {market_returns.shape}")
print(f"Assets: {asset_names}")
print(f"Sectors: {set(sector_labels)}")

# Visualize market data
fig, axes = plt.subplots(2, 1, figsize=(15, 10))

# Plot cumulative returns
cumulative_returns = np.cumprod(1 + market_returns, axis=0)
for i, (asset, sector) in enumerate(zip(asset_names, sector_labels)):
    color = {'Tech': 'blue', 'Finance': 'red', 'Energy': 'green'}[sector]
    axes[0].plot(cumulative_returns[:, i], label=asset, color=color, alpha=0.7)

axes[0].set_title('Cumulative Returns by Asset', fontsize=14, fontweight='bold')
axes[0].set_xlabel('Trading Days')
axes[0].set_ylabel('Cumulative Return')
axes[0].legend(bbox_to_anchor=(1.05, 1), loc='upper left')
axes[0].grid(True, alpha=0.3)

# Plot sector average returns
sectors = list(set(sector_labels))
for sector in sectors:
    sector_indices = [i for i, label in enumerate(sector_labels) if label == sector]
    sector_returns = np.mean(market_returns[:, sector_indices], axis=1)
    sector_cumulative = np.cumprod(1 + sector_returns)

    color = {'Tech': 'blue', 'Finance': 'red', 'Energy': 'green'}[sector]
    axes[1].plot(sector_cumulative, label=sector, color=color, linewidth=3)

axes[1].set_title('Sector Average Cumulative Returns', fontsize=14, fontweight='bold')
axes[1].set_xlabel('Trading Days')
axes[1].set_ylabel('Cumulative Return')
axes[1].legend()
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()
[ ]:
# Compute dynamic correlations in financial markets
dynamic_corr_short = tc.timecorr(
    market_returns,
    weights_function=tc.gaussian_weights,
    weights_params={'var': 25}  # Short-term correlations (1 month)
)

dynamic_corr_long = tc.timecorr(
    market_returns,
    weights_function=tc.gaussian_weights,
    weights_params={'var': 100}  # Long-term correlations (quarterly)
)

# Convert to matrix format
corr_matrices_short = tc.vec2mat(dynamic_corr_short)
corr_matrices_long = tc.vec2mat(dynamic_corr_long)

print(f"Short-term correlation matrices shape: {corr_matrices_short.shape}")
print(f"Long-term correlation matrices shape: {corr_matrices_long.shape}")

# Analyze market correlation during different periods
def analyze_market_periods(corr_matrices, period_name):
    """
    Analyze market correlations during different periods.
    """
    n_days = corr_matrices.shape[2]

    # Define periods
    normal_period = slice(0, n_days//2 - 20)
    crash_period = slice(n_days//2 - 20, n_days//2 + 20)
    recovery_period = slice(n_days//2 + 20, n_days)

    periods = {
        'Normal': normal_period,
        'Crash': crash_period,
        'Recovery': recovery_period
    }

    period_stats = {}

    for period_name, period_slice in periods.items():
        period_corrs = corr_matrices[:, :, period_slice]

        # Compute average correlation matrix for this period
        avg_corr = np.mean(period_corrs, axis=2)

        # Compute average correlation (excluding diagonal)
        mask = np.triu(np.ones_like(avg_corr), k=1)
        avg_correlation = np.mean(avg_corr[mask == 1])

        period_stats[period_name] = {
            'avg_corr_matrix': avg_corr,
            'avg_correlation': avg_correlation,
            'period_slice': period_slice
        }

    return period_stats

# Analyze both short-term and long-term correlations
short_term_stats = analyze_market_periods(corr_matrices_short, 'Short-term')
long_term_stats = analyze_market_periods(corr_matrices_long, 'Long-term')

# Plot correlation matrices for different periods
fig, axes = plt.subplots(2, 3, figsize=(18, 12))

periods = ['Normal', 'Crash', 'Recovery']

for i, period in enumerate(periods):
    # Short-term correlations
    sns.heatmap(short_term_stats[period]['avg_corr_matrix'],
                annot=False, cmap='RdBu_r', center=0, vmin=-1, vmax=1,
                ax=axes[0, i], cbar=True)
    axes[0, i].set_title(f'Short-term: {period} Period\n(Avg Corr: {short_term_stats[period]["avg_correlation"]:.3f})')
    axes[0, i].set_xticklabels(asset_names, rotation=45)
    axes[0, i].set_yticklabels(asset_names, rotation=0)

    # Long-term correlations
    sns.heatmap(long_term_stats[period]['avg_corr_matrix'],
                annot=False, cmap='RdBu_r', center=0, vmin=-1, vmax=1,
                ax=axes[1, i], cbar=True)
    axes[1, i].set_title(f'Long-term: {period} Period\n(Avg Corr: {long_term_stats[period]["avg_correlation"]:.3f})')
    axes[1, i].set_xticklabels(asset_names, rotation=45)
    axes[1, i].set_yticklabels(asset_names, rotation=0)

plt.tight_layout()
plt.show()

# Plot time series of average correlations
fig, ax = plt.subplots(figsize=(15, 6))

# Compute average correlation over time
def compute_avg_correlation_timeseries(corr_matrices):
    n_days = corr_matrices.shape[2]
    avg_corrs = []

    for t in range(n_days):
        corr_matrix = corr_matrices[:, :, t]
        mask = np.triu(np.ones_like(corr_matrix), k=1)
        avg_corr = np.mean(corr_matrix[mask == 1])
        avg_corrs.append(avg_corr)

    return np.array(avg_corrs)

avg_corr_short = compute_avg_correlation_timeseries(corr_matrices_short)
avg_corr_long = compute_avg_correlation_timeseries(corr_matrices_long)

ax.plot(avg_corr_short, label='Short-term (1 month)', linewidth=2, alpha=0.8)
ax.plot(avg_corr_long, label='Long-term (quarterly)', linewidth=2, alpha=0.8)
ax.axvline(x=len(market_returns)//2, color='red', linestyle='--', alpha=0.7, label='Market Crash')

ax.set_title('Average Market Correlation Over Time', fontsize=14, fontweight='bold')
ax.set_xlabel('Trading Days')
ax.set_ylabel('Average Correlation')
ax.legend()
ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

print("\nKey Insights:")
print(f"• Normal period average correlation: {short_term_stats['Normal']['avg_correlation']:.3f}")
print(f"• Crash period average correlation: {short_term_stats['Crash']['avg_correlation']:.3f}")
print(f"• Recovery period average correlation: {short_term_stats['Recovery']['avg_correlation']:.3f}")
print(f"• Correlation increase during crash: {short_term_stats['Crash']['avg_correlation'] - short_term_stats['Normal']['avg_correlation']:.3f}")

3. Climate Science Application: Environmental Variable Relationships#

In climate science, dynamic correlations can reveal changing relationships between environmental variables like temperature, precipitation, and atmospheric conditions.

[ ]:
# Simulate climate data
def simulate_climate_data(n_years=20, locations=['Arctic', 'Temperate', 'Tropical']):
    """
    Simulate climate data with seasonal patterns and long-term trends.
    """
    n_days = n_years * 365
    time = np.arange(n_days)

    # Create variable names
    variables = ['Temperature', 'Precipitation', 'Humidity', 'Wind_Speed']
    data = np.zeros((n_days, len(locations) * len(variables)))
    column_names = []

    for i, location in enumerate(locations):
        for j, variable in enumerate(variables):
            col_idx = i * len(variables) + j
            column_names.append(f'{location}_{variable}')

            # Seasonal patterns
            seasonal = np.sin(2 * np.pi * time / 365.25)  # Annual cycle

            # Location-specific modifications
            if location == 'Arctic':
                seasonal *= 2  # Extreme seasonal variation
                base_temp = -10
            elif location == 'Temperate':
                seasonal *= 1  # Moderate seasonal variation
                base_temp = 15
            else:  # Tropical
                seasonal *= 0.3  # Minimal seasonal variation
                base_temp = 25

            # Variable-specific patterns
            if variable == 'Temperature':
                data[:, col_idx] = base_temp + seasonal * 20 + np.random.randn(n_days) * 3
            elif variable == 'Precipitation':
                # Precipitation inversely correlated with temperature in some regions
                precip_seasonal = -seasonal if location == 'Tropical' else seasonal
                data[:, col_idx] = np.maximum(0, 50 + precip_seasonal * 30 + np.random.randn(n_days) * 15)
            elif variable == 'Humidity':
                # Humidity related to temperature and precipitation
                base_humidity = 70 if location == 'Tropical' else 50
                data[:, col_idx] = base_humidity + seasonal * 10 + np.random.randn(n_days) * 5
            elif variable == 'Wind_Speed':
                # Wind speed with seasonal patterns
                data[:, col_idx] = np.maximum(0, 10 + seasonal * 5 + np.random.randn(n_days) * 3)

    # Add long-term climate change trend
    climate_trend = np.linspace(0, 2, n_days)  # 2 degree warming over period
    for i, col_name in enumerate(column_names):
        if 'Temperature' in col_name:
            data[:, i] += climate_trend

    # Add extreme weather events
    n_events = 10
    event_days = np.random.choice(n_days, size=n_events, replace=False)
    for event_day in event_days:
        # Extreme weather affects multiple variables
        for i, col_name in enumerate(column_names):
            if 'Temperature' in col_name:
                data[event_day:event_day+3, i] += np.random.randn() * 10
            elif 'Precipitation' in col_name:
                data[event_day:event_day+3, i] *= (1 + np.random.randn() * 0.5)

    return data, column_names

# Generate climate data
climate_data, climate_variables = simulate_climate_data(n_years=10)

print(f"Climate data shape: {climate_data.shape}")
print(f"Variables: {climate_variables}")

# Visualize climate data
fig, axes = plt.subplots(2, 2, figsize=(16, 10))
axes = axes.flatten()

variable_types = ['Temperature', 'Precipitation', 'Humidity', 'Wind_Speed']
colors = ['red', 'blue', 'green', 'orange']

for i, var_type in enumerate(variable_types):
    # Plot all locations for this variable type
    for j, var_name in enumerate(climate_variables):
        if var_type in var_name:
            location = var_name.split('_')[0]
            # Plot monthly averages for clarity - use proper monthly averaging
            n_months = len(climate_data) // 30
            monthly_data = np.array([climate_data[k*30:(k+1)*30, j].mean() for k in range(n_months)])
            axes[i].plot(monthly_data, label=location, alpha=0.8)

    axes[i].set_title(f'{var_type} Over Time', fontsize=12, fontweight='bold')
    axes[i].set_xlabel('Months')
    axes[i].set_ylabel(var_type)
    axes[i].legend()
    axes[i].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()
[ ]:
# Compute dynamic correlations in climate data
# Use different time scales for different analyses

# Short-term correlations (seasonal)
climate_corr_seasonal = tc.timecorr(
    climate_data,
    weights_function=tc.gaussian_weights,
    weights_params={'var': 365}  # Annual window
)

# Long-term correlations (multi-year trends)
climate_corr_longterm = tc.timecorr(
    climate_data,
    weights_function=tc.gaussian_weights,
    weights_params={'var': 1000}  # Multi-year window
)

# Convert to matrix format
climate_matrices_seasonal = tc.vec2mat(climate_corr_seasonal)
climate_matrices_longterm = tc.vec2mat(climate_corr_longterm)

print(f"Seasonal correlation matrices shape: {climate_matrices_seasonal.shape}")
print(f"Long-term correlation matrices shape: {climate_matrices_longterm.shape}")

# Analyze temperature-precipitation relationships
def analyze_climate_relationships(corr_matrices, variable_names):
    """
    Analyze relationships between climate variables.
    """
    relationships = {}

    # Find temperature and precipitation indices
    temp_indices = [i for i, name in enumerate(variable_names) if 'Temperature' in name]
    precip_indices = [i for i, name in enumerate(variable_names) if 'Precipitation' in name]

    locations = ['Arctic', 'Temperate', 'Tropical']

    for location in locations:
        temp_idx = [i for i, name in enumerate(variable_names) if f'{location}_Temperature' in name][0]
        precip_idx = [i for i, name in enumerate(variable_names) if f'{location}_Precipitation' in name][0]

        # Extract temperature-precipitation correlation over time
        temp_precip_corr = corr_matrices[temp_idx, precip_idx, :]
        relationships[f'{location}_Temp_Precip'] = temp_precip_corr

    return relationships

seasonal_relationships = analyze_climate_relationships(climate_matrices_seasonal, climate_variables)
longterm_relationships = analyze_climate_relationships(climate_matrices_longterm, climate_variables)

# Plot temperature-precipitation relationships
fig, axes = plt.subplots(2, 1, figsize=(15, 10))

locations = ['Arctic', 'Temperate', 'Tropical']
colors = ['blue', 'green', 'red']

# Seasonal relationships
for location, color in zip(locations, colors):
    relationship = seasonal_relationships[f'{location}_Temp_Precip']
    # Plot monthly averages using proper monthly averaging
    n_months = len(relationship) // 30
    monthly_rel = np.array([relationship[k*30:(k+1)*30].mean() for k in range(n_months)])
    axes[0].plot(monthly_rel, label=location, color=color, linewidth=2)

axes[0].set_title('Temperature-Precipitation Correlation (Seasonal)', fontsize=14, fontweight='bold')
axes[0].set_xlabel('Months')
axes[0].set_ylabel('Correlation')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
axes[0].set_ylim([-1, 1])

# Long-term relationships
for location, color in zip(locations, colors):
    relationship = longterm_relationships[f'{location}_Temp_Precip']
    # Plot monthly averages using proper monthly averaging
    n_months = len(relationship) // 30
    monthly_rel = np.array([relationship[k*30:(k+1)*30].mean() for k in range(n_months)])
    axes[1].plot(monthly_rel, label=location, color=color, linewidth=2)

axes[1].set_title('Temperature-Precipitation Correlation (Long-term)', fontsize=14, fontweight='bold')
axes[1].set_xlabel('Months')
axes[1].set_ylabel('Correlation')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
axes[1].set_ylim([-1, 1])

plt.tight_layout()
plt.show()

# Show correlation matrices at different time periods
fig, axes = plt.subplots(1, 3, figsize=(18, 6))

time_periods = [len(climate_data)//4, len(climate_data)//2, 3*len(climate_data)//4]
period_names = ['Early Period', 'Middle Period', 'Late Period']

for i, (time_point, period_name) in enumerate(zip(time_periods, period_names)):
    # Use monthly averages for time point
    monthly_time_point = time_point // 30
    if monthly_time_point < climate_matrices_seasonal.shape[2]:
        corr_matrix = climate_matrices_seasonal[:, :, monthly_time_point]

        sns.heatmap(corr_matrix, annot=False, cmap='RdBu_r', center=0, vmin=-1, vmax=1,
                    ax=axes[i], cbar=True)
        axes[i].set_title(f'{period_name}', fontsize=12)
        axes[i].set_xticklabels([name.replace('_', '\n') for name in climate_variables], rotation=45)
        axes[i].set_yticklabels([name.replace('_', '\n') for name in climate_variables], rotation=0)

plt.tight_layout()
plt.show()

4. Social Sciences Application: Social Network Dynamics#

In social sciences, dynamic correlations can reveal changing relationships in social networks, communication patterns, or behavioral synchrony.

[ ]:
# Simulate social network data
def simulate_social_network_data(n_individuals=15, n_timepoints=200):
    """
    Simulate social network activity with group formation and influence dynamics.
    """
    # Create individual names
    individuals = [f'Person_{i+1}' for i in range(n_individuals)]

    # Initialize data
    social_data = np.random.randn(n_timepoints, n_individuals) * 0.5

    # Define social groups
    group_size = n_individuals // 3
    groups = {
        'Group_A': list(range(group_size)),
        'Group_B': list(range(group_size, 2*group_size)),
        'Group_C': list(range(2*group_size, n_individuals))
    }

    # Add group-specific behaviors
    time = np.arange(n_timepoints)

    # Group A: Early adopters - lead trends
    trend_A = np.sin(2 * np.pi * time / 50) * 1.5
    for idx in groups['Group_A']:
        social_data[:, idx] += trend_A + np.random.randn(n_timepoints) * 0.3

    # Group B: Followers - adopt trends with delay
    trend_B = np.sin(2 * np.pi * (time - 10) / 50) * 1.2  # 10 time-step delay
    for idx in groups['Group_B']:
        social_data[:, idx] += trend_B + np.random.randn(n_timepoints) * 0.3

    # Group C: Independent - different pattern
    trend_C = np.cos(2 * np.pi * time / 30) * 1.0
    for idx in groups['Group_C']:
        social_data[:, idx] += trend_C + np.random.randn(n_timepoints) * 0.4

    # Add social influence - individuals influence their neighbors
    influence_strength = 0.1
    for t in range(1, n_timepoints):
        for group_name, group_indices in groups.items():
            # Within-group influence
            group_mean = np.mean(social_data[t-1, group_indices])
            for idx in group_indices:
                social_data[t, idx] += influence_strength * (group_mean - social_data[t-1, idx])

    # Add interaction events between groups
    interaction_timepoints = np.random.choice(n_timepoints, size=10, replace=False)
    for t in interaction_timepoints:
        # Random individuals from different groups interact
        person1 = np.random.choice(groups['Group_A'])
        person2 = np.random.choice(groups['Group_B'])

        # Mutual influence
        avg_behavior = (social_data[t, person1] + social_data[t, person2]) / 2
        social_data[t:t+5, person1] += 0.2 * (avg_behavior - social_data[t, person1])
        social_data[t:t+5, person2] += 0.2 * (avg_behavior - social_data[t, person2])

    return social_data, individuals, groups

# Generate social network data
social_data, individuals, groups = simulate_social_network_data(n_individuals=15, n_timepoints=200)

print(f"Social network data shape: {social_data.shape}")
print(f"Individuals: {individuals}")
print(f"Groups: {list(groups.keys())}")

# Visualize social network activity
fig, axes = plt.subplots(2, 1, figsize=(15, 10))

# Plot individual behaviors
colors = ['red', 'blue', 'green']
group_names = list(groups.keys())

for i, (group_name, group_indices) in enumerate(groups.items()):
    for idx in group_indices:
        axes[0].plot(social_data[:, idx], color=colors[i], alpha=0.6, linewidth=1)

    # Plot group average
    group_avg = np.mean(social_data[:, group_indices], axis=1)
    axes[0].plot(group_avg, color=colors[i], linewidth=3, label=f'{group_name} Average')

axes[0].set_title('Individual Social Behaviors Over Time', fontsize=14, fontweight='bold')
axes[0].set_xlabel('Time')
axes[0].set_ylabel('Behavior Measure')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# Plot group correlations over time
for i, (group_name, group_indices) in enumerate(groups.items()):
    group_data = social_data[:, group_indices]

    # Compute within-group correlations over time windows
    window_size = 20
    within_group_corrs = []

    for t in range(window_size, len(social_data) - window_size):
        window_data = group_data[t-window_size:t+window_size, :]
        corr_matrix = np.corrcoef(window_data.T)

        # Average correlation (excluding diagonal)
        mask = np.triu(np.ones_like(corr_matrix), k=1)
        if np.sum(mask) > 0:
            avg_corr = np.mean(corr_matrix[mask == 1])
            within_group_corrs.append(avg_corr)
        else:
            within_group_corrs.append(0)

    time_axis = np.arange(window_size, len(social_data) - window_size)
    axes[1].plot(time_axis, within_group_corrs, color=colors[i], linewidth=2, label=f'{group_name} Cohesion')

axes[1].set_title('Group Cohesion Over Time', fontsize=14, fontweight='bold')
axes[1].set_xlabel('Time')
axes[1].set_ylabel('Average Within-Group Correlation')
axes[1].legend()
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()
[ ]:
# Compute dynamic correlations in social network
social_dynamic_corr = tc.timecorr(
    social_data,
    weights_function=tc.gaussian_weights,
    weights_params={'var': 20}  # Medium-term social influence window
)

# Convert to matrix format
social_corr_matrices = tc.vec2mat(social_dynamic_corr)

print(f"Social correlation matrices shape: {social_corr_matrices.shape}")

# Analyze social network structure
def analyze_social_network_structure(corr_matrices, groups):
    """
    Analyze within-group and between-group social connections.
    """
    n_timepoints = corr_matrices.shape[2]
    group_names = list(groups.keys())

    # Compute within-group and between-group correlations
    network_measures = {}

    # Within-group correlations
    for group_name, group_indices in groups.items():
        within_group_corrs = []
        for t in range(n_timepoints):
            group_matrix = corr_matrices[np.ix_(group_indices, group_indices, [t])].squeeze()
            if len(group_indices) > 1:
                mask = np.triu(np.ones_like(group_matrix), k=1)
                if np.sum(mask) > 0:
                    avg_corr = np.mean(group_matrix[mask == 1])
                    within_group_corrs.append(avg_corr)
                else:
                    within_group_corrs.append(0)
            else:
                within_group_corrs.append(0)

        network_measures[f'{group_name}_within'] = np.array(within_group_corrs)

    # Between-group correlations
    for i, group1 in enumerate(group_names):
        for j, group2 in enumerate(group_names):
            if i < j:  # Avoid duplicates
                between_group_corrs = []
                indices1 = groups[group1]
                indices2 = groups[group2]

                for t in range(n_timepoints):
                    between_matrix = corr_matrices[np.ix_(indices1, indices2, [t])].squeeze()
                    avg_corr = np.mean(between_matrix)
                    between_group_corrs.append(avg_corr)

                network_measures[f'{group1}_{group2}'] = np.array(between_group_corrs)

    return network_measures

# Analyze network structure
network_measures = analyze_social_network_structure(social_corr_matrices, groups)

# Plot network measures
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
axes = axes.flatten()

plot_idx = 0
colors = ['red', 'blue', 'green', 'purple', 'orange', 'brown']

for measure_name, measure_values in network_measures.items():
    if plot_idx < 6:
        axes[plot_idx].plot(measure_values, color=colors[plot_idx], linewidth=2)
        axes[plot_idx].set_title(f'{measure_name.replace("_", "-")} Correlation',
                                fontsize=12, fontweight='bold')
        axes[plot_idx].set_xlabel('Time')
        axes[plot_idx].set_ylabel('Correlation')
        axes[plot_idx].grid(True, alpha=0.3)
        axes[plot_idx].set_ylim([-1, 1])
        plot_idx += 1

# Remove empty subplots
for i in range(plot_idx, 4):
    axes[i].remove()

plt.tight_layout()
plt.show()

# Show network structure at different time points
fig, axes = plt.subplots(1, 3, figsize=(18, 6))

time_points = [50, 100, 150]
time_labels = ['Early', 'Middle', 'Late']

for i, (time_point, time_label) in enumerate(zip(time_points, time_labels)):
    corr_matrix = social_corr_matrices[:, :, time_point]

    # Create group-colored visualization
    sns.heatmap(corr_matrix, annot=False, cmap='RdBu_r', center=0, vmin=-1, vmax=1,
                ax=axes[i], cbar=True)
    axes[i].set_title(f'{time_label} Period\n(t={time_point})', fontsize=12)

    # Add group boundaries
    group_boundaries = [0, 5, 10, 15]
    for boundary in group_boundaries[1:-1]:
        axes[i].axhline(y=boundary, color='white', linewidth=2)
        axes[i].axvline(x=boundary, color='white', linewidth=2)

    axes[i].set_xticklabels(individuals, rotation=45)
    axes[i].set_yticklabels(individuals, rotation=0)

plt.tight_layout()
plt.show()

print("\nKey Social Network Insights:")
print(f"• Average within-group correlation: {np.mean([np.mean(network_measures[key]) for key in network_measures.keys() if 'within' in key]):.3f}")
print(f"• Average between-group correlation: {np.mean([np.mean(network_measures[key]) for key in network_measures.keys() if 'within' not in key]):.3f}")
print(f"• Group cohesion varies over time, showing social dynamics")
print(f"• Between-group connections emerge through social interactions")

5. Summary and Best Practices for Different Domains#

This tutorial has demonstrated how to apply dynamic correlation analysis across various research domains. Here are key takeaways for each field:

Domain-Specific Considerations:#

Neuroscience#

  • Time Scale: Use TR-appropriate kernels (Gaussian with var=25-100 for fMRI)

  • Networks: Focus on within-network and between-network connectivity

  • Validation: Compare with known anatomical connections

Economics/Finance#

  • Time Scale: Short-term (daily) vs. long-term (quarterly) correlations

  • Events: Account for market events and volatility clustering

  • Sectors: Analyze sector-specific vs. market-wide correlations

Climate Science#

  • Seasonality: Account for strong seasonal patterns

  • Geography: Consider spatial relationships between locations

  • Trends: Separate long-term trends from short-term variations

Social Sciences#

  • Groups: Analyze within-group vs. between-group dynamics

  • Influence: Consider social influence and contagion effects

  • Events: Account for social events and interactions

General Best Practices:#

  1. Choose Appropriate Time Scales: Match your kernel function to the temporal dynamics of your domain

  2. Validate with Known Structure: Use synthetic data or known relationships to validate your approach

  3. Consider Multiple Scales: Analyze both short-term and long-term correlations

  4. Account for Confounds: Control for known confounding variables in your domain

  5. Statistical Testing: Always perform appropriate statistical tests and corrections

  6. Visualize Results: Create domain-appropriate visualizations

  7. Cross-Validation: Use multiple datasets or time periods to validate findings

Next Steps:#

  • Adapt these examples to your specific research question

  • Explore higher-order correlations for deeper insights

  • Consider multi-subject or multi-site analyses

  • Investigate graph-theoretic measures for network analysis

  • Develop domain-specific statistical tests and validation methods

[ ]:
# Final comparison across domains
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
axes = axes.flatten()

# Domain comparison data
domains = ['Neuroscience', 'Economics', 'Climate', 'Social']
example_correlations = [
    network_measures['DMN_within'],  # Neuroscience
    avg_corr_short,  # Economics
    seasonal_relationships['Arctic_Temp_Precip'],  # Climate
    network_measures['Group_A_within']  # Social
]

colors = ['blue', 'red', 'green', 'purple']

for i, (domain, correlation, color) in enumerate(zip(domains, example_correlations, colors)):
    # Normalize time axes for comparison
    time_axis = np.linspace(0, 1, len(correlation))
    axes[i].plot(time_axis, correlation, color=color, linewidth=2)
    axes[i].set_title(f'{domain}\nDynamic Correlation Example', fontsize=12, fontweight='bold')
    axes[i].set_xlabel('Normalized Time')
    axes[i].set_ylabel('Correlation')
    axes[i].grid(True, alpha=0.3)
    axes[i].set_ylim([-1, 1])

plt.tight_layout()
plt.show()

print("\n" + "="*60)
print("TUTORIAL COMPLETED SUCCESSFULLY!")
print("="*60)
print("\nYou now know how to apply dynamic correlation analysis to:")
print("✓ Neuroscience: Brain network connectivity")
print("✓ Economics: Market correlation dynamics")
print("✓ Climate Science: Environmental relationships")
print("✓ Social Sciences: Social network dynamics")
print("\nKey skills acquired:")
print("• Domain-specific data simulation")
print("• Appropriate time scale selection")
print("• Multi-scale correlation analysis")
print("• Network structure analysis")
print("• Statistical validation methods")
print("• Domain-specific visualization techniques")
print("\nReady to apply these techniques to your own research!")