xbe.at

Data visualisation

Highlighting Key Insights in Data Visualizations

10 min read, 14 sections

Contents
  1. Introduction to Text Annotations in Matplotlib
  2. Advanced Annotation Styling
  3. Multiple Annotations and Positioning
  4. Programmatic Annotation Placement
  5. Custom Annotation Styles and Shapes
  6. Real-World Example - Stock Market Analysis
  7. Annotation with Mathematical Expressions
  8. Interactive Annotations
  9. Annotating Time Series Data
  10. Annotation with Custom Drawing
  11. Annotation in Subplots
  12. Annotation with Data Statistics
  13. Real-World Example - Scientific Data Visualization
  14. Additional Resources

Introduction to Text Annotations in Matplotlib

Text annotations are powerful tools for enhancing data visualizations by providing contextual information directly on the plot. They help guide viewers' attention to significant data points, trends, or patterns while maintaining the visual flow of the presentation.

import matplotlib.pyplot as plt
import numpy as np

# Generate sample data
x = np.linspace(0, 10, 100)
y = np.sin(x)

# Create the plot
plt.figure(figsize=(10, 6))
plt.plot(x, y, 'b-', label='Sine Wave')

# Add text annotation with arrow
plt.annotate('Local Maximum', 
            xy=(4.71, 1),             # Point to annotate
            xytext=(5.5, 0.5),        # Text position
            arrowprops=dict(facecolor='black', shrink=0.05),
            fontsize=12)

plt.title('Simple Sine Wave with Annotation')
plt.grid(True)
plt.legend()
plt.show()

Advanced Annotation Styling

Matplotlib offers extensive customization options for annotations, including different arrow styles, text boxes, and coordinate systems. Understanding these parameters allows for creating more sophisticated and informative visualizations.

import matplotlib.pyplot as plt
import numpy as np

# Create data
x = np.linspace(0, 5, 50)
y = x**2

# Create plot
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(x, y, 'r-')

# Add styled annotation
ax.annotate('Quadratic Growth', 
           xy=(3, 9),
           xytext=(1, 15),
           bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5),
           arrowprops=dict(
               arrowstyle='->',
               connectionstyle='arc3,rad=.2',
               color='black'
           ))

plt.title('Quadratic Function with Styled Annotation')
plt.grid(True)
plt.show()

Multiple Annotations and Positioning

When working with multiple annotations, careful positioning and styling become crucial for maintaining clarity. This example demonstrates how to effectively place multiple annotations without overlapping while highlighting different aspects of the data.

import matplotlib.pyplot as plt
import numpy as np

# Generate data
x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x) * np.exp(-x/3)

# Create plot
fig, ax = plt.subplots(figsize=(12, 6))
line = ax.plot(x, y, 'b-', label='Damped Sine Wave')

# Add multiple annotations
annotations = [
    {'xy': (0, 0), 'text': 'Starting Point', 'xytext': (0.5, 0.5)},
    {'xy': (np.pi/2, np.exp(-np.pi/6)), 'text': 'First Peak', 'xytext': (2, 0.8)},
    {'xy': (2*np.pi, y[-1]), 'text': 'Dampening Effect', 'xytext': (5, -0.2)}
]

for ann in annotations:
    ax.annotate(ann['text'],
                xy=ann['xy'],
                xytext=ann['xytext'],
                arrowprops=dict(facecolor='black', shrink=0.05),
                bbox=dict(boxstyle='round', fc='white', ec='gray'))

plt.title('Damped Sine Wave with Multiple Annotations')
plt.grid(True)
plt.legend()
plt.show()

Programmatic Annotation Placement

Automated annotation placement is essential when dealing with dynamic data or multiple plots. This example shows how to programmatically determine annotation positions based on data characteristics.

import matplotlib.pyplot as plt
import numpy as np
from scipy.signal import find_peaks

# Generate sample data
x = np.linspace(0, 10, 1000)
y = np.sin(2*x) * np.exp(-x/5)

# Find peaks automatically
peaks, _ = find_peaks(y, height=0.1)

# Create plot
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(x, y, 'b-')

# Add annotations programmatically
for i, peak in enumerate(peaks):
    ax.annotate(f'Peak {i+1}\ny={y[peak]:.2f}',
                xy=(x[peak], y[peak]),
                xytext=(x[peak]+0.5, y[peak]+0.1),
                arrowprops=dict(facecolor='black', shrink=0.05),
                bbox=dict(boxstyle='round', fc='white', alpha=0.8))

plt.title('Automated Peak Detection and Annotation')
plt.grid(True)
plt.show()

Custom Annotation Styles and Shapes

Creating custom annotation styles enhances the visual appeal and effectiveness of your visualizations. This example demonstrates how to implement custom shapes, colors, and text styles for annotations.

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import FancyBboxPatch

# Generate data
x = np.linspace(-5, 5, 100)
y = x**2

# Create plot
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(x, y, 'b-')

# Custom annotation with fancy box
ax.annotate('Minimum Point',
           xy=(0, 0),
           xytext=(2, 10),
           bbox=dict(
               boxstyle='round,pad=0.5',
               fc='lightgreen',
               ec='green',
               alpha=0.8
           ),
           arrowprops=dict(
               arrowstyle='fancy',
               connectionstyle='arc3,rad=.2',
               color='darkgreen'
           ),
           fontsize=12,
           color='darkgreen',
           fontweight='bold')

plt.title('Parabola with Custom Styled Annotation')
plt.grid(True)
plt.show()

Real-World Example - Stock Market Analysis

Financial market analysis often requires highlighting specific events or patterns. This example demonstrates how to annotate key market events on a stock price chart using real-world-like data analysis techniques.

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

# Generate sample stock data
np.random.seed(42)
dates = pd.date_range(start='2023-01-01', end='2023-12-31', freq='D')
price = 100 * (1 + np.random.randn(len(dates)).cumsum() * 0.02)

# Create DataFrame
df = pd.DataFrame({'Date': dates, 'Price': price})

# Find significant events
max_price = df.loc[df['Price'].idxmax()]
min_price = df.loc[df['Price'].idxmin()]

# Create plot
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(df['Date'], df['Price'], 'b-', label='Stock Price')

# Add annotations for significant events
ax.annotate(f'Yearly High\n${max_price["Price"]:.2f}',
            xy=(max_price['Date'], max_price['Price']),
            xytext=(10, 10),
            textcoords='offset points',
            bbox=dict(boxstyle='round,pad=0.5', fc='green', alpha=0.5),
            arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.2'))

ax.annotate(f'Yearly Low\n${min_price["Price"]:.2f}',
            xy=(min_price['Date'], min_price['Price']),
            xytext=(-10, -10),
            textcoords='offset points',
            bbox=dict(boxstyle='round,pad=0.5', fc='red', alpha=0.5),
            arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=-0.2'))

plt.title('Stock Price Movement with Key Events')
plt.xlabel('Date')
plt.ylabel('Price ($)')
plt.grid(True)
plt.legend()
plt.xticks(rotation=45)
plt.tight_layout()

Annotation with Mathematical Expressions

Incorporating mathematical expressions in annotations adds technical precision to visualizations. This example shows how to include LaTeX-formatted equations in annotations.

import matplotlib.pyplot as plt
import numpy as np

# Generate data for normal distribution
x = np.linspace(-4, 4, 100)
y = 1/(np.sqrt(2*np.pi)) * np.exp(-x**2/2)

# Create plot
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(x, y, 'b-', label='Normal Distribution')

# Add annotation with LaTeX equation
equation = r'$$f(x) = \frac{1}{\sqrt{2\pi}} e^{-\frac{x^2}{2}}$$'
ax.annotate(equation,
            xy=(0, 0.4),
            xytext=(1, 0.3),
            bbox=dict(boxstyle='round', fc='white', ec='gray'),
            arrowprops=dict(arrowstyle='->'),
            fontsize=12)

plt.title('Normal Distribution with Mathematical Annotation')
plt.grid(True)
plt.legend()
plt.show()

Interactive Annotations

Creating interactive annotations that respond to data exploration enhances the user experience. This example implements hoverable annotations using event handling.

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Circle

class InteractiveAnnotation:
    def __init__(self, fig, ax, x, y):
        self.fig = fig
        self.ax = ax
        self.x = x
        self.y = y
        
        # Create scatter plot
        self.points = ax.scatter(x, y, c='blue', picker=True)
        
        # Initialize annotation
        self.annot = ax.annotate("",
                                xy=(0,0),
                                xytext=(10,10),
                                textcoords="offset points",
                                bbox=dict(boxstyle='round', fc='white', alpha=0.8),
                                arrowprops=dict(arrowstyle='->'))
        self.annot.set_visible(False)
        
        # Connect events
        self.fig.canvas.mpl_connect('pick_event', self.on_pick)
        
    def on_pick(self, event):
        ind = event.ind[0]
        self.annot.xy = (self.x[ind], self.y[ind])
        self.annot.set_text(f'Point: ({self.x[ind]:.2f}, {self.y[ind]:.2f})')
        self.annot.set_visible(True)
        self.fig.canvas.draw_idle()

# Generate sample data
x = np.random.rand(20)
y = np.random.rand(20)

# Create plot
fig, ax = plt.subplots(figsize=(10, 6))
interactive_plot = InteractiveAnnotation(fig, ax, x, y)

plt.title('Interactive Plot with Hoverable Annotations')
plt.grid(True)
plt.show()

Annotating Time Series Data

Time series visualization often requires highlighting specific temporal events or patterns. This example demonstrates effective annotation techniques for time-based data with multiple significant points.

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

# Generate sample time series data
dates = pd.date_range(start='2023-01-01', periods=100, freq='D')
values = np.cumsum(np.random.randn(100)) + 10

# Create anomalies for annotation
anomalies = {
    '2023-02-15': 'Sudden Spike',
    '2023-03-01': 'Market Event',
    '2023-03-20': 'Pattern Change'
}

# Create plot
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(dates, values, 'b-', label='Time Series')

# Add annotations for each anomaly
for date, event in anomalies.items():
    idx = dates.get_loc(date)
    ax.annotate(event,
                xy=(dates[idx], values[idx]),
                xytext=(10, 10),
                textcoords='offset points',
                bbox=dict(
                    boxstyle='round,pad=0.5',
                    fc='yellow',
                    alpha=0.5
                ),
                arrowprops=dict(
                    arrowstyle='->',
                    connectionstyle='arc3,rad=0.2'
                ))

plt.title('Time Series Analysis with Event Annotations')
plt.xlabel('Date')
plt.ylabel('Value')
plt.xticks(rotation=45)
plt.grid(True)
plt.legend()
plt.tight_layout()

Annotation with Custom Drawing

Advanced visualization sometimes requires custom shapes or drawings to highlight specific features. This example shows how to create custom annotation shapes using path effects and patches.

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Ellipse
from matplotlib.patheffects import withStroke

# Generate data
x = np.linspace(0, 10, 100)
y = np.sin(x) + np.random.normal(0, 0.1, 100)

# Create plot
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(x, y, 'b-', label='Data')

# Add custom shape annotation
ellipse = Ellipse((5, 0), 2, 1, angle=0, 
                  fc='none', ec='red', lw=2)
ax.add_patch(ellipse)

# Add text with path effects
ax.annotate('Region of Interest',
           xy=(5, 0),
           xytext=(6, 1),
           path_effects=[withStroke(linewidth=3, foreground='white')],
           bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5),
           arrowprops=dict(arrowstyle='fancy',
                          fc='0.6',
                          ec='none',
                          connectionstyle='angle3,angleA=0,angleB=-90'))

plt.title('Custom Shape Annotations')
plt.grid(True)
plt.legend()
plt.show()

Annotation in Subplots

Managing annotations across multiple subplots requires careful coordination of space and positioning. This example demonstrates effective annotation strategies for multi-plot layouts.

import matplotlib.pyplot as plt
import numpy as np

# Create figure with subplots
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))

# Generate data
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)

# Plot data
ax1.plot(x, y1, 'b-')
ax2.plot(x, y2, 'r-')

# Add annotations to first subplot
ax1.annotate('Peak',
            xy=(np.pi/2, 1),
            xytext=(np.pi/2 - 1, 0.5),
            arrowprops=dict(facecolor='black', shrink=0.05))

# Add annotations to second subplot
ax2.annotate('Valley',
            xy=(np.pi, -1),
            xytext=(np.pi - 1, -0.5),
            arrowprops=dict(facecolor='black', shrink=0.05))

# Customize subplots
ax1.set_title('Sine Wave')
ax2.set_title('Cosine Wave')
ax1.grid(True)
ax2.grid(True)

plt.tight_layout()
plt.show()

Annotation with Data Statistics

Incorporating statistical information in annotations provides valuable context. This example demonstrates how to automatically calculate and display statistical measures within annotations.

import matplotlib.pyplot as plt
import numpy as np
from scipy import stats

# Generate sample data
np.random.seed(42)
data = np.random.normal(100, 15, 1000)

# Calculate statistics
mean = np.mean(data)
std = np.std(data)
median = np.median(data)

# Create histogram
fig, ax = plt.subplots(figsize=(12, 6))
counts, bins, _ = ax.hist(data, bins=30, alpha=0.7, color='skyblue')

# Add statistical annotations
stats_text = (
    f'Mean: {mean:.2f}\n'
    f'Std Dev: {std:.2f}\n'
    f'Median: {median:.2f}'
)

ax.annotate(stats_text,
            xy=(mean, max(counts)),
            xytext=(mean + 20, max(counts)),
            bbox=dict(
                boxstyle='round,pad=0.5',
                fc='white',
                ec='gray',
                alpha=0.9
            ),
            arrowprops=dict(
                arrowstyle='->',
                connectionstyle='arc3,rad=0.2'
            ))

# Add vertical lines for mean and median
ax.axvline(mean, color='red', linestyle='--', label='Mean')
ax.axvline(median, color='green', linestyle=':', label='Median')

plt.title('Distribution with Statistical Annotations')
plt.xlabel('Value')
plt.ylabel('Frequency')
plt.legend()
plt.grid(True)
plt.show()

Real-World Example - Scientific Data Visualization

This example demonstrates how to annotate scientific data with confidence intervals and significant points, commonly used in research publications.

import matplotlib.pyplot as plt
import numpy as np
from scipy import stats

# Generate experimental data
x = np.linspace(0, 10, 50)
y = 2 * x + np.random.normal(0, 1, 50)

# Calculate confidence interval
slope, intercept, r_value, p_value, std_err = stats.linregress(x, y)
y_pred = slope * x + intercept
confidence = 0.95
degrees_of_freedom = len(x) - 2
t_value = stats.t.ppf((1 + confidence) / 2, degrees_of_freedom)
ci = t_value * std_err * np.sqrt(1/len(x) + (x - np.mean(x))**2 / np.sum((x - np.mean(x))**2))

# Create plot
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(x, y, 'o', label='Data Points')
ax.plot(x, y_pred, 'r-', label='Linear Fit')
ax.fill_between(x, y_pred - ci, y_pred + ci, color='gray', alpha=0.2, label='95% CI')

# Add statistical annotation
stats_text = (
    f'R² = {r_value**2:.3f}\n'
    f'p-value = {p_value:.3e}\n'
    f'Slope = {slope:.3f} ± {std_err:.3f}'
)

ax.annotate(stats_text,
            xy=(1, np.max(y)),
            bbox=dict(
                boxstyle='round,pad=0.5',
                fc='white',
                ec='gray',
                alpha=0.9
            ))

plt.title('Scientific Data Analysis with Statistical Annotations')
plt.xlabel('Independent Variable')
plt.ylabel('Dependent Variable')
plt.legend()
plt.grid(True)
plt.show()

Additional Resources

More in Data visualisation

  1. K-Means Clustering Visualized Through Travel Themes7 min

    K-means clustering is an unsupervised machine learning algorithm that partitions n observations into k clusters, where each observation belongs to the...

  2. Euclidean Distance Pitfalls in Python Visualizations11 min

    Euclidean distance is a common measure of similarity in data science, but it can be misleading in certain scenarios. This presentation explores why and how...

  3. Key Matplotlib Functions for Data Scientists7 min

    Matplotlib is a powerful data visualization library for Python. It provides a wide range of functions to create various types of plots and charts. This...

  4. Essential Python Libraries for Data Visualization5 min

    Data visualization is a powerful tool for transforming complex data into clear, actionable insights. Python offers several libraries that excel in creating...

  5. Machine Learning Stacking A Visual Guide8 min

    Stacking is an advanced ensemble learning technique that combines predictions from multiple models to create a more powerful and accurate predictor. This...

  6. Essential Data Visualization Plots for Data Scientists Using Python8 min

    This presentation covers 11 key plots that data scientists frequently use in their work. We'll explore each plot's purpose, interpretation, and provide...