To set a Matplotlib y axis to a log scale, call set_yscale on the axes or plt.yscale on the current figure:
ax.set_yscale("log") # object-oriented
plt.yscale("log") # pyplot
ax.set_yscale("log", base=2) # a base other than 10
Two things catch people out. The argument is base, not basey, and any zero or negative value is dropped without a warning.
Both are demonstrated below, on Python 3.12.5, Matplotlib 3.11.2.
Setting the y axis to a log scale
A log axis earns its place whenever your values span several orders of magnitude:
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(1, 11)
y = 2 ** x # doubles every step
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4.2))
ax1.plot(x, y, marker="o", color="#0b6bcb")
ax1.set_title("Linear y axis: everything is squashed")
ax2.plot(x, y, marker="o", color="#0a7d32")
ax2.set_yscale("log") # the whole change
ax2.set_title('ax.set_yscale("log")')
for ax in (ax1, ax2):
ax.set_xlabel("x"); ax.set_ylabel("y"); ax.grid(alpha=.3)
plt.tight_layout(); plt.show()

On the linear axis every point below about 200 is pressed into the bottom of the chart. On the log axis the doubling becomes a straight line.
That straightening is the real signal: exponential growth looks linear on a log axis, so a straight line tells you the growth rate is constant.
plt.yscale vs ax.set_yscale
Two spellings, same effect. The difference is what they act on:
import matplotlib.pyplot as plt
# the pyplot way, acting on the current axes
plt.plot([1, 2, 3], [10, 100, 1000])
plt.yscale("log")
print("plt.yscale('log') -> current axes")
plt.close()
# the object-oriented way, acting on a named axes
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [10, 100, 1000])
ax.set_yscale("log")
print("ax.set_yscale('log') -> that specific axes")
print("\nboth do the same thing. Use ax.set_yscale when you have subplots.")
print("current y scale:", ax.get_yscale())
Output:
plt.yscale('log') -> current axes
ax.set_yscale('log') -> that specific axes
both do the same thing. Use ax.set_yscale when you have subplots.
current y scale: log

plt.yscale uses the current axes. ax.set_yscale names one.| Call | Acts on | Use when |
|---|---|---|
plt.yscale("log") | The current axes | A single quick plot |
ax.set_yscale("log") | That axes object | Subplots, or anything reusable |
plt.semilogy(x, y) | Plots and sets the scale at once | A one-liner |
With more than one subplot, always use ax.set_yscale. plt.yscale will change whichever axes happens to be current, which is rarely the one you meant. The same logic applies to setting the axis range.
The matplotlib yscale log base parameter
The default base is 10. Pass base for anything else, which matters for data that doubles rather than multiplying by ten:
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(1, 11)
y = 2 ** x
fig, axes = plt.subplots(1, 3, figsize=(12, 3.8))
for ax, base in zip(axes, (10, 2, np.e)):
ax.plot(x, y, marker="o", color="#0b6bcb")
ax.set_yscale("log", base=base)
label = {10: "base=10 (default)", 2: "base=2", np.e: "base=np.e"}[base]
ax.set_title(label)
ax.grid(alpha=.3, which="both")
plt.tight_layout(); plt.show()
print("base=2 puts a tick at every doubling: 2, 4, 8, 16, ...")
print("base=10 ticks at 1, 10, 100, 1000")
Output:
base=2 puts a tick at every doubling: 2, 4, 8, 16, ...
base=10 ticks at 1, 10, 100, 1000

base=2 puts a gridline at every doubling, which suits memory sizes, cell counts and anything binary.
base=np.e gives natural log spacing. It’s less readable for most audiences, so reach for it only when the maths calls for it.
Why basey=2 raises a TypeError
If you copied a snippet from an older tutorial, this is the error you’ll hit:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
# the old argument name, still in a lot of tutorials
try:
ax.set_yscale("log", basey=2)
except TypeError as err:
print("basey=2 ->", type(err).__name__ + ":", err)
# the current name
ax.set_yscale("log", base=2)
print("\nbase=2 -> works")
print("y scale is now:", ax.get_yscale())
print("\nbasey was renamed to base in Matplotlib 3.3 (2020).")
Output:
basey=2 -> TypeError: LogScale.__init__() got an unexpected keyword argument 'basey'
base=2 -> works
y scale is now: log
basey was renamed to base in Matplotlib 3.3 (2020).

unexpected keyword argument 'basey'. The new name is base.Matplotlib 3.3 renamed basex and basey to a single base, because the axis is already implied by which method you call.
The same rename hit nonposx and nonposy, which are now both nonpositive.
Why do zero and negative values disappear on a log scale?
The logarithm of zero is undefined and the log of a negative number is not a real number. Matplotlib’s response is to drop those points, quietly:
import matplotlib.pyplot as plt
import numpy as np
import warnings
values = [0, 1, 10, -5, 100]
fig, ax = plt.subplots()
ax.plot(range(5), values)
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
ax.set_yscale("log")
fig.canvas.draw()
print("warnings raised:", [str(w.message) for w in caught] or "NONE")
print("\ny limits chosen:", tuple(round(v, 2) for v in ax.get_ylim()))
print("the 0 and the -5 are simply not drawn, and nothing tells you")
print("\nhow many points are actually plottable?")
print(" ", sum(1 for v in values if v > 0), "of", len(values))
Output:
warnings raised: NONE
y limits chosen: (np.float64(0.79), np.float64(125.89))
the 0 and the -5 are simply not drawn, and nothing tells you
how many points are actually plottable?
3 of 5

Read that output again: no warning is raised. Your chart renders, it looks plausible, and it is missing data.
This is the most dangerous thing on this page. If a log-scale chart looks thinner than you expected, count your points before you trust it.
nonpositive="clip"— the default, pushes bad values to a very small number.nonpositive="mask"— removes them from the line entirely, leaving a gap.- Use
"symlog"instead when the data genuinely crosses zero.
Using symlog for data that crosses zero
symlog is linear near zero and logarithmic beyond it, so negative values survive:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-50, 50, 400)
y = x ** 3
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4.2))
ax1.plot(x, y, color="#c0392b")
ax1.set_yscale("log")
ax1.set_title('"log": the negative half vanishes')
ax2.plot(x, y, color="#0a7d32")
ax2.set_yscale("symlog", linthresh=100)
ax2.set_title('"symlog", linthresh=100: all of it')
for ax in (ax1, ax2):
ax.axhline(0, color="#888", lw=.8); ax.grid(alpha=.3, which="both")
plt.tight_layout(); plt.show()
print("symlog is linear between -linthresh and +linthresh, log outside it")
Output:
symlog is linear between -linthresh and +linthresh, log outside it

log loses half the curve. symlog keeps all of it.linthresh sets where the linear middle stops and the log part begins. Too small and the region near zero stretches out oddly.
Start with linthresh around the smallest value you care about resolving, then adjust until the shape reads correctly.
Formatting log scale tick labels
Powers of ten are readable. Awkward numbers in scientific notation are not:
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np
x = np.arange(1, 11)
y = 2 ** x
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4.2))
ax1.plot(x, y, marker="o")
ax1.set_yscale("log")
ax1.set_title("default: scientific notation")
ax1.grid(alpha=.3, which="both")
ax2.plot(x, y, marker="o")
ax2.set_yscale("log")
ax2.yaxis.set_major_formatter(ticker.ScalarFormatter()) # plain numbers
ax2.yaxis.set_minor_formatter(ticker.NullFormatter())
ax2.set_title("ScalarFormatter: plain numbers")
ax2.grid(alpha=.3, which="both")
plt.tight_layout(); plt.show()
