我想使用matplotlib绘制一些存储在Pandas Dataframe中的数据。我想在x轴刻度上放置特定标签。因此,我将它们设置为:
ax.xaxis.set_ticklabels(data_frame['labels'])
效果很好,但是它为每个数据点设置了一个刻度标签,使绘图不可读,因此我尝试:
ax.locator_params(axis='x', nbins=3)
这将刻度数减少到3,但是标签与正确的数据点不对应(如果标签是a,b,c,d,e ...,x,y,z我得到标签a,b,c代替,m,z之类的东西)。我的下一个想法是设置刻度标签位置:
ax.xaxis.set_ticks(data_frame.index.values)
但它不起作用。
起作用的是:
ax.xaxis.set_ticklabels(data_frame['labels'][::step])
ax.xaxis.set_ticks(data_frame.index.values[::step])
无需设置任何
locator_params
。这几乎是完美的。它修复了刻度和标签,但是当我缩放绘图时(使用matplotlib交互式窗口),显然不会出现新标签。我需要的是可读取的刻度线,这些刻度线可以根据绘图缩放进行调整(这是
ax.locator_params(axis='x', nbins=3)
在没有任何自定义标签的情况下正确执行的操作)。换句话说:我需要为每个数据点设置特定的标签,但是在绘图轴刻度上仅显示其中一些标签,而不会丢失正确的分配。
最佳答案
使用Locator
,我们可以定义将产生多少个滴答声以及应将它们放置在何处。通过对MaxNLocator
进行子分类(这实际上是默认的Locator),我们可以重用该功能,并简单地过滤掉不需要的刻度(例如,标签范围之外的刻度)。由于稀疏或非等距的x范围数据会破坏我的简单过滤解决方案,因此我的方法肯定可以在这一点上得到改进。浮点值也可能是一个挑战,但是我敢肯定,如果不满足上述条件,那么这样的数据范围总是可以映射到一个方便的整数范围。但这超出了这个问题的范围。
使用Formatter
,我们现在可以简单地在标签列表中查找相应的标签以生成正确的刻度标签。为了找到最接近的匹配值,我们可以有效地利用bisect
模块(related question)。对于静态图,我们可以依靠这样的假设,即我们的定位器已经产生了可以直接用于列表访问的索引(避免了不必要的二等分运算)。但是,动态视图(请参见屏幕快照的左下角)使用Formatter格式化非刻度位置标签。因此,使用bisect是更通用,更稳定的方法。
import matplotlib.pyplot as plt
import numpy as np
import bisect
from matplotlib.ticker import Formatter
from matplotlib.ticker import MaxNLocator
x = np.arange(0, 100, 1)
y = np.sin(x)
# custom labels, could by anything
l = ["!{}!".format(v) for v in x]
plt.plot(x, y)
ax = plt.gca()
class LookupLocator(MaxNLocator):
def __init__(self, valid_ticks, nbins='auto', min_n_ticks=0, integer=True):
MaxNLocator.__init__(self, integer=integer, nbins=nbins, min_n_ticks=min_n_ticks)
self._valid_ticks = valid_ticks
self._integer = integer
def is_tick_valid(self, t):
if self._integer:
return t.is_integer() and int(t) in self._valid_ticks
return t in self._valid_ticks
def tick_values(self, vmin, vmax):
return filter(self.is_tick_valid, MaxNLocator.tick_values(self, vmin, vmax))
class LookupFormatter(Formatter):
def __init__(self, tick_values, tick_labels):
Formatter.__init__(self)
self._tick_values = tick_values
self._tick_labels = tick_labels
def _find_closest(self, x):
# https://stackoverflow.com/questions/12141150/from-list-of-integers-get-number-closest-to-a-given-value
i = bisect.bisect_left(self._tick_values, x)
if i == 0:
return i
if i == len(self._tick_values):
return i - 1
l, r = self._tick_values[i - 1], self._tick_values[i]
if l - x < x - r:
return i
return i - 1
def __call__(self, x, pos=None):
return self._tick_labels[self._find_closest(x)]
ax.xaxis.set_major_locator(LookupLocator(x))
ax.xaxis.set_major_formatter(LookupFormatter(x, l))
plt.show()