使用matplotlib在箱线图中添加散点分布

25

我在这篇文章(图2)中看到了这个精美的箱线图。

一个精美的箱线图

正如您所见,这是一个箱线图,在上面叠加了一些黑色点的散点图:x表示黑色点(随机排序),y是所关注的变量。我想使用Matplotlib做类似的图形,但我不知道从哪里开始。到目前为止,我在网上找到的箱线图差别很大,都是这样的:

普通的箱线图

Matplotlib文档: http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.boxplot

着色箱线图的方法: https://github.com/jbmouret/matplotlib_for_papers#colored-boxes


可能相关:https://dev59.com/Nofca4cB1Zd3GeqPouC7 - Wok
1
请注意,解决此问题的更现代方法可能是使用 seaborn。https://python-graph-gallery.com/36-add-jitter-over-boxplot-seaborn/ - Eike P.
4个回答

33

您要找的是一种在x轴上添加抖动的方法。

类似于从这里获取的以下内容:

bp = titanic.boxplot(column='age', by='pclass', grid=False)
for i in [1,2,3]:
    y = titanic.age[titanic.pclass==i].dropna()
    # Add some random "jitter" to the x-axis
    x = np.random.normal(i, 0.04, size=len(y))
    plot(x, y, 'r.', alpha=0.2)

在这里输入图片描述

引用链接:

向箱线图添加附加信息的一种方法是覆盖实际数据;这通常适用于小或中等大小的数据系列。当数据密集时,上面使用的一些技巧有助于可视化:

  1. 减小 alpha 水平以使点部分透明
  2. 沿 x 轴添加随机“抖动”以避免过度叠加

代码如下:

import pylab as P
import numpy as np

# Define data
# Define numBoxes

P.figure()

bp = P.boxplot(data)

for i in range(numBoxes):
    y = data[i]
    x = np.random.normal(1+i, 0.04, size=len(y))
    P.plot(x, y, 'r.', alpha=0.2)

P.show()

18

扩展Kyrubas的解决方案并仅使用matplotlib进行绘图部分(有时我难以使用matplotlib对pandas图进行格式化)。

from matplotlib import cm
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

# initialize dataframe
n = 200
ngroup = 3
df = pd.DataFrame({'data': np.random.rand(n), 'group': map(np.floor, np.random.rand(n) * ngroup)})

group = 'group'
column = 'data'
grouped = df.groupby(group)

names, vals, xs = [], [] ,[]

for i, (name, subdf) in enumerate(grouped):
    names.append(name)
    vals.append(subdf[column].tolist())
    xs.append(np.random.normal(i+1, 0.04, subdf.shape[0]))

plt.boxplot(vals, labels=names)
ngroup = len(vals)
clevels = np.linspace(0., 1., ngroup)

for x, val, clevel in zip(xs, vals, clevels):
    plt.scatter(x, val, c=cm.prism(clevel), alpha=0.4)

输入图像描述


2
对于Python 3用户,您需要将map函数包装在列表中,如下所示:'group': list(map(np.floor, np.random.rand(n) * ngroup)) - jss367
最好定义一个函数,可以像经典的箱线图一样调用它(也许添加一个选项只显示箱子外的点)。我认为所有的箱线图都应该被抖动箱线图所取代。 - Jakob
我在我的答案中添加了这个功能作为一个Python函数:https://dev59.com/Nl0a5IYBdhLWcg3w88k0#70311225。在那里,人们还可以选择仅显示箱线图之外的异常值。 - Jakob

16
再次看原始问题(并且有了更多的经验),我认为与其使用`sns.swarmplot`,使用`sns.stripplot`会更准确。
作为一个更简单、可能是更新的选择,你可以使用seaborn的swarmplot选项。
import seaborn as sns
import matplotlib.pyplot as plt

sns.set(style="whitegrid")
tips = sns.load_dataset("tips")

ax = sns.boxplot(x="day", y="total_bill", data=tips, showfliers = False)
ax = sns.swarmplot(x="day", y="total_bill", data=tips, color=".25")

plt.show()

enter image description here



2
是的,当使用swarmplot处理数千个数据点时,计算机也会永远挂起。 - user3496060

2
通过扩展解决方案,由Kyrubashwang,您还可以定义一个函数scattered_boxplot(并将其添加为plt.Axes的方法),这样您就可以始终使用scattered_boxplot而不是boxplot
fig, ax = plt.subplots(figsize=(5, 6))
ax.scattered_boxplot(x=[np.array([1,2,3]*50),np.array([1.1,2.2,3.3])])

函数scattered_boxplot可以仅使用matplotlib定义如下:

import matplotlib.pyplot as plt

import numpy as np
from numbers import Number

def scattered_boxplot(ax, x, notch=None, sym=None, vert=None, whis=None, positions=None, widths=None, patch_artist=None, bootstrap=None, usermedians=None, conf_intervals=None, meanline=None, showmeans=None, showcaps=None, showbox=None,
                      showfliers="unif",
                      hide_points_within_whiskers=False,
                      boxprops=None, labels=None, flierprops=None, medianprops=None, meanprops=None, capprops=None, whiskerprops=None, manage_ticks=True, autorange=False, zorder=None, *, data=None):
    if showfliers=="classic":
        classic_fliers=True
    else:
        classic_fliers=False
    ax.boxplot(x, notch=notch, sym=sym, vert=vert, whis=whis, positions=positions, widths=widths, patch_artist=patch_artist, bootstrap=bootstrap, usermedians=usermedians, conf_intervals=conf_intervals, meanline=meanline, showmeans=showmeans, showcaps=showcaps, showbox=showbox,
               showfliers=classic_fliers,
               boxprops=boxprops, labels=labels, flierprops=flierprops, medianprops=medianprops, meanprops=meanprops, capprops=capprops, whiskerprops=whiskerprops, manage_ticks=manage_ticks, autorange=autorange, zorder=zorder,data=data)
    N=len(x)
    datashape_message = ("List of boxplot statistics and `{0}` "
                             "values must have same the length")
    # check position
    if positions is None:
        positions = list(range(1, N + 1))
    elif len(positions) != N:
        raise ValueError(datashape_message.format("positions"))

    positions = np.array(positions)
    if len(positions) > 0 and not isinstance(positions[0], Number):
        raise TypeError("positions should be an iterable of numbers")

    # width
    if widths is None:
        widths = [np.clip(0.15 * np.ptp(positions), 0.15, 0.5)] * N
    elif np.isscalar(widths):
        widths = [widths] * N
    elif len(widths) != N:
        raise ValueError(datashape_message.format("widths"))

    if hide_points_within_whiskers:
        import matplotlib.cbook as cbook
        from matplotlib import rcParams
        if whis is None:
            whis = rcParams['boxplot.whiskers']
        if bootstrap is None:
            bootstrap = rcParams['boxplot.bootstrap']
        bxpstats = cbook.boxplot_stats(x, whis=whis, bootstrap=bootstrap,
                                       labels=labels, autorange=autorange)
    for i in range(N):
        if hide_points_within_whiskers:
            xi=bxpstats[i]['fliers']
        else:
            xi=x[i]
        if showfliers=="unif":
            jitter=np.random.uniform(-widths[i]*0.5,widths[i]*0.5,size=np.size(xi))
        elif showfliers=="normal":
            jitter=np.random.normal(loc=0.0, scale=widths[i]*0.1,size=np.size(xi))
        elif showfliers==False or showfliers=="classic":
            return
        else:
            raise NotImplementedError("showfliers='"+str(showfliers)+"' is not implemented. You can choose from 'unif', 'normal', 'classic' and False")

        plt.scatter(positions[i]+jitter,xi,alpha=0.2,marker="o", facecolors='none', edgecolors="k")

并且可以通过添加方法到plt.Axes中实现

setattr(plt.Axes, "scattered_boxplot", scattered_boxplot)

一个人仍然可以访问所有箱线图选项,并且还可以选择用于水平抖动的散点分布(例如showfliers="unif"),并且可以选择是否显示在须之外的异常值(例如hide_points_within_whiskers=False)。

这个解决方案已经相当不错了。另一种选择是直接更改matplotlib的源代码,主要是在这一行:https://github.com/matplotlib/matplotlib/blob/9765379ce6e7343070e815afc0988874041b98e2/lib/matplotlib/axes/_axes.py#L4006


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接