1

The code below produces plots like this one:

enter image description here

I need to show only the tick labels in the y axis that are over the horizontal line. In this case, the labels [2,3,4,5] would need to be hidden. I've tried using

ax.get_yticks()
ax.get_yticklabels()

to retrieve the ticks that are drawn, and from those select only the ones above the y_min value to show. Neither command returns the actual tick labels drawn in the plot.

How can I do this?


import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FormatStrFormatter

# Some random data
x = np.random.uniform(1, 20, 100)
y = np.array(list(np.random.uniform(1, 150, 97)) + [4, 7, 9])
y_min = np.random.uniform(4, 10)

ax = plt.subplot(111)
ax.scatter(x, y)
ax.hlines(y_min, xmin=min(x), xmax=max(x))

ax.set_xscale('log')
ax.set_yscale('log')
ax.yaxis.set_minor_formatter(FormatStrFormatter('%.0f'))
ax.yaxis.set_major_formatter(FormatStrFormatter('%.0f'))

plt.show()
Zephyr
  • 11,891
  • 53
  • 45
  • 80
Gabriel
  • 40,504
  • 73
  • 230
  • 404

2 Answers2

3

You have to get current y tick labels:

fig.canvas.draw()
labels = [float(text.get_text()) for text in ax.yaxis.get_ticklabels(which = 'minor')]

Then apply the filter you need:

labels_above_threshold = [label if label >= y_min else '' for label in labels]

And finally set filtered labels:

ax.yaxis.set_ticklabels(labels_above_threshold, minor = True)

Complete Code

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FormatStrFormatter

x = np.random.uniform(1, 20, 100)
y = np.array(list(np.random.uniform(1, 150, 97)) + [4, 7, 9])
y_min = np.random.uniform(4, 10)

fig, ax = plt.subplots()
ax.scatter(x, y)
ax.hlines(y_min, xmin=min(x), xmax=max(x))

ax.set_xscale('log')
ax.set_yscale('log')
ax.yaxis.set_minor_formatter(FormatStrFormatter('%.0f'))
ax.yaxis.set_major_formatter(FormatStrFormatter('%.0f'))

fig.canvas.draw()

# MINOR AXIS
labels = [int(text.get_text()) for text in ax.yaxis.get_ticklabels(which = 'minor')]
labels_above_threshold = [label if label >= y_min else '' for label in labels]
ax.yaxis.set_ticklabels(labels_above_threshold, minor = True)

# MAJOR AXIS
labels = [int(text.get_text()) for text in ax.yaxis.get_ticklabels(which = 'major')]
labels_above_threshold = [label if label >= y_min else '' for label in labels]
ax.yaxis.set_ticklabels(labels_above_threshold, minor = False)

plt.show()

enter image description here

Zephyr
  • 11,891
  • 53
  • 45
  • 80
  • 1
    This almost gives me the plot I need, I just have to change the `float` to `int` to remove the `.0` decimal place. Thank you! – Gabriel Aug 12 '21 at 14:16
3

The tick labels are only available when the plot is effectively drawn. Note that the positions will change when the plot is interactively resized or zoomed in.

An idea is to add the test to the formatter function, so everything will stay OK after zooming etc.

The following example code uses the latest matplotlib, which allows to set a FuncFormatter without declaring a separate function:

import numpy as np
import matplotlib.pyplot as plt

x = np.random.uniform(1, 20, 100)
y = np.array(list(np.random.uniform(1, 150, 97)) + [4, 7, 9])
y_min = np.random.uniform(4, 10)

ax = plt.subplot(111)
ax.scatter(x, y)
ax.axhline(y_min) # occupies the complete width of the plot

ax.set_xscale('log')
ax.set_yscale('log')
ax.yaxis.set_minor_formatter(lambda x, t: f'{x:.0f}' if x >= y_min else None)
ax.yaxis.set_major_formatter(lambda x, t: f'{x:.0f}' if x >= y_min else None)

plt.show()

custom formatter

PS: You might use ax.tick_params(length=4, which='both') to set the same tick length for minor and major ticks.

JohanC
  • 71,591
  • 8
  • 33
  • 66
  • I'm selecting this question because it's cleaner and it does not throw a warning of the type `FixedFormatter should only be used together with FixedLocator` like Andrea's does. Thank you! – Gabriel Aug 12 '21 at 14:19