0

I have an image that I've defined using the following:

import matplotlib as mpl
import numpy as np
import pandas as pd

d = {'ind': [15041, 15149, 15150, 15158], '1': [.0051, .0076, .0051, .0151], '2':[np.NaN, .0079, .0053, .0134], 
     '3':[np.NaN, .0085, .0056, .0135], '4':[np.NaN, .0088, .0058, .0111], '5':[np.NaN, .008907, .0057, .01011], 
     '6':[np.NaN, np.NaN, np.NaN, .0098], '7':[np.NaN, np.NaN, np.NaN, .0076], '8':[np.NaN, np.NaN, np.NaN, .0057]}
data = pd.DataFrame(data=d).set_index('ind')
eccs = mpl.colors.ListedColormap(['navy', 'firebrick', 'gold', 'darkgreen', 'darkorange', 'darkcyan', 'purple', 'saddlebrown'], N = 8)
fig = data.plot.bar(stacked = False, figsize=(6,6), width = 0.88, zorder = 3, align = 'center', cmap = eccs)

It looks like this:

Plot Output

How do I "autofit" the bar widths? E.g. make the width of the bar from the first grouping (15041) the same as the width of the 8 bars from the last (15158). I'd like to remove the white space from this plot in a programatic way, similar to how geom_bar() in ggplot2 automatically changes the widths of the bars.

ALollz
  • 57,915
  • 7
  • 66
  • 89

1 Answers1

0

The following would be a function to display grouped bars in a way that their cumulative width is the same, independent of the number of bars per group.
This is accomplished by calculating the needed width of each bar and the shifted position as a function of the number of nan elements in the input dataframe.

The format of the dataframe is interpreted the same way that pandas does, i.e. rows are groups along the x axis and columns are categories, shown per group.

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

def groupedbarplot(df, width=0.8, ax=None, **kw):
    ax = ax or plt.gca()

    widths = np.ones_like(df.values)*np.nan
    shifts = np.ones_like(df.values)*np.nan

    for i in range(len(df)):
        n = sum(~np.isnan(df.values[i,:]))
        w = 1./n
        pos = (np.linspace(w/2., 1-w/2., n)-0.5)*width
        widths[i,:] = np.ones_like(df.values[i,:])*w*width
        shifts[i,~np.isnan(df.values[i,:])] = pos

    bars = []
    for i, col in enumerate(df.columns):
        bars.append(ax.bar(np.arange(len(df))+shifts[:,i], df[col].values, 
                           width=widths[:,i], **kw))

    ax.set_xticks(np.arange(len(df)))
    ax.set_xticklabels(df.index)
    return bars



d = {'ind': [15041, 15149, 15150, 15158], '1': [.0051, .0076, .0051, np.nan], '2':[np.NaN, .0079, np.nan, .0134], 
     '3':[np.NaN, .0085, np.nan, .0135], '4':[np.NaN, .0088, .0058, .0111], '5':[np.NaN, .008907, 0.005, .01011], 
     '6':[np.NaN, np.NaN, np.NaN, .0098], '7':[np.NaN, np.NaN, np.NaN, .0076], '8':[np.NaN, np.NaN, np.NaN, .0057]}
data = pd.DataFrame(data=d).set_index('ind')

print(data)

groupedbarplot(data)
plt.show()

Print of the dataset used (note that I changes some entries compared to the original one to be able to test/show what happens for intermediate nan values)

            1       2       3       4         5       6       7       8
ind                                                                    
15041  0.0051     NaN     NaN     NaN       NaN     NaN     NaN     NaN
15149  0.0076  0.0079  0.0085  0.0088  0.008907     NaN     NaN     NaN
15150  0.0051     NaN     NaN  0.0058  0.005000     NaN     NaN     NaN
15158     NaN  0.0134  0.0135  0.0111  0.010110  0.0098  0.0076  0.0057

enter image description here

*(Part of the function above is inspired by my previous answer for grouped plots with custom annotations.)

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712