【问题标题】:Adding a scatter of points to a boxplot using matplotlib使用 matplotlib 向箱线图添加点散点图
【发布时间】:2015-06-29 00:28:31
【问题描述】:

我在this article(图2)中看到了这个精彩的箱线图。

如您所见,这是一个箱线图,上面叠加了黑点的散点图:x 索引黑点(以随机顺序),y 是感兴趣的变量。我想使用 Matplotlib 做类似的事情,但我不知道从哪里开始。到目前为止,我在网上找到的箱线图不太酷,看起来像这样:

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

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

【问题讨论】:

标签: python matplotlib boxplot


【解决方案1】:

您正在寻找一种将抖动添加到 x 轴的方法。

类似这样的内容来自here:

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()

【讨论】:

    【解决方案2】:

    扩展 Kyrubas 的解决方案并仅将 matplotlib 用于绘图部分(有时我很难使用 matplotlib 格式化熊猫图)。

    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)
    

    【讨论】:

    • 对于 Python 3 用户,您需要将地图包装在一个列表中,如下所示:'group': list(map(np.floor, np.random.rand(n) * ngroup))
    • 最好为此定义一个函数,可以以与经典箱线图相同的方式调用(并且可能添加一个选项以仅显示框外的点)。我认为所有的箱线图通常都应该用抖动的箱线图代替。
    • 我已将此功能作为 python 函数添加到我的答案中:stackoverflow.com/a/70311225/7735095。也可以选择只显示胡须之外的传单。
    【解决方案3】:

    作为一个更简单、可能更新的选项,您可以使用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()
    


    再看原来的问题(自己也有更多经验),我觉得sns.swarmplot,sns.stripplot 会更准确。

    【讨论】:

    • 是的,即使在使用 swarmplot 处理数千个数据点时,计算机也会死机 4 次。
    【解决方案4】:

    通过Kyrubas 和hwang 扩展solutions,您还可以定义一个函数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 by

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

    仍然可以使用箱线图的所有选项,另外可以选择用于水平抖动的覆盖分布(例如showfliers="unif"),并且可以选择是否也应显示晶须外的传单(例如@987654335 @)。

    这个解决方案已经很好用了。另一种方法是直接更改matplotlib的源代码,主要是:https://github.com/matplotlib/matplotlib/blob/9765379ce6e7343070e815afc0988874041b98e2/lib/matplotlib/axes/_axes.py#L4006

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-05-06
      • 2021-01-28
      • 1970-01-01
      • 1970-01-01
      • 2014-03-31
      • 1970-01-01
      • 2016-01-14
      相关资源
      最近更新 更多