pH of NaOH solutions based on molarity

When working with chemical concentrations, the logarithmic nature of the pH scale often makes data visualization tricky. If you plot pH against Molarity on a standard linear scale, you get a “hockey stick” curve that masks the proportional relationship of the ions.

In this post, we’ll use Python and matplotlib to visualize the pH of Sodium Hydroxide (NaOH) from 0 up to a highly concentrated 3 Molar, comparing Linear and Logarithmic scales to see which provides better insight.

pH NaOH molarity.svg

The Chemistry Logic

Since NaOH is a strong base, it dissociates completely. To calculate the pH accurately across a wide range—especially as we exceed 1.0M—we calculate the pOH first:

$$pOH = -\log_{10}[OH^-]$$

$$pH = 14 - pOH$$

Interestingly, at 3M NaOH, the pOH is negative ($\approx -0.48$), which means the pH actually rises to 14.48, breaking the common “0 to 14” textbook rule.

The Python Implementation

We use numpy to generate our concentration ranges and matplotlib.pyplot with the ggplot style for a clean, professional look. Note that we generate two separate plots to highlight the different perspectives.

example.py
#!/usr/bin/env python3
# SPDX-License-Identifier: CC0-1.0
import numpy as np
import matplotlib.pyplot as plt

# Set the style to ggplot
plt.style.use('ggplot')

def calculate_ph(molarity):
    """Calculates pH for a given NaOH molarity including water auto-ionization."""
    kw = 1e-14
    # Quadratic solution for [OH-] to handle low concentrations accurately
    oh_conc = (molarity + np.sqrt(molarity**2 + 4 * kw)) / 2
    poh = -np.log10(oh_conc)
    return 14 - poh

# Data for Linear Plot (0 to 3M)
x_linear = np.linspace(0, 3, 1000)
y_linear = calculate_ph(x_linear)

# Data for Logarithmic Plot (0.01 to 3M)
# Using logspace ensures smooth plotting on a log scale
x_log = np.logspace(np.log10(0.01), np.log10(3), 1000)
y_log = calculate_ph(x_log)

# Create Linear Scale Plot
plt.figure(figsize=(10, 6))
plt.plot(x_linear, y_linear, color='#E24A33', linewidth=2.5, label='pH Value')
plt.title('pH of NaOH Solution (Linear Scale: 0M to 3M)', fontsize=14, fontweight='bold')
plt.xlabel('Molarity of NaOH (mol/L)', fontsize=12)
plt.ylabel('pH', fontsize=12)
plt.ylim(7, 15)
plt.grid(True, linestyle='--', alpha=0.7)
plt.legend()
plt.tight_layout()
plt.savefig('ph_linear_scale.png', dpi=300)
plt.close()

# Create Logarithmic Scale Plot
plt.figure(figsize=(10, 6))
plt.plot(x_log, y_log, color='#348ABD', linewidth=2.5, label='pH Value')
plt.xscale('log')
plt.title('pH of NaOH Solution (Log Scale: 0.01M to 3M)', fontsize=14, fontweight='bold')
plt.xlabel('Molarity of NaOH (Logarithmic Scale mol/L)', fontsize=12)
plt.ylabel('pH', fontsize=12)
plt.ylim(11.5, 15) # Focused range for the 0.01M-3M window
plt.grid(True, which="both", linestyle='--', alpha=0.7)
plt.legend()
plt.tight_layout()
plt.savefig('ph_log_scale.png', dpi=300)
plt.close()

# For the sake of the system requirement to provide a single "main" file name in the tag,
# I will save a combined version as well.
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 12))

# Linear Plot
ax1.plot(x_linear, y_linear, color='#E24A33', linewidth=2.5)
ax1.set_title('Linear Scale (0M to 3M)', fontsize=14, fontweight='bold')
ax1.set_ylabel('pH')
ax1.set_ylim(7, 15)

# Log Plot
ax2.plot(x_log, y_log, color='#348ABD', linewidth=2.5)
ax2.set_xscale('log')
ax2.set_title('Logarithmic Scale (0.01M to 3M)', fontsize=14, fontweight='bold')
ax2.set_xlabel('Molarity of NaOH (mol/L)')
ax2.set_ylabel('pH')
ax2.set_ylim(11.5, 15)

plt.tight_layout()
plt.savefig('pH NaOH molarity.svg', dpi=300)

Check out similar posts by category: Chemistry