Skip to content

Logarithmic Scales

Logarithmic scales compress large ranges of data, making patterns visible across multiple orders of magnitude.

Mental Model

A log scale replaces equal spacing with equal ratios: each tick mark is 10x (or 2x, etc.) the previous one. This turns exponential growth into straight lines and makes patterns across orders of magnitude visible. Use ax.set_yscale('log') when your data spans several powers of ten and small values are getting crushed against zero.

When to Use a Log Scale

Switch to log when:

  • Data spans multiple orders of magnitude (e.g., 1 to 1,000,000)
  • Exponential growth looks like a hockey stick on linear scale
  • Small values are compressed near zero and invisible
  • You want to compare ratios rather than differences

Interpretation Rules

text Straight line on semi-log (log y, linear x) → exponential relationship Straight line on log-log (log y, log x) → power-law relationship These are among the most powerful diagnostic tools in science: plot your data on a log scale and see if it straightens out. If it does, you know the functional form.

Setting Log Scale

Using set_xscale / set_yscale

```python 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

python 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

```python 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

python fig, ax = plt.subplots() ax.loglog(x, y) # Both axes logarithmic ax.set_title('loglog') plt.show()

Comparison: Linear vs Log Scale

```python 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

python ax.set_yscale('log') # Base 10

Base 2 (Binary)

python ax.set_yscale('log', base=2)

Natural Log (Base e)

python ax.set_yscale('log', base=np.e)

Custom Base

python 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:

```python 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

python 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:

```python 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

```python fig, ax = plt.subplots() ax.plot(x, y) ax.set_yscale('log')

Shows: 10^0, 10^1, 10^2, etc.

```

Custom Tick Locations

```python 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

```python from matplotlib.ticker import LogFormatterSciNotation

ax.yaxis.set_major_formatter(LogFormatterSciNotation()) ```

Custom Format

```python 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

```python 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

```python 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

```python 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

```python 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

```python 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

```python 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

```python 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

```python 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

```python

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

```python

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

```python

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

The Deeper Meaning of Scale Choice

Changing the scale is not just a visual preference --- it changes the geometry of your data:

text Linear scale → equal spacing = equal differences (additive structure) Log scale → equal spacing = equal ratios (multiplicative structure)

This is why exponential growth becomes a straight line on a log scale: growth by a constant factor (multiplicative) maps to growth by a constant step (additive) in log space. Choosing the right scale reveals the natural structure of your data.


Exercises

Exercise 1. Plot the function \(y = x^3\) for x in \([1, 1000]\) using four scale combinations in a 2x2 grid: linear-linear, log-linear (log x, linear y), linear-log (linear x, log y), and log-log. Title each subplot with its scale type.

Solution to Exercise 1
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(1, 1000, 500)
y = x ** 3

fig, axes = plt.subplots(2, 2, figsize=(10, 8))

axes[0, 0].plot(x, y)
axes[0, 0].set_title('Linear-Linear')

axes[0, 1].plot(x, y)
axes[0, 1].set_xscale('log')
axes[0, 1].set_title('Log-Linear')

axes[1, 0].plot(x, y)
axes[1, 0].set_yscale('log')
axes[1, 0].set_title('Linear-Log')

axes[1, 1].plot(x, y)
axes[1, 1].set_xscale('log')
axes[1, 1].set_yscale('log')
axes[1, 1].set_title('Log-Log')

for ax in axes.flat:
    ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

Exercise 2. Create a semilogy plot of three exponential decays: \(y = e^{-x}\), \(y = e^{-2x}\), and \(y = e^{-3x}\) for x in \([0, 5]\). On a log scale y-axis, these should appear as straight lines. Add a legend and grid.

Solution to Exercise 2
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 5, 200)

fig, ax = plt.subplots(figsize=(8, 5))
for k in [1, 2, 3]:
    ax.semilogy(x, np.exp(-k * x), label=f'exp(-{k}x)')

ax.set_xlabel('x')
ax.set_ylabel('y (log scale)')
ax.set_title('Exponential Decays on Semilogy')
ax.legend()
ax.grid(True, alpha=0.3)
plt.show()

Exercise 3. Use ax.set_xscale('symlog', linthresh=1) to plot the function \(y = x^3\) over \([-100, 100]\). The symmetric log scale handles both positive and negative values. Compare this with a standard linear plot side by side, and shade the linear region \([-1, 1]\) with axvspan.

Solution to Exercise 3
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(-100, 100, 1000)
y = x ** 3

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))

ax1.plot(x, y, color='navy')
ax1.set_title('Linear Scale')
ax1.set_xlabel('x')
ax1.set_ylabel(r'$x^3$')
ax1.grid(True, alpha=0.3)

ax2.plot(x, y, color='navy')
ax2.set_xscale('symlog', linthresh=1)
ax2.set_title('Symmetric Log Scale (linthresh=1)')
ax2.set_xlabel('x (symlog)')
ax2.set_ylabel(r'$x^3$')
ax2.axvspan(-1, 1, color='yellow', alpha=0.3, label='Linear region')
ax2.legend()
ax2.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

Exercise 4. Generate data from a power law y = 2 * x^1.5 for x in [1, 1000]. Plot it on linear, semi-log, and log-log axes (3 subplots). Verify that only the log-log plot produces a straight line, and explain why the slope of that line equals the exponent (1.5).

Solution to Exercise 4
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(1, 1000, 500)
y = 2 * x ** 1.5

fig, axes = plt.subplots(1, 3, figsize=(15, 4))

axes[0].plot(x, y)
axes[0].set_title('Linear: curved')

axes[1].semilogy(x, y)
axes[1].set_title('Semi-log: still curved')

axes[2].loglog(x, y)
axes[2].set_title('Log-log: STRAIGHT (slope=1.5)')

for ax in axes:
    ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

# On log-log: log(y) = log(2) + 1.5*log(x)
# This is linear in log-log space with slope = exponent
# The straight line confirms the power-law relationship