Visualization
Easy PMF provides comprehensive visualization capabilities for exploring and presenting PMF analysis results. This guide covers all available plotting options and customization techniques.
Built-in Visualization Functions
Quick Plotting
import matplotlib.pyplot as plt
import seaborn as sns
from easy_pmf import PMF
# After fitting PMF
pmf = PMF(n_components=5, random_state=42)
pmf.fit(concentrations, uncertainties)
# Quick time series plot
pmf.contributions_.plot(figsize=(12, 6))
plt.title('Factor Contributions Over Time')
plt.ylabel('Contribution (μg/m³)')
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
plt.tight_layout()
plt.show()
# Quick heatmap
plt.figure(figsize=(10, 8))
sns.heatmap(pmf.profiles_, annot=True, fmt='.3f', cmap='viridis')
plt.title('Factor Profiles Heatmap')
plt.ylabel('Factors')
plt.xlabel('Chemical Species')
plt.tight_layout()
plt.show()
Comprehensive Visualization Suite
Factor Profiles Visualization
def plot_factor_profiles(pmf, source_names=None, figsize=(15, 10)):
"""Create comprehensive factor profiles visualization."""
profiles = pmf.profiles_
n_factors = len(profiles)
# Create subplots
cols = 3
rows = (n_factors + cols - 1) // cols
fig, axes = plt.subplots(rows, cols, figsize=figsize)
if rows == 1:
axes = [axes]
if cols == 1:
axes = [[ax] for ax in axes]
axes = [ax for row in axes for ax in row] # Flatten
colors = plt.cm.Set3(np.linspace(0, 1, n_factors))
for i, (factor_idx, factor_data) in enumerate(profiles.iterrows()):
if i >= len(axes):
break
ax = axes[i]
# Bar plot
bars = ax.bar(range(len(factor_data)), factor_data,
color=colors[i], alpha=0.8, edgecolor='black', linewidth=0.5)
# Formatting
factor_name = source_names[i] if source_names else f'Factor {i+1}'
ax.set_title(f'{factor_name}', fontsize=12, fontweight='bold')
ax.set_xlabel('Chemical Species')
ax.set_ylabel('Relative Abundance')
# Rotate x-axis labels
ax.set_xticks(range(len(factor_data)))
ax.set_xticklabels(factor_data.index, rotation=45, ha='right')
# Add grid
ax.grid(True, alpha=0.3, axis='y')
# Highlight top contributors
top_3_indices = factor_data.nlargest(3).index
for j, species in enumerate(factor_data.index):
if species in top_3_indices:
bars[j].set_color('red')
bars[j].set_alpha(0.9)
# Remove unused subplots
for i in range(n_factors, len(axes)):
fig.delaxes(axes[i])
plt.suptitle('Factor Profiles (Chemical Signatures)', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
# Example usage
source_names = ['Traffic', 'Coal', 'Sea Salt', 'Soil', 'Secondary Sulfate']
plot_factor_profiles(pmf, source_names)
Factor Contributions Visualization
def plot_factor_contributions(pmf, source_names=None, figsize=(15, 12)):
"""Create comprehensive factor contributions visualization."""
contributions = pmf.contributions_
fig, axes = plt.subplots(3, 2, figsize=figsize)
# 1. Time series plot
ax1 = axes[0, 0]
contributions.plot(ax=ax1, alpha=0.8)
ax1.set_title('Factor Contributions Time Series')
ax1.set_ylabel('Contribution (μg/m³)')
ax1.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
ax1.grid(True, alpha=0.3)
# 2. Stacked area plot
ax2 = axes[0, 1]
contributions.plot.area(ax=ax2, alpha=0.7, stacked=True)
ax2.set_title('Stacked Factor Contributions')
ax2.set_ylabel('Contribution (μg/m³)')
ax2.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
# 3. Box plot
ax3 = axes[1, 0]
contributions.boxplot(ax=ax3)
ax3.set_title('Contribution Distributions')
ax3.set_ylabel('Contribution (μg/m³)')
ax3.tick_params(axis='x', rotation=45)
# 4. Pie chart of average contributions
ax4 = axes[1, 1]
avg_contributions = contributions.mean()
colors = plt.cm.Set3(np.linspace(0, 1, len(avg_contributions)))
wedges, texts, autotexts = ax4.pie(avg_contributions.values,
labels=source_names if source_names else avg_contributions.index,
autopct='%1.1f%%',
colors=colors,
startangle=90)
ax4.set_title('Average Factor Contributions')
# 5. Correlation heatmap
ax5 = axes[2, 0]
corr_matrix = contributions.corr()
im = ax5.imshow(corr_matrix, cmap='RdBu_r', vmin=-1, vmax=1)
ax5.set_xticks(range(len(corr_matrix)))
ax5.set_yticks(range(len(corr_matrix)))
ax5.set_xticklabels(source_names if source_names else corr_matrix.columns, rotation=45)
ax5.set_yticklabels(source_names if source_names else corr_matrix.index)
ax5.set_title('Factor Correlation Matrix')
# Add correlation values
for i in range(len(corr_matrix)):
for j in range(len(corr_matrix)):
ax5.text(j, i, f'{corr_matrix.iloc[i, j]:.2f}',
ha='center', va='center', color='black')
# 6. Monthly patterns (if data spans multiple months)
ax6 = axes[2, 1]
if len(contributions) > 30: # If we have enough data
monthly_data = contributions.copy()
monthly_data['month'] = monthly_data.index.month
monthly_avg = monthly_data.groupby('month')[contributions.columns].mean()
monthly_avg.plot(kind='bar', ax=ax6, alpha=0.8)
ax6.set_title('Monthly Average Contributions')
ax6.set_xlabel('Month')
ax6.set_ylabel('Average Contribution')
ax6.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
ax6.tick_params(axis='x', rotation=0)
else:
ax6.text(0.5, 0.5, 'Insufficient data\nfor monthly analysis',
ha='center', va='center', transform=ax6.transAxes)
ax6.set_title('Monthly Analysis')
plt.tight_layout()
plt.show()
# Example usage
plot_factor_contributions(pmf, source_names)
EPA PMF-Style Plots
def create_epa_style_plots(pmf, concentrations, uncertainties, source_names=None):
"""Create EPA PMF-style plots for official reporting."""
contributions = pmf.contributions_
profiles = pmf.profiles_
# Calculate additional metrics
reconstructed = pd.DataFrame(
contributions.values @ profiles.values,
index=concentrations.index,
columns=concentrations.columns
)
fig, axes = plt.subplots(2, 3, figsize=(20, 12))
# 1. Time series with data points
ax1 = axes[0, 0]
total_measured = concentrations.sum(axis=1)
total_reconstructed = contributions.sum(axis=1)
ax1.scatter(contributions.index, total_measured, alpha=0.6, s=20, label='Measured', color='blue')
ax1.plot(contributions.index, total_reconstructed, color='red', linewidth=2, label='PMF Reconstruction')
ax1.set_title('Total Mass: Measured vs PMF')
ax1.set_ylabel('Concentration (μg/m³)')
ax1.legend()
ax1.grid(True, alpha=0.3)
# 2. Scatter plot: measured vs predicted
ax2 = axes[0, 1]
ax2.scatter(total_measured, total_reconstructed, alpha=0.6, s=30)
# Add 1:1 line
min_val = min(total_measured.min(), total_reconstructed.min())
max_val = max(total_measured.max(), total_reconstructed.max())
ax2.plot([min_val, max_val], [min_val, max_val], 'r--', alpha=0.8, linewidth=2)
# Add R²
r_squared = np.corrcoef(total_measured, total_reconstructed)[0, 1] ** 2
ax2.text(0.05, 0.95, f'R² = {r_squared:.3f}', transform=ax2.transAxes,
bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
ax2.set_xlabel('Measured Total Mass')
ax2.set_ylabel('PMF Reconstructed Total Mass')
ax2.set_title('Measured vs PMF Total Mass')
ax2.grid(True, alpha=0.3)
# 3. Residuals plot
ax3 = axes[0, 2]
residuals = total_measured - total_reconstructed
ax3.scatter(total_reconstructed, residuals, alpha=0.6, s=30)
ax3.axhline(y=0, color='red', linestyle='--', linewidth=2)
ax3.set_xlabel('PMF Reconstructed Total Mass')
ax3.set_ylabel('Residuals (Measured - PMF)')
ax3.set_title('Residuals vs PMF Reconstruction')
ax3.grid(True, alpha=0.3)
# 4. Factor profiles bar chart (EPA style)
ax4 = axes[1, 0]
profiles_pct = profiles.div(profiles.sum(axis=1), axis=0) * 100
x = np.arange(len(profiles.columns))
width = 0.8 / len(profiles)
colors = plt.cm.Set3(np.linspace(0, 1, len(profiles)))
for i, (factor_idx, factor_data) in enumerate(profiles_pct.iterrows()):
offset = (i - len(profiles)/2 + 0.5) * width
factor_name = source_names[i] if source_names else f'Factor {i+1}'
ax4.bar(x + offset, factor_data, width, label=factor_name,
color=colors[i], alpha=0.8, edgecolor='black', linewidth=0.5)
ax4.set_xlabel('Chemical Species')
ax4.set_ylabel('Percentage of Factor (%)')
ax4.set_title('Factor Profiles (% by Factor)')
ax4.set_xticks(x)
ax4.set_xticklabels(profiles.columns, rotation=45, ha='right')
ax4.legend()
ax4.grid(True, alpha=0.3, axis='y')
# 5. Seasonal analysis
ax5 = axes[1, 1]
if len(contributions) > 90: # Need at least ~3 months of data
seasonal_data = contributions.copy()
seasonal_data['season'] = seasonal_data.index.month % 12 // 3
season_names = ['Winter', 'Spring', 'Summer', 'Fall']
seasonal_avg = seasonal_data.groupby('season')[contributions.columns].mean()
seasonal_avg.index = [season_names[i] for i in seasonal_avg.index]
seasonal_avg.plot(kind='bar', ax=ax5, alpha=0.8, width=0.8)
ax5.set_title('Seasonal Average Contributions')
ax5.set_ylabel('Average Contribution (μg/m³)')
ax5.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
ax5.tick_params(axis='x', rotation=45)
else:
ax5.text(0.5, 0.5, 'Insufficient data\nfor seasonal analysis',
ha='center', va='center', transform=ax5.transAxes, fontsize=12)
ax5.set_title('Seasonal Analysis')
# 6. Q-value information
ax6 = axes[1, 2]
ax6.axis('off')
q_value = pmf.score(concentrations, uncertainties)
n_samples, n_species = concentrations.shape
n_factors = pmf.n_components
q_theoretical = (n_samples * n_species) - (n_factors * (n_samples + n_species))
info_text = f"""PMF Model Summary
Number of Factors: {n_factors}
Samples: {n_samples}
Species: {n_species}
Q-value: {q_value:.1f}
Q-theoretical: {q_theoretical:.0f}
Q/Q-theo: {q_value/q_theoretical:.2f}
Converged: {pmf.converged_}
Iterations: {pmf.n_iter_}
Status: {'Good' if q_value/q_theoretical < 2 else 'Check model'}
"""
ax6.text(0.1, 0.9, info_text, transform=ax6.transAxes, fontsize=12,
verticalalignment='top', fontfamily='monospace',
bbox=dict(boxstyle='round', facecolor='lightblue', alpha=0.8))
plt.suptitle('EPA PMF-Style Analysis Summary', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
# Example usage
create_epa_style_plots(pmf, concentrations, uncertainties, source_names)
Interactive Visualizations
def create_interactive_dashboard(pmf, concentrations):
"""Create interactive dashboard using plotly (if available)."""
try:
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import plotly.express as px
except ImportError:
print("Plotly not available. Install with: pip install plotly")
return
contributions = pmf.contributions_
profiles = pmf.profiles_
# Create subplots
fig = make_subplots(
rows=2, cols=2,
subplot_titles=('Factor Contributions', 'Factor Profiles',
'Correlation Matrix', 'Monthly Patterns'),
specs=[[{"secondary_y": False}, {"secondary_y": False}],
[{"secondary_y": False}, {"secondary_y": False}]]
)
# 1. Factor contributions time series
for col in contributions.columns:
fig.add_trace(
go.Scatter(x=contributions.index, y=contributions[col],
mode='lines', name=col,
line=dict(width=2)),
row=1, col=1
)
# 2. Factor profiles heatmap
fig.add_trace(
go.Heatmap(z=profiles.values,
x=profiles.columns,
y=profiles.index,
colorscale='Viridis',
showscale=True),
row=1, col=2
)
# 3. Correlation matrix
corr_matrix = contributions.corr()
fig.add_trace(
go.Heatmap(z=corr_matrix.values,
x=corr_matrix.columns,
y=corr_matrix.index,
colorscale='RdBu',
zmid=0,
showscale=True),
row=2, col=1
)
# 4. Monthly patterns (if applicable)
if len(contributions) > 30:
monthly_data = contributions.copy()
monthly_data['month'] = monthly_data.index.month
monthly_avg = monthly_data.groupby('month')[contributions.columns].mean()
for col in contributions.columns:
fig.add_trace(
go.Bar(x=monthly_avg.index, y=monthly_avg[col],
name=col, showlegend=False),
row=2, col=2
)
# Update layout
fig.update_layout(
title="Interactive PMF Analysis Dashboard",
height=800,
showlegend=True
)
# Show the interactive plot
fig.show()
# Example usage (requires plotly)
# create_interactive_dashboard(pmf, concentrations)
Publication-Ready Figures
def create_publication_figure(pmf, concentrations, uncertainties,
source_names=None, save_path=None):
"""Create publication-ready figure with proper formatting."""
# Set publication style
plt.style.use('seaborn-v0_8-whitegrid')
plt.rcParams.update({
'font.size': 12,
'axes.labelsize': 14,
'axes.titlesize': 16,
'xtick.labelsize': 12,
'ytick.labelsize': 12,
'legend.fontsize': 11,
'figure.titlesize': 18
})
contributions = pmf.contributions_
profiles = pmf.profiles_
fig = plt.figure(figsize=(16, 12))
# Create custom grid layout
gs = fig.add_gridspec(3, 4, hspace=0.3, wspace=0.3)
# 1. Factor contributions (spans 2 columns)
ax1 = fig.add_subplot(gs[0, :2])
for i, col in enumerate(contributions.columns):
color = plt.cm.Set2(i / len(contributions.columns))
factor_name = source_names[i] if source_names else col
ax1.plot(contributions.index, contributions[col],
label=factor_name, linewidth=2, color=color)
ax1.set_xlabel('Date')
ax1.set_ylabel('Contribution (μg m⁻³)')
ax1.set_title('(a) Factor Contributions Time Series')
ax1.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
ax1.grid(True, alpha=0.3)
# 2. Average contribution pie chart
ax2 = fig.add_subplot(gs[0, 2])
avg_contributions = contributions.mean()
colors = plt.cm.Set2(np.linspace(0, 1, len(avg_contributions)))
wedges, texts, autotexts = ax2.pie(
avg_contributions.values,
labels=[source_names[i] if source_names else f'F{i+1}'
for i in range(len(avg_contributions))],
autopct='%1.1f%%',
colors=colors,
startangle=90
)
ax2.set_title('(b) Average Contributions')
# 3. Q-value and model info
ax3 = fig.add_subplot(gs[0, 3])
ax3.axis('off')
q_value = pmf.score(concentrations, uncertainties)
n_samples, n_species = concentrations.shape
q_theoretical = (n_samples * n_species) - (pmf.n_components * (n_samples + n_species))
info_text = f"""Model Performance:
Q-value: {q_value:.1f}
Q/Q_theo: {q_value/q_theoretical:.2f}
R²: {np.corrcoef(concentrations.sum(axis=1), contributions.sum(axis=1))[0,1]**2:.3f}
Converged: {pmf.converged_}
Iterations: {pmf.n_iter_}
"""
ax3.text(0.1, 0.9, info_text, transform=ax3.transAxes, fontsize=11,
verticalalignment='top', fontfamily='monospace',
bbox=dict(boxstyle='round', facecolor='lightgray', alpha=0.8))
ax3.set_title('(c) Model Statistics')
# 4. Factor profiles (spans all columns)
n_factors = len(profiles)
factor_axes = []
for i in range(n_factors):
if i < 3: # First row of factor profiles
ax = fig.add_subplot(gs[1, i])
else: # Second row of factor profiles
ax = fig.add_subplot(gs[2, i-3])
factor_axes.append(ax)
factor_data = profiles.iloc[i]
bars = ax.bar(range(len(factor_data)), factor_data,
color=plt.cm.Set2(i / n_factors), alpha=0.8,
edgecolor='black', linewidth=0.5)
# Highlight top 3 species
top_3_indices = factor_data.nlargest(3).index
for j, species in enumerate(factor_data.index):
if species in top_3_indices:
bars[j].set_color('red')
bars[j].set_alpha(0.9)
factor_name = source_names[i] if source_names else f'Factor {i+1}'
ax.set_title(f'({chr(100+i)}) {factor_name}')
ax.set_ylabel('Relative Abundance')
# Format x-axis
ax.set_xticks(range(len(factor_data)))
ax.set_xticklabels(factor_data.index, rotation=45, ha='right')
ax.grid(True, alpha=0.3, axis='y')
# Main title
plt.suptitle('Positive Matrix Factorization Analysis Results',
fontsize=18, fontweight='bold', y=0.98)
# Save if path provided
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight',
facecolor='white', edgecolor='none')
print(f"Figure saved to: {save_path}")
plt.show()
# Example usage
create_publication_figure(pmf, concentrations, uncertainties,
source_names, save_path='pmf_results_figure.png')
Diagnostic Plots
def create_diagnostic_plots(pmf, concentrations, uncertainties):
"""Create comprehensive diagnostic plots for model validation."""
# Calculate metrics
reconstructed = pd.DataFrame(
pmf.contributions_.values @ pmf.profiles_.values,
index=concentrations.index,
columns=concentrations.columns
)
residuals = concentrations - reconstructed
scaled_residuals = residuals / uncertainties
fig, axes = plt.subplots(3, 3, figsize=(18, 15))
# 1. Convergence history (if available)
ax1 = axes[0, 0]
if hasattr(pmf, '_convergence_history'):
ax1.semilogy(pmf._convergence_history)
ax1.axhline(y=pmf.tol, color='red', linestyle='--', label='Tolerance')
ax1.set_xlabel('Iteration')
ax1.set_ylabel('Convergence Metric')
ax1.set_title('Convergence History')
ax1.legend()
ax1.grid(True, alpha=0.3)
else:
ax1.text(0.5, 0.5, 'Convergence history\nnot available',
ha='center', va='center', transform=ax1.transAxes)
ax1.set_title('Convergence History')
# 2. Scaled residuals histogram
ax2 = axes[0, 1]
ax2.hist(scaled_residuals.values.flatten(), bins=50, alpha=0.7, density=True)
x = np.linspace(-4, 4, 100)
ax2.plot(x, norm.pdf(x, 0, 1), 'r-', linewidth=2, label='Standard Normal')
ax2.set_xlabel('Scaled Residuals')
ax2.set_ylabel('Density')
ax2.set_title('Scaled Residuals Distribution')
ax2.legend()
ax2.grid(True, alpha=0.3)
# 3. Q-Q plot
ax3 = axes[0, 2]
from scipy import stats
stats.probplot(scaled_residuals.values.flatten(), dist="norm", plot=ax3)
ax3.set_title('Q-Q Plot vs Normal')
ax3.grid(True, alpha=0.3)
# 4. Species reconstruction quality
ax4 = axes[1, 0]
r_squared_values = []
for col in concentrations.columns:
obs = concentrations[col]
pred = reconstructed[col]
ss_res = np.sum((obs - pred) ** 2)
ss_tot = np.sum((obs - obs.mean()) ** 2)
r2 = 1 - (ss_res / ss_tot) if ss_tot > 0 else 0
r_squared_values.append(r2)
species_r2 = pd.Series(r_squared_values, index=concentrations.columns)
species_r2.plot(kind='bar', ax=ax4, color='skyblue', alpha=0.8)
ax4.set_title('Species Reconstruction Quality (R²)')
ax4.set_ylabel('R²')
ax4.tick_params(axis='x', rotation=45)
ax4.grid(True, alpha=0.3, axis='y')
# 5. Residuals vs predicted
ax5 = axes[1, 1]
ax5.scatter(reconstructed.values.flatten(), residuals.values.flatten(),
alpha=0.5, s=10)
ax5.axhline(y=0, color='red', linestyle='--', linewidth=2)
ax5.set_xlabel('Predicted Concentration')
ax5.set_ylabel('Residual')
ax5.set_title('Residuals vs Predicted')
ax5.grid(True, alpha=0.3)
# 6. Factor correlation matrix
ax6 = axes[1, 2]
corr_matrix = pmf.contributions_.corr()
im = ax6.imshow(corr_matrix, cmap='RdBu_r', vmin=-1, vmax=1)
ax6.set_xticks(range(len(corr_matrix)))
ax6.set_yticks(range(len(corr_matrix)))
ax6.set_xticklabels(corr_matrix.columns)
ax6.set_yticklabels(corr_matrix.index)
ax6.set_title('Factor Correlation Matrix')
# Add text annotations
for i in range(len(corr_matrix)):
for j in range(len(corr_matrix)):
ax6.text(j, i, f'{corr_matrix.iloc[i, j]:.2f}',
ha='center', va='center', color='black', fontsize=10)
plt.colorbar(im, ax=ax6)
# 7. Bootstrap stability (placeholder)
ax7 = axes[2, 0]
ax7.text(0.5, 0.5, 'Bootstrap stability\nanalysis would go here\n\n' +
'Run bootstrap validation\nto populate this plot',
ha='center', va='center', transform=ax7.transAxes)
ax7.set_title('Bootstrap Stability')
# 8. Temporal patterns
ax8 = axes[2, 1]
if len(pmf.contributions_) > 30:
monthly_data = pmf.contributions_.copy()
monthly_data['month'] = monthly_data.index.month
monthly_cv = monthly_data.groupby('month')[pmf.contributions_.columns].std() / \
monthly_data.groupby('month')[pmf.contributions_.columns].mean()
monthly_cv.mean(axis=1).plot(kind='bar', ax=ax8, color='orange', alpha=0.8)
ax8.set_title('Monthly Variability (CV)')
ax8.set_xlabel('Month')
ax8.set_ylabel('Coefficient of Variation')
ax8.tick_params(axis='x', rotation=0)
else:
ax8.text(0.5, 0.5, 'Insufficient data\nfor temporal analysis',
ha='center', va='center', transform=ax8.transAxes)
ax8.set_title('Temporal Patterns')
# 9. Model summary statistics
ax9 = axes[2, 2]
ax9.axis('off')
q_value = pmf.score(concentrations, uncertainties)
n_samples, n_species = concentrations.shape
q_theoretical = (n_samples * n_species) - (pmf.n_components * (n_samples + n_species))
# Calculate additional metrics
total_r2 = np.corrcoef(concentrations.sum(axis=1), pmf.contributions_.sum(axis=1))[0,1]**2
mean_species_r2 = np.mean(r_squared_values)
outlier_pct = (np.abs(scaled_residuals) > 3).sum().sum() / scaled_residuals.size * 100
summary_text = f"""Model Diagnostics Summary:
Q-value: {q_value:.1f}
Q/Q_theoretical: {q_value/q_theoretical:.2f}
Total Mass R²: {total_r2:.3f}
Mean Species R²: {mean_species_r2:.3f}
Outliers (|z| > 3): {outlier_pct:.1f}%
Convergence: {pmf.converged_}
Iterations: {pmf.n_iter_}
Status: {'✓ Good' if q_value/q_theoretical < 2 and total_r2 > 0.8 else '⚠ Check'}
"""
ax9.text(0.1, 0.9, summary_text, transform=ax9.transAxes, fontsize=11,
verticalalignment='top', fontfamily='monospace',
bbox=dict(boxstyle='round', facecolor='lightblue', alpha=0.8))
ax9.set_title('Diagnostic Summary')
plt.suptitle('PMF Model Diagnostic Plots', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
from scipy.stats import norm
# Example usage
create_diagnostic_plots(pmf, concentrations, uncertainties)
Customization Options
Color Schemes and Styling
# Custom color palettes
def get_custom_colors(n_colors, palette='environmental'):
"""Get custom color palettes for PMF plots."""
palettes = {
'environmental': ['#2E8B57', '#4682B4', '#CD853F', '#8B4513', '#9370DB', '#20B2AA'],
'pollution': ['#FF4500', '#DC143C', '#8B0000', '#FF6347', '#FF1493', '#B22222'],
'sources': ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b'],
'scientific': ['#0072BD', '#D95319', '#EDB120', '#7E2F8E', '#77AC30', '#4DBEEE']
}
if palette in palettes:
colors = palettes[palette]
# Extend if needed
while len(colors) < n_colors:
colors.extend(colors)
return colors[:n_colors]
else:
return plt.cm.Set3(np.linspace(0, 1, n_colors))
# Apply custom styling
plt.rcParams.update({
'font.family': 'DejaVu Sans',
'axes.grid': True,
'grid.alpha': 0.3,
'axes.spines.top': False,
'axes.spines.right': False,
'figure.facecolor': 'white'
})
Export Options
def save_all_plots(pmf, concentrations, uncertainties, output_dir='plots'):
"""Save all PMF plots in multiple formats."""
import os
os.makedirs(output_dir, exist_ok=True)
# Set high DPI for publication quality
plt.rcParams['savefig.dpi'] = 300
# Factor profiles
plot_factor_profiles(pmf)
plt.savefig(f'{output_dir}/factor_profiles.png', bbox_inches='tight')
plt.savefig(f'{output_dir}/factor_profiles.pdf', bbox_inches='tight')
plt.close()
# Factor contributions
plot_factor_contributions(pmf)
plt.savefig(f'{output_dir}/factor_contributions.png', bbox_inches='tight')
plt.savefig(f'{output_dir}/factor_contributions.pdf', bbox_inches='tight')
plt.close()
# Diagnostic plots
create_diagnostic_plots(pmf, concentrations, uncertainties)
plt.savefig(f'{output_dir}/diagnostics.png', bbox_inches='tight')
plt.close()
print(f"All plots saved to {output_dir}/")
# Example usage
# save_all_plots(pmf, concentrations, uncertainties)
Best Practices for Visualization
1. Choose Appropriate Plot Types
- Time series: For temporal patterns
- Bar charts: For comparing factor profiles
- Heatmaps: For correlation matrices
- Scatter plots: For model validation
- Pie charts: For contribution percentages
2. Use Clear Labels and Titles
- Include units in axis labels
- Use descriptive factor names
- Add informative titles
- Include legends when needed
3. Color Considerations
- Use colorblind-friendly palettes
- Maintain consistency across plots
- Use red for highlighting important features
- Consider printing in grayscale
4. Publication Guidelines
- Use vector formats (PDF, SVG) for scalability
- Ensure text is readable at target size
- Follow journal-specific formatting requirements
- Include all necessary information in captions
Next Steps
- Apply these visualizations to your own data
- Explore the Examples for real-world applications
- Learn about Advanced Analysis techniques
- Contribute new visualization features via GitHub