2

I want to plot both a boxplot and the mean in one figure. So far my plot looks like this using these lines of code:

sns.swarmplot(x="stimulus", y="data", data=spi_num.astype(np.float), edgecolor="black", linewidth=.9)
sns.boxplot(x="stimulus", y="data", data=spi_num.astype(np.float), saturation=1)
sns.pointplot(x="stimulus", y="data", data=spi_num.astype(np.float), linestyles='', scale=1, color='k', errwidth=1.5, capsize=0.2, markers='x')
sns.pointplot(x="stimulus", y="data", data=spi_num.astype(np.float), linestyles='--', scale=0.4, color='k', errwidth=0, capsize=0)
plt.ylabel("number of spikes")
plt.title("Median Number of Spikes");

enter image description here

I would like to shift my mean 'x' markers a bit to the right so that the errorbars don't overlap with the whiskers from the boxplot. Any idea how to do that? A bonus question: how do I insert a legend in this plot saying "x: mean, o: data values" elegantly?


Build my dataframe

trial_vec    = np.tile(np.arange(16)+1, 10)     
stimulus_vec = np.repeat([-2., -1.75, -1., -0.75, -0.5,  0.5,  1.,  1.25,  1.75,  2.5 ], 16)                  
data_vec     = np.random.randint(0, 16, size=160)
spi_num      = pd.DataFrame({'trial': trial_vec, 'stimulus': stimulus_vec, 'data': data_vec}).astype('object')
Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
Svenno Nito
  • 635
  • 1
  • 6
  • 22

1 Answers1

4

In order to shift points on a plot, one may use a transform; in this case a ScaledTranslation is useful. Unfortunately, seaborn does not allow to use the transform directly and does not give access to the plotted objects. Therefore one needs to get the plotted object (in this case the PathCollection) from the axes. If the plot to be offset is the first plot in the axes ax, we might simply get it via ax.collections[0]. Then we can set the transform to it via .set_transform.

fig, ax = plt.subplots()
sns.pointplot(... , ax=ax)
#produce transform with 5 points offset in x direction
offset = transforms.ScaledTranslation(5/72., 0, ax.figure.dpi_scale_trans)
trans = ax.collections[0].get_transform()
ax.collections[0].set_transform(trans + offset)

Complete code:

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.transforms as transforms


trial_vec    = np.tile(np.arange(16)+1, 10)     
stimulus_vec = np.repeat([-2., -1.75, -1., -0.75, -0.5,  0.5,  1.,  1.25,  1.75,  2.5 ], 16)
data_vec     = np.random.randint(0, 16, size=160)
spi_num      = pd.DataFrame({'trial': trial_vec, 
                             'stimulus': stimulus_vec, 'data': data_vec})

fig, ax = plt.subplots()

sns.pointplot(x="stimulus", y="data", data=spi_num, linestyles='', scale=1, 
              color='k', errwidth=1.5, capsize=0.2, markers='x', ax=ax)
#produce transform with 5 points offset in x direction
offset = transforms.ScaledTranslation(5/72., 0, ax.figure.dpi_scale_trans)
trans = ax.collections[0].get_transform()
ax.collections[0].set_transform(trans + offset)

sns.swarmplot(x="stimulus", y="data", data=spi_num, edgecolor="black", linewidth=.9, ax=ax)
sns.boxplot(x="stimulus", y="data", data=spi_num, saturation=1, ax=ax)
sns.pointplot(x="stimulus", y="data", data=spi_num, linestyles='--', scale=0.4, 
              color='k', errwidth=0, capsize=0, ax=ax)
plt.ylabel("number of spikes")
plt.title("Median Number of Spikes");

plt.show()

enter image description here

To shift the lineplot as well, you would need to do the same as above with its scatter points (ax.collections[1]) and for all the lines in the plot (ax.lines)

sns.pointplot(x="stimulus", y="data", data=spi_num, linestyles='--', scale=0.4, 
              color='k', errwidth=0, capsize=0, ax=ax, gid="Nm")
# shift points of connecting line:
trans = ax.collections[1].get_transform()
ax.collections[1].set_transform(trans + offset)
# shift everything else:
for line in ax.lines:
    trans = line.get_transform()
    line.set_transform(trans + offset)
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • How do I shift the dotted line with my 'x's? And also the errorbars? So far only my markers are shifted, but that looks weird. – Svenno Nito Sep 30 '17 at 12:11
  • I mean with your code I shifted the 'x' markers. But I cannot shift the dotted line that connects them, neither can I shift the errorbars. – Svenno Nito Sep 30 '17 at 13:04
  • And what I mean is, what have you tried? I've shown you the concept of shifting the markers. You would have to do the same for the lines. If you try that and encouter a problem I can help you solve it. – ImportanceOfBeingErnest Sep 30 '17 at 13:21
  • So I used your code. And in the first pointplot I just inserted linestyles='--'. I removed the second pointplot. So the same command that produces the 'x' markers produces the dotted line. Nevertheless the code only shifts the markers. Why? – Svenno Nito Sep 30 '17 at 14:27
  • The markers are part of a `collection`, hence you shift the `collection`. The errorbars and the line are `lines`, hence you need to shift the `lines`, which you get via `ax.lines`. – ImportanceOfBeingErnest Sep 30 '17 at 14:32
  • Mh, so what would be the code? I tried ax.lines[0].set_transform(trans + offset) but that doesn't do anything. If you edit your answer accordingly I could close the post for good. – Svenno Nito Oct 03 '17 at 13:21