0

I want to make a plot to compare one variable (Fp1) against other 5 ones. How can I make the bars be joined? How can I get rid of the space between them? Is there a way?

The dataframe:

raw_data = {'Max_Acc': [90.71, 87.98, 92.62, 78.93, 73.69, 73.66, 72.29,
                     92.62, 94.17, 92.62, 83.81, 79.76, 74.40, 72.38],
        'Stage': ['AWA', 'Rem', 'S1', 'S2', 'SWS', 'SX', 'ALL',
                  'AWA', 'Rem', 'S1', 'S2', 'SWS', 'SX', 'ALL'],
        'Elec': ['Fp1', 'Fp1', 'Fp1', 'Fp1', 'Fp1', 'Fp1', 'Fp1',
                 'C4', 'T3', 'Fp1', 'P4', 'Fp2', 'Fz', 'Fz']}

df_m=pd.DataFrame(raw_data, columns = ['Max_Acc', 'Stage', 'Elec'])

The code to make the plot:

#Seaborn 
sns.set(style="white")
g = sns.factorplot(x="Stage", y="Mean_Acc", hue='Clf', data=df, size=4, aspect=3, kind="bar",
           legend=False) 

g.set(ylim=(0, 120)) 
g.despine(right=False) 
g.set_xlabels("") 
g.set_ylabels("") 
g.set_yticklabels("") 


mylegend=plt.legend(bbox_to_anchor=(0., 1.1, 1., .102), prop ={'size':10}, loc=10, ncol=8, #left, bottom, width,
            title=r'BEST STAGE AFTER OPTIMIZATION')                                #height, loc='center'
mylegend.get_title().set_fontsize('24') 



ax=g.ax 
def annotateBars(row, ax=ax): 
    for p in ax.patches:
        ax.annotate("%.2f" % p.get_height(), (p.get_x() + p.get_width() / 2., p.get_height()),
             ha='center', va='center', fontsize=11, color='gray', rotation=90, xytext=(0, 20),
             textcoords='offset points')  


plot = df_m.apply(annotateBars, ax=ax, axis=1)

The plot:

enter image description here

EDIT So I made some improvements after reading the answer. But how can I make the colors in the legend to match the ones in the bars? I only have 2 colors, but it should be 6.

enter image description here

EDIT: using the same code form the answer

enter image description here

EDIT:

So I upgraded Matplotlib and added the code to annotate the bars

enter image description here

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
Aizzaac
  • 3,146
  • 8
  • 29
  • 61

2 Answers2

3

The factorplot reserved one position in the bar subgroups for each unique item in the column given to the hue argument. You could therefore introduce a new column with only two different values.

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

raw_data = {'Max_Acc': [90.71, 87.98, 92.62, 78.93, 73.69, 73.66, 72.29,
                     92.62, 94.17, 92.62, 83.81, 79.76, 74.40, 72.38],
        'Stage': ['AWA', 'Rem', 'S1', 'S2', 'SWS', 'SX', 'ALL',
                  'AWA', 'Rem', 'S1', 'S2', 'SWS', 'SX', 'ALL'],
        'Elec': ['Fp1', 'Fp1', 'Fp1', 'Fp1', 'Fp1', 'Fp1', 'Fp1',
                 'C4', 'T3', 'Fp1', 'P4', 'Fp2', 'Fz', 'Fz']}

df_m=pd.DataFrame(raw_data, columns = ['Max_Acc', 'Stage', 'Elec'])
df_m["hue"] = np.arange(0,len(df_m)) // (len(df_m)//2)

g = sns.factorplot(x="Stage", y="Max_Acc", hue="hue", data=df_m, size=4,
                     aspect=3, kind="bar", legend=False)

plt.show()

enter image description here

You would then need to do a lot of tweaking with matplotlib to get the colors back.

At this point you may decide to anyways use a matplotlib bar plot.

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

raw_data = {'Max_Acc': [90.71, 87.98, 92.62, 78.93, 73.69, 73.66, 72.29,
                     92.62, 94.17, 92.62, 83.81, 79.76, 74.40, 72.38],
        'Stage': ['AWA', 'Rem', 'S1', 'S2', 'SWS', 'SX', 'ALL',
                  'AWA', 'Rem', 'S1', 'S2', 'SWS', 'SX', 'ALL'],
        'Elec': ['Fp1', 'Fp1', 'Fp1', 'Fp1', 'Fp1', 'Fp1', 'Fp1',
                 'C4', 'T3', 'Fp1', 'P4', 'Fp2', 'Fz', 'Fz']}

df_m=pd.DataFrame(raw_data)


uelec, uind = np.unique(df_m["Elec"], return_inverse=1)
cmap = plt.cm.get_cmap("Set1")

fig, ax=plt.subplots()
l = len(df_m)
pos = np.arange(0,l) % (l//2) + (np.arange(0,l)//(l//2)-1)*0.4
ax.bar(pos, df_m["Max_Acc"], width=0.4, align="edge", ec="k", color=cmap(uind)  )

handles=[plt.Rectangle((0,0),1,1, color=cmap(i), ec="k") for i in range(len(uelec))]
ax.legend(handles=handles, labels=list(uelec),
           prop ={'size':10}, loc=9, ncol=8, 
            title=r'BEST STAGE AFTER OPTIMIZATION')

ax.set_xticks(range(l//2))
ax.set_xticklabels(df_m["Stage"][:l//2])
ax.set_ylim(0, 120)
plt.show()

enter image description here

To get colors from a seaborn palette, you can use

palette = sns.color_palette()
colors= [ palette[i] for i in uind]

...
ax.bar(..., color=colors)
...

handles=[plt.Rectangle((0,0),1,1, color=palette[i], ec="k") for i in range(len(uelec))]
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • Is there a way to do it in seaborn? Or at least annotate each bar in matplotlib? – Aizzaac May 28 '17 at 14:41
  • 1
    What do you mean by "in seaborn"? Annotations are already in the code from the question. I don't think it's necessary that I repeat that part of the code in the answer. – ImportanceOfBeingErnest May 28 '17 at 14:54
  • The last one. When you say that I should use matplotlib bar plot. I have added the image. – Aizzaac May 28 '17 at 15:55
  • You have an older version of matplotlib. I cannot test this, so you can either try to normalize `color=cmap(uind/float(len(uind)))` or to extend `color=cmap(uind*256//len(uind))`. I would guess one of them works. – ImportanceOfBeingErnest May 28 '17 at 16:01
  • I also updated the answer for how to use a seaborn color palette if that was what you meant by "in seaborn". – ImportanceOfBeingErnest May 28 '17 at 16:11
  • ok. I will try the seaborn code. Also I upgraded matplotlib. Plot has improved, although I still have to work on the size of the title and move the legend. – Aizzaac May 28 '17 at 16:18
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/145312/discussion-between-aizzaac-and-importanceofbeingernest). – Aizzaac May 28 '17 at 16:35
1

This problem occurs only if you don't have value for each group and each hue. You can minimize the gap between bars with dodge=False

When you plot with dodge=False and When you plot without dodge=False If you also want to add bar values you need to set height according to hue. Otherwise it will give an error like "ValueError: posx and posy should be finite values"

for p in ax.patches:
    if float(p.get_height()) > 0:
        height = p.get_height()
        ax.text(p.get_x()+p.get_width()/2., height + 0.1,height ,ha="center")
    else:
        height = .00000001