How do I plot JUST the mean and stdev of data in seaborn?

Viewed 5665

I cannot for the life of me find a similar question to this, and I have been pulling my hair out trying to figure out how to do this. It seems like it should be a simple thing!

The setup: I have some X vs Y data grouped into bins, and each bin contains X and Y data points. For each bin, I would like to plot the mean of X vs mean of Y along with their respective stdevs, and most importantly: color code each bin using the Seaborn "colorblind" palette (this is mandatory).

What I've tried: Everything under the sun. Lineplot, scatterplot, catplot, plotpoints. And when none of those were working, I tried to use matplotlib's "errorbars" but I apparently can't seem to export Seaborn's "colorblind" palette to matplotlib so that was a bust too.

Some dummy code:

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

some_data = pd.DataFrame({'X':[9,10,11,12,39,40,41,42], 'Y':[99,100,110,111,499,500,510,511], 'Bin':[1,1,1,1,2,2,2,2]})

Results of some tries:

sns.pointplot(x="X", y="Y", data=some_data, legend='full', hue='Bin')

pointplot Scatterplot completely screws up the x-axis scale, so that's another issue that I haven't been able to work around.

sns.lineplot(x="X", y="Y", data=some_data, legend='full', hue='Bin', err_style="band", estimator="mean", ci='sd')

lineplot Better but it's just drawing a line between the points instead of calculating the mean and stdev, which, I thought it would do when I specify an estimator and confidence interval method!!!!!.

sns.scatterplot(x="X", y="Y", data=some_data, legend='full', hue='Bin')

Scatterplot is fine, but it doesn't possess estimator functionality so I'm literally just plotting the raw data.

scatterplot

I'm just completely lost on what to do. I've been at this all night. It's 4:30 AM and I've barely slept for the past few nights. Any help would be appreciated!

2 Answers

The following approach draws an ellipse using the mean and sdevs:

import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
import pandas as pd
import seaborn as sns

df = pd.DataFrame({'X':[9,10,11,12,39,40,41,42], 'Y':[99,100,110,111,499,500,510,511], 'Bin':[1,1,1,1,2,2,2,2]})
means = df.groupby('Bin').mean()
sdevs = df.groupby('Bin').std()

fig, ax = plt.subplots()
colors = ['crimson', 'dodgerblue']
sns.scatterplot(x='X', y='Y', hue='Bin', palette=colors, data=df, ax=ax)
sns.scatterplot(x='X', y='Y', data=means, color='limegreen', label='means', ax=ax)

for (_, mean), (_, sdev), color in zip(means.iterrows(), sdevs.iterrows(), colors):
    ellipse = Ellipse((mean['X'], mean['Y']), width=2*sdev['X'], height=2*sdev['Y'],
                          facecolor=color, alpha=0.3)
    ax.add_patch(ellipse)
plt.show()

example plot

Here is a more elaborate example, showing ellipses for 1, 2 and 3 times the sdev.

import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
import pandas as pd
import numpy as np
import seaborn as sns

K = 5
N = 100
df = pd.DataFrame({'X': np.random.normal(np.tile(np.random.uniform(10, 40, K), N), np.tile([3, 4, 7, 9, 10], N)),
                   'Y': np.random.normal(np.tile(np.random.uniform(90, 500, K), N), np.tile([20, 25, 8, 22, 18], N)),
                   'Bin': np.tile(np.arange(1, K + 1), N)})
means = df.groupby('Bin').mean()
sdevs = df.groupby('Bin').std()

fig, axes = plt.subplots(ncols=2, figsize=(12, 4))
colors = ['crimson', 'dodgerblue', 'limegreen', 'turquoise', 'gold']
for ax in axes:
    sns.scatterplot(x='X', y='Y', hue='Bin', palette=colors, s=5, ec='none', data=df, ax=ax)
    sns.scatterplot(x='X', y='Y', marker='o', s=50, fc='none', ec='black', label='means', data=means, ax=ax)
    if ax == axes[1]:
        for (_, mean), (_, sdev), color in zip(means.iterrows(), sdevs.iterrows(), colors):
            for sdev_mult in [1, 2, 3]:
                ellipse = Ellipse((mean['X'], mean['Y']), width=2 * sdev['X'] * sdev_mult,
                                  height=2 * sdev['Y'] * sdev_mult,
                                  facecolor=color, alpha=0.2 if sdev_mult == 1 else 0.1)
                ax.add_patch(ellipse)
plt.show()

multiples of sdev

I acknowledge this is not the full answer - but I hope it will help with the data stats and give you some direction with the plot. I'm not terribly good with matplot/seaborn, so to get this over to you, I've quickly written the graph in plotly. I hope it at least provides some direction for you ...

Mean / Std:

import pandas as pd
from plotly.offline import iplot

x = [9, 10, 11, 12, 39, 40, 41, 42]
y = [99, 100, 110, 111, 499, 500, 510, 511]
b = [1, 1, 1, 1, 2, 2, 2, 2]

df = pd.DataFrame({'x': x, 'y': y, 'bin': b})
df = df.groupby(['bin']).agg(['mean', 'std'])
df.columns = ['_'.join(c).rstrip('_') for c in df.columns.to_list()]
df.reset_index(inplace=True)

Output:

    bin x_mean  x_std       y_mean  y_std
0   1   10.5    1.290994    105     6.377042
1   2   40.5    1.290994    505     6.377042

Plotting:

data = []
for row in df.itertuples():
    data.append({'x': [row.x_mean],
                 'y': [row.y_mean],
                 'mode': 'markers',
                 'name': '{} mean'.format(row.bin),
                 'marker': {'size': 25}})
    data.append({'x': [row.x_std],
                 'y': [row.y_std],
                 'mode': 'markers',
                 'name': '{} std'.format(row.bin),
                 'marker': {'size': 25}})
iplot({'data': data})

Output:

Note that as the stds are the same, the red/purple dots overlay each other.

enter image description here

I hope this helps a bit ...

Related