Matplotlib:避免“散点/点/蜂群”图中的重叠数据点
Posted
技术标签:
【中文标题】Matplotlib:避免“散点/点/蜂群”图中的重叠数据点【英文标题】:Matplotlib: avoiding overlapping datapoints in a "scatter/dot/beeswarm" plot 【发布时间】:2012-01-30 02:30:29 【问题描述】:使用matplotlib
绘制点图时,我想偏移重叠的数据点以使它们全部可见。例如,如果我有:
CategoryA: 0,0,3,0,5
CategoryB: 5,10,5,5,10
我希望每个 CategoryA
"0" 数据点并排设置,而不是彼此重叠,同时仍与 CategoryB
保持不同。
在 R (ggplot2
) 中有一个 "jitter"
选项可以执行此操作。 matplotlib 中是否有类似的选项,或者是否有其他方法会导致类似的结果?
编辑: 澄清一下,the "beeswarm"
plot in R 基本上是我的想法,pybeeswarm
是 matplotlib/Python 版本的早期但有用的开始。
编辑: 添加 Seaborn 的 Swarmplot,在 0.7 版中引入,是我想要的一个很好的实现。
【问题讨论】:
在dot plot 中,这些点已经在它们的列中分开了 “点图”的 wiki 定义不是我想要描述的,但我从未听说过“点图”以外的术语。它大约是一个散点图,但带有任意(不一定是数字)x 标签。因此,在我在问题中描述的示例中,“CategoryA”将有一列值,“CategoryB”将有一列值,等等。(编辑:“克利夫兰点图的***定义" 与我正在寻找的更相似,但仍然不完全相同。) 类似问题:***.com/questions/56347325 【参考方案1】:不知道这里有一个直接的 mpl 替代方案,你有一个非常基本的建议:
from matplotlib import pyplot as plt
from itertools import groupby
CA = [0,4,0,3,0,5]
CB = [0,0,4,4,2,2,2,2,3,0,5]
x = []
y = []
for indx, klass in enumerate([CA, CB]):
klass = groupby(sorted(klass))
for item, objt in klass:
objt = list(objt)
points = len(objt)
pos = 1 + indx + (1 - points) / 50.
for item in objt:
x.append(pos)
y.append(item)
pos += 0.04
plt.plot(x, y, 'o')
plt.xlim((0,3))
plt.show()
【讨论】:
【参考方案2】:我使用 numpy.random 沿 X 轴“分散/蜂群”数据,但围绕每个类别的固定点,然后基本上为每个类别执行 pyplot.scatter():
import matplotlib.pyplot as plt
import numpy as np
#random data for category A, B, with B "taller"
yA, yB = np.random.randn(100), 5.0+np.random.randn(1000)
xA, xB = np.random.normal(1, 0.1, len(yA)),
np.random.normal(3, 0.1, len(yB))
plt.scatter(xA, yA)
plt.scatter(xB, yB)
plt.show()
【讨论】:
【参考方案3】:通过@user2467675 扩展答案,我是这样做的:
def rand_jitter(arr):
stdev = .01 * (max(arr) - min(arr))
return arr + np.random.randn(len(arr)) * stdev
def jitter(x, y, s=20, c='b', marker='o', cmap=None, norm=None, vmin=None, vmax=None, alpha=None, linewidths=None, verts=None, hold=None, **kwargs):
return scatter(rand_jitter(x), rand_jitter(y), s=s, c=c, marker=marker, cmap=cmap, norm=norm, vmin=vmin, vmax=vmax, alpha=alpha, linewidths=linewidths, **kwargs)
stdev
变量确保抖动足以在不同的尺度上看到,但它假定轴的限制为零和最大值。
然后您可以调用jitter
而不是scatter
。
【讨论】:
我真的很喜欢你自动计算抖动的规模。很适合我。 如果arr
仅包含零(即stdev=0),这是否有效?
我必须从jitter()
的参数和scatter()
的调用中删除holds
和verts
才能让它在2020 年工作。希望这对某人有帮助:)。 【参考方案4】:
解决问题的一种方法是将散点图/点图/蜂群图中的每一“行”视为直方图中的一个 bin:
data = np.random.randn(100)
width = 0.8 # the maximum width of each 'row' in the scatter plot
xpos = 0 # the centre position of the scatter plot in x
counts, edges = np.histogram(data, bins=20)
centres = (edges[:-1] + edges[1:]) / 2.
yvals = centres.repeat(counts)
max_offset = width / counts.max()
offsets = np.hstack((np.arange(cc) - 0.5 * (cc - 1)) for cc in counts)
xvals = xpos + (offsets * max_offset)
fig, ax = plt.subplots(1, 1)
ax.scatter(xvals, yvals, s=30, c='b')
这显然涉及对数据进行分箱,因此您可能会丢失一些精度。如果你有离散数据,你可以替换:
counts, edges = np.histogram(data, bins=20)
centres = (edges[:-1] + edges[1:]) / 2.
与:
centres, counts = np.unique(data, return_counts=True)
保留精确 y 坐标(即使对于连续数据)的另一种方法是使用 kernel density estimate 来缩放 x 轴上随机抖动的幅度:
from scipy.stats import gaussian_kde
kde = gaussian_kde(data)
density = kde(data) # estimate the local density at each datapoint
# generate some random jitter between 0 and 1
jitter = np.random.rand(*data.shape) - 0.5
# scale the jitter by the KDE estimate and add it to the centre x-coordinate
xvals = 1 + (density * jitter * width * 2)
ax.scatter(xvals, data, s=30, c='g')
for sp in ['top', 'bottom', 'right']:
ax.spines[sp].set_visible(False)
ax.tick_params(top=False, bottom=False, right=False)
ax.set_xticks([0, 1])
ax.set_xticklabels(['Histogram', 'KDE'], fontsize='x-large')
fig.tight_layout()
第二种方法大致基于violin plots 的工作方式。它仍然不能保证没有任何点重叠,但我发现在实践中,只要有相当数量的点(> 20),它往往会给出非常漂亮的结果,并且分布可以相当好地近似由高斯和。
【讨论】:
不幸的是,xvals = 1 + (density * jitter * width * 2)
部分中的2
是一个必须根据数据集进行调整的参数。对于我的数据,我必须将其设置为 2000 以查看任何抖动,并设置为 20,000 以在最密集的区域获得良好的分散。【参考方案5】:
Seaborn 通过sns.swarmplot()
提供类直方图分类点图,通过sns.stripplot()
提供抖动分类点图:
import seaborn as sns
sns.set(style='ticks', context='talk')
iris = sns.load_dataset('iris')
sns.swarmplot('species', 'sepal_length', data=iris)
sns.despine()
sns.stripplot('species', 'sepal_length', data=iris, jitter=0.2)
sns.despine()
【讨论】:
你的例子不是两个分类变量,而是一个分类和一个数字(sepal_length)。 @felice 这个问题要求一个分类和一个数字 甚至变量名也包含“类别”一词。但我现在明白我的困惑了,谢谢。【参考方案6】:Seaborn 的 swarmplot 似乎最适合您的想法,但您也可以使用 Seaborn 的 regplot:
import seaborn as sns
iris = sns.load_dataset('iris')
sns.swarmplot('species', 'sepal_length', data=iris)
sns.regplot(x='sepal_length',
y='sepal_width',
data=iris,
fit_reg=False, # do not fit a regression line
x_jitter=0.1, # could also dynamically set this with range of data
y_jitter=0.1,
scatter_kws='alpha': 0.5) # set transparency to 50%
【讨论】:
【参考方案7】:通过@wordsforthewise 扩展答案(抱歉,无法评论我的声誉),如果您需要抖动和使用色调以某种分类(就像我所做的那样)为点着色,Seaborn 的 lmplot 是一个不错的选择而不是reglpot:
import seaborn as sns
iris = sns.load_dataset('iris')
sns.lmplot(x='sepal_length', y='sepal_width', hue='species', data=iris, fit_reg=False, x_jitter=0.1, y_jitter=0.1)
【讨论】:
如果您想在现有答案中添加一些内容,您可以对其进行编辑,如果该答案没问题,那么您可以添加另一个答案来扩展它。 ;)以上是关于Matplotlib:避免“散点/点/蜂群”图中的重叠数据点的主要内容,如果未能解决你的问题,请参考以下文章
在 PyQt4 的嵌入式 matplotlib 图中使用 ginput