Skip to content

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')