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:#
Choose Appropriate Time Scales: Match your kernel function to the temporal dynamics of your domain
Validate with Known Structure: Use synthetic data or known relationships to validate your approach
Consider Multiple Scales: Analyze both short-term and long-term correlations
Account for Confounds: Control for known confounding variables in your domain
Statistical Testing: Always perform appropriate statistical tests and corrections
Visualize Results: Create domain-appropriate visualizations
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!")