Arrhenius Plot for Reaction Kinetics — Seaborn

An Arrhenius plot displays ln(k) versus 1/T to determine the activation energy of a chemical reaction from experimental rate constant data. The Arrhenius equation predicts a linear relationship on this transformed scale, where the slope equals -Ea/R (activation energy divided by the gas constant). This visualization is fundamental in physical chemistry and chemical engineering for characterizing reaction kinetics and comparing catalytic performance.

Arrhenius Plot for Reaction Kinetics rendered with Seaborn

Python source (Seaborn)

""" anyplot.ai
line-arrhenius: Arrhenius Plot for Reaction Kinetics
Library: seaborn 0.13.2 | Python 3.13.14
Quality: 92/100 | Updated: 2026-06-24
"""

import os
import sys


# Remove the script's own directory from sys.path so sibling files (e.g. matplotlib.py)
# do not shadow installed packages when running from the implementations directory.
_here = os.path.dirname(os.path.abspath(__file__))
sys.path = [p for p in sys.path if os.path.abspath(p) != _here]
del _here

import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np
import seaborn as sns
from scipy import stats


# Theme-adaptive chrome tokens (Imprint palette)
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"

# Imprint palette — first series always #009E73 (brand green)
BRAND = "#009E73"

# Data — enzyme-catalyzed hydrolysis (alkaline phosphatase) across physiological temperatures
# Domain: enzyme kinetics, Ea ~58 kJ/mol (lower than thermal decomposition, typical for enzymes)
temperature_K = np.array([278, 283, 288, 293, 298, 303, 308, 313, 320, 330])
R = 8.314  # gas constant (J/mol·K)
Ea_true = 58000  # activation energy (J/mol)
A = 1.2e9  # pre-exponential factor (s⁻¹)

np.random.seed(7)
noise = np.random.normal(0, 0.28, len(temperature_K))
rate_constant_k = A * np.exp(-Ea_true / (R * temperature_K)) * np.exp(noise)
rate_constant_k[7] *= 2.0  # one elevated outlier at 313 K — demonstrates regression robustness

inv_T = 1.0 / temperature_K
ln_k = np.log(rate_constant_k)

# Linear regression for annotation
slope, intercept, r_value, p_value, std_err = stats.linregress(inv_T, ln_k)
r_squared = r_value**2
Ea_extracted = -slope * R

# Seaborn theme setup with full theme-adaptive chrome
sns.set_theme(
    style="ticks",
    rc={
        "figure.facecolor": PAGE_BG,
        "axes.facecolor": PAGE_BG,
        "axes.edgecolor": INK_SOFT,
        "axes.labelcolor": INK,
        "text.color": INK,
        "xtick.color": INK_SOFT,
        "ytick.color": INK_SOFT,
        "grid.color": INK,
        "grid.alpha": 0.15,
        "legend.facecolor": ELEVATED_BG,
        "legend.edgecolor": INK_SOFT,
        "font.family": "sans-serif",
        "axes.spines.top": False,
        "axes.spines.right": False,
    },
)

# Canvas: landscape 3200×1800 — figsize=(8, 4.5) × dpi=400
fig, ax = plt.subplots(figsize=(8, 4.5), dpi=400)
fig.patch.set_facecolor(PAGE_BG)

# Main plot — sns.regplot with 95% CI band is idiomatic seaborn for regression + scatter
sns.regplot(
    x=inv_T,
    y=ln_k,
    ci=95,
    scatter_kws={"s": 170, "color": BRAND, "edgecolor": "white", "linewidths": 1.5, "zorder": 5},
    line_kws={"color": BRAND, "linewidth": 2.5, "alpha": 0.9},
    ax=ax,
)

# Fix CI band visibility in dark theme — regplot draws the band as a PolyCollection
# with low default alpha; raise it in dark mode so it reads against #1A1A17
for coll in ax.collections:
    coll.set_facecolor(BRAND)
    coll.set_alpha(0.38 if THEME == "dark" else 0.18)

# Outlier annotation — raised to 8pt (above mobile-readable minimum)
ax.annotate(
    "outlier",
    xy=(inv_T[7], ln_k[7]),
    xytext=(6, 10),
    textcoords="offset points",
    fontsize=8,
    color=INK_MUTED,
    arrowprops={"arrowstyle": "-", "color": INK_MUTED, "lw": 0.8},
)

# Annotation box with extracted kinetic parameters
annotation_text = f"$R^2$ = {r_squared:.4f}\nSlope = {slope:.0f} K\n$E_a$ = {Ea_extracted / 1000:.1f} kJ/mol"
ax.text(
    0.03,
    0.38,
    annotation_text,
    transform=ax.transAxes,
    fontsize=8,
    verticalalignment="top",
    horizontalalignment="left",
    color=INK,
    bbox={"boxstyle": "round,pad=0.6", "facecolor": ELEVATED_BG, "edgecolor": BRAND, "alpha": 0.95, "linewidth": 1.5},
)

# Secondary x-axis — temperature in Kelvin for chemical context
ax_top = ax.twiny()
ax_top.set_xlim(ax.get_xlim())
temp_ticks_K = np.array([278, 293, 303, 313, 330])
ax_top.set_xticks(1.0 / temp_ticks_K)
ax_top.set_xticklabels([f"{t} K" for t in temp_ticks_K], fontsize=8)
ax_top.set_xlabel("Temperature (K)", fontsize=9, labelpad=8, color=INK)
# Tick-less secondary axis: labels only, no tick marks — cleaner composition
ax_top.tick_params(axis="x", labelsize=8, colors=INK_SOFT, length=0)
ax_top.spines["top"].set_color(INK_SOFT)
ax_top.spines["right"].set_visible(False)

# Bottom axis styling
ax.set_xlabel("1/T (K⁻¹)", fontsize=10, color=INK)
ax.set_ylabel("ln(k)", fontsize=10, color=INK)
ax.set_title("line-arrhenius · python · seaborn · anyplot.ai", fontsize=12, fontweight="medium", color=INK)
ax.tick_params(axis="both", labelsize=8, colors=INK_SOFT)
ax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)
ax.xaxis.grid(True, alpha=0.08, linewidth=0.6, color=INK)
ax.xaxis.set_major_formatter(ticker.FormatStrFormatter("%.4f"))

# Explicit spine cleanup (belt-and-suspenders alongside rc — seaborn despine idiom)
sns.despine(ax=ax, top=True, right=True)

fig.subplots_adjust(top=0.78, bottom=0.15, left=0.10, right=0.97)
_out = os.path.join(os.path.dirname(os.path.abspath(__file__)), f"plot-{THEME}.png")
plt.savefig(_out, dpi=400, facecolor=PAGE_BG)
plt.close(fig)

Part of Arrhenius Plot for Reaction Kinetics on anyplot.ai.

Other implementations