Search code examples
pythonmatplotlibseabornboxplotviolin-plot

Can you have a box plot overlap a half violin plot on a raincloud plot using Python?


I've created a raincloud plot using the following code:

#Rainplot

dx = 'Time Point'; dy = 'Score'; ort = "v"; pal = "Set2"; sigma = .2; 

f, ax = plt.subplots(figsize=(7, 5))

pt.RainCloud(x = dx, y = dy, data = df, palette = pal, bw = sigma,
             width_viol = .9, ax= ax, orient = ort, move = .2)

plt.title("")
plt.ylabel('Score', fontsize = 20) 
plt.xlabel('', fontsize = 20) 
plt.grid(color = 'w')

if savefigs: 
    plt.savefig('/Users/zeidanlab/Desktop/gv/rainplots/figure_tsc.png', bbox_inches='tight')

Raincloud Plot

I'm wondering if there is a way to have the box plot overlap with the distribution blob.


Solution

  • Here is an approach to combine a half violinplot with a boxplot and a stripplot.

    Half violins are created by extracting their bounding box, and using half of it to clip the violins.

    The dots of the stripplot are moved in order not to overlap.

    from matplotlib import pyplot as plt
    import seaborn as sns
    import numpy as np
    
    sns.set_style('white')
    tips = sns.load_dataset('tips')
    palette = sns.cubehelix_palette(start=.5, rot=-.5, dark=0.3, light=0.7)
    ax = sns.violinplot(y="day", x="total_bill", data=tips,
                        palette=palette,
                        scale="width", inner=None)
    xlim = ax.get_xlim()
    ylim = ax.get_ylim()
    for violin in ax.collections:
        bbox = violin.get_paths()[0].get_extents()
        x0, y0, width, height = bbox.bounds
        violin.set_clip_path(plt.Rectangle((x0, y0), width, height / 2, transform=ax.transData))
    
    sns.boxplot(y="day", x="total_bill", data=tips, saturation=1, showfliers=False,
                width=0.3, boxprops={'zorder': 3, 'facecolor': 'none'}, ax=ax)
    old_len_collections = len(ax.collections)
    sns.stripplot(y="day", x="total_bill", data=tips, color='dodgerblue', ax=ax)
    for dots in ax.collections[old_len_collections:]:
        dots.set_offsets(dots.get_offsets() + np.array([0, 0.12]))
    ax.set_xlim(xlim)
    ax.set_ylim(ylim)
    plt.show()
    

    half violin with boxplot and stripplot

    Here is another example, in the vertical direction.

    from matplotlib import pyplot as plt
    import seaborn as sns
    import numpy as np
    
    sns.set_style('white')
    iris = sns.load_dataset('iris')
    palette = 'Set2'
    ax = sns.violinplot(x="species", y="sepal_length", data=iris, hue="species", dodge=False,
                        palette=palette,
                        scale="width", inner=None)
    xlim = ax.get_xlim()
    ylim = ax.get_ylim()
    for violin in ax.collections:
        bbox = violin.get_paths()[0].get_extents()
        x0, y0, width, height = bbox.bounds
        violin.set_clip_path(plt.Rectangle((x0, y0), width / 2, height, transform=ax.transData))
    
    sns.boxplot(x="species", y="sepal_length", data=iris, saturation=1, showfliers=False,
                width=0.3, boxprops={'zorder': 3, 'facecolor': 'none'}, ax=ax)
    old_len_collections = len(ax.collections)
    sns.stripplot(x="species", y="sepal_length", data=iris, hue="species", palette=palette, dodge=False, ax=ax)
    for dots in ax.collections[old_len_collections:]:
        dots.set_offsets(dots.get_offsets() + np.array([0.12, 0]))
    ax.set_xlim(xlim)
    ax.set_ylim(ylim)
    ax.legend_.remove()
    plt.show()
    

    half violin with boxplot, vertical