pythonpandasmatplotlibmatplotlib-3dbar3d

How can I plot a pandas multiindex dataframe as 3d


I have a dataframe df grouped like this:

Year    Product Sales
2010        A   111
            B   20
            C   150
2011        A   10
            B   28
            C   190
            …   …

and I would like to plot this in matplotlib as 3d Chart having the Year as the x-axis, Sales on the y-axis and Product on the z-axis. enter image description here

I have been trying the following:

from mpl_toolkits.mplot3d import axes3d
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X = dfgrouped['Year']
Y = dfgrouped['Sales']
Z = dfgrouped['Product']
ax.bar(X, Y, Z, color=cs, alpha=0.8)

unfortunately I am getting

"ValueError: incompatible sizes: argument 'height' must be length 7 or scalar"


Solution

  • You could plot a 3D Bar graph using Pandas as shown:

    Setup:

    arrays = [[2010, 2010, 2010, 2011, 2011, 2011],['A', 'B', 'C', 'A', 'B', 'C']]
    tuples = list(zip(*arrays))
    index = pd.MultiIndex.from_tuples(tuples, names=['Year', 'Product'])         
    
    df = pd.DataFrame({'Sales': [111, 20, 150, 10, 28, 190]}, index=index)
    print (df)
    
                  Sales
    Year Product       
    2010 A          111
         B           20
         C          150
    2011 A           10
         B           28
         C          190
    

    Data Wrangling:

    import numpy as np
    import pandas as pd
    from mpl_toolkits.mplot3d import axes3d
    import matplotlib.pyplot as plt
    
    # Set plotting style
    plt.style.use('seaborn-white')
    

    Grouping similar entries (get_group) occuring in the Sales column and iterating through them and later appending them to a list. This gets stacked horizontally using np.hstack which forms the z dimension of the 3d plot.

    L = []
    for i, group in df.groupby(level=1)['Sales']:
        L.append(group.values)
    z = np.hstack(L).ravel()
    

    Letting the labels on both the x and y dimensions take unique values of the respective levels of the Multi-Index Dataframe. The x and y dimensions then take the range of these values.

    xlabels = df.index.get_level_values('Year').unique()
    ylabels = df.index.get_level_values('Product').unique()
    x = np.arange(xlabels.shape[0])
    y = np.arange(ylabels.shape[0])
    

    Returning coordinate matrices from coordinate vectors using np.meshgrid

    x_M, y_M = np.meshgrid(x, y, copy=False)
    

    3-D plotting:

    fig = plt.figure(figsize=(10, 10))
    ax = fig.add_subplot(111, projection='3d')
    
    # Making the intervals in the axes match with their respective entries
    ax.w_xaxis.set_ticks(x + 0.5/2.)
    ax.w_yaxis.set_ticks(y + 0.5/2.)
    
    # Renaming the ticks as they were before
    ax.w_xaxis.set_ticklabels(xlabels)
    ax.w_yaxis.set_ticklabels(ylabels)
    
    # Labeling the 3 dimensions
    ax.set_xlabel('Year')
    ax.set_ylabel('Product')
    ax.set_zlabel('Sales')
    
    # Choosing the range of values to be extended in the set colormap
    values = np.linspace(0.2, 1., x_M.ravel().shape[0])
    
    # Selecting an appropriate colormap
    colors = plt.cm.Spectral(values)
    ax.bar3d(x_M.ravel(), y_M.ravel(), z*0, dx=0.5, dy=0.5, dz=z, color=colors)
    plt.show()
    

    Image


    Note:

    Incase of unbalanced groupby objects, you could still do it by unstacking and filling Nans with 0's and then stacking it back as follows:

    df = df_multi_index.unstack().fillna(0).stack()
    

    where df_multi_index.unstack is your original multi-index dataframe.

    For the new values added to the Multi-index Dataframe, following plot is obtained:

    Image2