Logarithmic Scales¶
Logarithmic scales compress large ranges of data, making patterns visible across multiple orders of magnitude.
Setting Log Scale¶
Using set_xscale / set_yscale¶
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(1, 100, 100)
y = x ** 2
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_yscale('log') # Log scale on y-axis
ax.set_xlabel('x')
ax.set_ylabel('y (log scale)')
plt.show()
Log-Log Plot¶
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_xscale('log')
ax.set_yscale('log')
ax.set_title('Log-Log Plot')
plt.show()
Semi-Log Plots¶
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
# Semilog-y: log y-axis, linear x-axis
ax1.semilogy(x, y)
ax1.set_title('semilogy')
# Semilog-x: log x-axis, linear y-axis
ax2.semilogx(x, y)
ax2.set_title('semilogx')
plt.tight_layout()
plt.show()
Using loglog¶
fig, ax = plt.subplots()
ax.loglog(x, y) # Both axes logarithmic
ax.set_title('loglog')
plt.show()
Comparison: Linear vs Log Scale¶
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0.1, 100, 200)
y = np.exp(x / 10)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
# Linear scale
ax1.plot(x, y)
ax1.set_title('Linear Scale')
ax1.set_xlabel('x')
ax1.set_ylabel('exp(x/10)')
# Log scale
ax2.plot(x, y)
ax2.set_yscale('log')
ax2.set_title('Logarithmic Y Scale')
ax2.set_xlabel('x')
ax2.set_ylabel('exp(x/10) - log scale')
plt.tight_layout()
plt.show()
Log Scale Bases¶
Default Base 10¶
ax.set_yscale('log') # Base 10
Base 2 (Binary)¶
ax.set_yscale('log', base=2)
Natural Log (Base e)¶
ax.set_yscale('log', base=np.e)
Custom Base¶
ax.set_yscale('log', base=5)
Handling Negative and Zero Values¶
Log scales cannot display zero or negative values directly.
Symmetric Log (symlog)¶
For data that spans positive and negative values:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-100, 100, 500)
y = x ** 3
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
# Linear (for comparison)
ax1.plot(x, y)
ax1.set_title('Linear Scale')
# Symmetric log
ax2.plot(x, y)
ax2.set_yscale('symlog', linthresh=100) # Linear within ±linthresh
ax2.set_title('Symmetric Log Scale')
plt.tight_layout()
plt.show()
symlog Parameters¶
ax.set_yscale('symlog',
linthresh=1, # Range around zero treated as linear
linscale=0.5, # Stretch factor for linear region
base=10) # Log base
Using logit for Probabilities¶
For data between 0 and 1:
x = np.linspace(0.01, 0.99, 100)
y = x # Identity for demonstration
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_xscale('logit')
ax.set_title('Logit Scale (for probabilities)')
plt.show()
Tick Formatting¶
Default Log Ticks¶
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_yscale('log')
# Shows: 10^0, 10^1, 10^2, etc.
Custom Tick Locations¶
from matplotlib.ticker import LogLocator
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_yscale('log')
# Major ticks at powers of 10
ax.yaxis.set_major_locator(LogLocator(base=10, numticks=10))
plt.show()
Scientific Notation¶
from matplotlib.ticker import LogFormatterSciNotation
ax.yaxis.set_major_formatter(LogFormatterSciNotation())
Custom Format¶
from matplotlib.ticker import FuncFormatter
def log_format(x, pos):
if x >= 1e6:
return f'{x/1e6:.0f}M'
elif x >= 1e3:
return f'{x/1e3:.0f}K'
else:
return f'{x:.0f}'
ax.yaxis.set_major_formatter(FuncFormatter(log_format))
Practical Examples¶
1. Exponential Growth¶
import matplotlib.pyplot as plt
import numpy as np
# Population growth
years = np.arange(1800, 2025, 25)
population = [1, 1.2, 1.4, 1.65, 2.0, 2.5, 3.0, 4.0, 6.0, 8.0] # billions
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
ax1.plot(years, population, 'o-')
ax1.set_title('World Population (Linear)')
ax1.set_ylabel('Population (billions)')
ax2.plot(years, population, 'o-')
ax2.set_yscale('log')
ax2.set_title('World Population (Log Scale)')
ax2.set_ylabel('Population (billions)')
for ax in [ax1, ax2]:
ax.set_xlabel('Year')
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
2. Power Law Distributions¶
import matplotlib.pyplot as plt
import numpy as np
# Zipf's law example (word frequencies)
rank = np.arange(1, 101)
frequency = 1000 / rank ** 1.07
fig, ax = plt.subplots()
ax.loglog(rank, frequency, 'o-', markersize=4)
ax.set_xlabel('Rank')
ax.set_ylabel('Frequency')
ax.set_title("Power Law Distribution (Zipf's Law)")
ax.grid(True, alpha=0.3)
plt.show()
3. Frequency Response¶
import matplotlib.pyplot as plt
import numpy as np
# Bode plot style
freq = np.logspace(0, 4, 100) # 1 Hz to 10 kHz
gain = 1 / np.sqrt(1 + (freq / 1000) ** 2) # Low-pass filter
gain_db = 20 * np.log10(gain)
fig, ax = plt.subplots()
ax.semilogx(freq, gain_db)
ax.set_xlabel('Frequency (Hz)')
ax.set_ylabel('Gain (dB)')
ax.set_title('Frequency Response (Bode Plot)')
ax.grid(True, which='both', alpha=0.3)
ax.axhline(-3, color='red', linestyle='--', label='-3 dB cutoff')
ax.legend()
plt.show()
4. Financial Returns¶
import matplotlib.pyplot as plt
import numpy as np
# Compound growth
years = np.arange(0, 31)
returns = {'7% Annual': 1.07 ** years * 1000,
'10% Annual': 1.10 ** years * 1000,
'15% Annual': 1.15 ** years * 1000}
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
for label, values in returns.items():
ax1.plot(years, values, label=label)
ax2.plot(years, values, label=label)
ax1.set_title('Investment Growth (Linear)')
ax2.set_title('Investment Growth (Log Scale)')
ax2.set_yscale('log')
for ax in [ax1, ax2]:
ax.set_xlabel('Years')
ax.set_ylabel('Value ($)')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
5. Scatter Plot with Log Scales¶
import matplotlib.pyplot as plt
import numpy as np
# Data spanning many orders of magnitude
np.random.seed(42)
x = 10 ** (np.random.rand(100) * 4) # 1 to 10000
y = x * (0.5 + np.random.rand(100))
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
ax1.scatter(x, y, alpha=0.6)
ax1.set_title('Linear Scale')
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax2.scatter(x, y, alpha=0.6)
ax2.set_xscale('log')
ax2.set_yscale('log')
ax2.set_title('Log-Log Scale')
ax2.set_xlabel('x (log)')
ax2.set_ylabel('y (log)')
plt.tight_layout()
plt.show()
6. Histograms with Log Scale¶
import matplotlib.pyplot as plt
import numpy as np
# Log-normal distribution
data = np.random.lognormal(0, 1, 10000)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
ax1.hist(data, bins=50, edgecolor='black', alpha=0.7)
ax1.set_title('Linear Scale')
ax1.set_xlabel('Value')
# Log scale on x-axis
ax2.hist(data, bins=np.logspace(-2, 2, 50), edgecolor='black', alpha=0.7)
ax2.set_xscale('log')
ax2.set_title('Log Scale (X-axis)')
ax2.set_xlabel('Value (log)')
plt.tight_layout()
plt.show()
Grid Lines on Log Scale¶
Major and Minor Grid¶
fig, ax = plt.subplots()
ax.loglog(x, y)
# Major grid (powers of 10)
ax.grid(True, which='major', linestyle='-', alpha=0.7)
# Minor grid (subdivisions)
ax.grid(True, which='minor', linestyle=':', alpha=0.4)
plt.show()
Minor Ticks¶
from matplotlib.ticker import LogLocator, NullFormatter
fig, ax = plt.subplots()
ax.loglog(x, y)
# Add minor ticks
ax.yaxis.set_minor_locator(LogLocator(subs=np.arange(2, 10)))
ax.xaxis.set_minor_locator(LogLocator(subs=np.arange(2, 10)))
# Hide minor tick labels
ax.yaxis.set_minor_formatter(NullFormatter())
ax.xaxis.set_minor_formatter(NullFormatter())
ax.grid(True, which='both', alpha=0.3)
plt.show()
Scale Types Summary¶
| Scale | Function | Use Case |
|---|---|---|
'linear' |
Default | Normal data |
'log' |
Logarithmic | Exponential growth, wide range |
'symlog' |
Symmetric log | Data with positive and negative |
'logit' |
Logit | Probabilities (0 to 1) |
'asinh' |
Inverse hyperbolic sine | Like symlog, smoother |
Common Pitfalls¶
1. Zero or Negative Values¶
# This will cause issues:
y = np.array([0, 1, 10, 100])
ax.set_yscale('log') # Warning: zero cannot be displayed
# Solution: filter or offset
y = np.array([0.1, 1, 10, 100]) # Replace 0 with small value
2. Axis Limits with Log Scale¶
# Must set positive limits
ax.set_ylim(0.1, 1000) # Correct
# ax.set_ylim(0, 1000) # Wrong: 0 is invalid
3. Log Bins for Histograms¶
# Use logarithmically spaced bins
bins = np.logspace(np.log10(data.min()), np.log10(data.max()), 50)
ax.hist(data, bins=bins)
ax.set_xscale('log')