How to combine groups of columns as a bar plot in pandas

Viewed 159

I have 3 tables

Morning Tennis Score Baseball Score
Jack 10 2
Emma 5 6
Theo 5 8
Noon Tennis Score Baseball Score
Jack 5 4
Emma 8 2
Theo 12 9
Evening Tennis Score Baseball Score
Jack 3 3
Emma 6 7
Theo 8 3

I need to club different columns from the tables and get a graph like this.


dataf = pd.read_csv('score.txt', names=["Tennis","Baseball"])

dataf.plot(kind='bar')

I get a plot as:

Seperate bars

Here the values are in different x-axis index. How do i combine and get it together? I also have to combine the values from different tables and plot in the same plot. The final plot should look like this: Final plot

How can i group columns together and plot it in the same graph?

1 Answers

Given these DataFrames:

m_df = pd.DataFrame({
    'Morning': ['Jack', 'Emma', 'Theo'],
    'Tennis Score': [10, 5, 5],
    'Baseball Score': [2, 6, 8]
})

n_df = pd.DataFrame({
    'Noon': ['Jack', 'Emma', 'Theo'],
    'Tennis Score': [5, 8, 12],
    'Baseball Score': [4, 2, 9]
})

e_df = pd.DataFrame({
    'Evening': ['Jack', 'Emma', 'Theo'],
    'Tennis Score': [3, 6, 8],
    'Baseball Score': [3, 7, 3]
})

They should be concat together and each DataFrame should receive an indicator column. Additionally, the Morning, Noon, and Evening columns should be renamed so that they align in a single column:

keys = ['Morning', 'Noon', 'Evening']
plot_df = pd.concat(
    [df_.assign(id=label)
         .rename(columns={label: 'Player'})
         .set_index('Player')
     for df_, label in zip([m_df, n_df, e_df], keys)]
).reset_index()

plot_df:

  Player  Tennis Score  Baseball Score       id
0   Jack            10               2  Morning
1   Emma             5               6  Morning
2   Theo             5               8  Morning
3   Jack             5               4     Noon
4   Emma             8               2     Noon
5   Theo            12               9     Noon
6   Jack             3               3  Evening
7   Emma             6               7  Evening
8   Theo             8               3  Evening

Then pivot into wide-form from long, reindex to get index in order Morning, Noon, and Evening instead of alphabetical order, swaplevel and sort_index so that columns are grouped by player and not by score type:

plot_df = (
    plot_df.pivot(index='id', columns='Player')
        .reindex(keys)
        .swaplevel(0, 1, 1)
        .sort_index(level=0, axis=1)
        .rename_axis(columns=["Player", 'Score Type'])
)

plot_df:

Player               Emma                        Jack                        Theo             
Score Type Baseball Score Tennis Score Baseball Score Tennis Score Baseball Score Tennis Score
id                                                                                            
Morning                 6            5              2           10              8            5
Noon                    2            8              4            5              9           12
Evening                 7            6              3            3              3            8

This can be simply plotted:

imports:

import pandas as pd
from matplotlib import pyplot as plt

Plotting Code:

fig, ax = plt.subplots()
plot_df.plot(kind='bar', rot=0, ax=ax, xlabel='', ylabel='Score')
plt.tight_layout()
plt.show()

plot 1 (simple plot)


Alternatively update the facecolor and hatch of the patches:

imports:

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

plotting code:

fig, ax = plt.subplots()
plot_df.plot(kind='bar', rot=0, ax=ax, legend=False, xlabel='', ylabel='Score')

players = plot_df.columns.get_level_values(0).unique()
score_types = plot_df.columns.get_level_values(1).unique()
# Create hatches (should be same length as types of scores)
hatches = np.tile(np.repeat(['/', '.'], plot_df.shape[0]), len(players))
# Create Colors (should be same number of colours as number of players)
colours = np.repeat(['green', 'pink', 'purple'],
                    len(score_types) * plot_df.shape[0])
# Iterate over patches, colours, and hatches to set the facecolor and hatch
for patch, colour, hatch in zip(ax.patches, colours, hatches):
    patch.set_facecolor(colour)
    patch.set_hatch(hatch)

# Add legend:
ax.legend(loc=1)
plt.tight_layout()
plt.show()

plot 2 hatched and colors applied


Or even further with custom legends:

imports:

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from matplotlib.lines import Line2D
from matplotlib.patches import Patch

plotting code:

fig, ax = plt.subplots()
plot_df.plot(kind='bar', rot=0, ax=ax, legend=False, xlabel='', ylabel='Score')

players = plot_df.columns.get_level_values(0).unique()
score_types = plot_df.columns.get_level_values(1).unique()
colours = ['green', 'pink', 'purple']
hatches = ['/', '.']
iter_colours = np.repeat(colours, len(score_types) * plot_df.shape[0])
iter_hatches = np.tile(np.repeat(hatches, plot_df.shape[0]), len(players))
for patch, colour, hatch in zip(ax.patches, iter_colours, iter_hatches):
    patch.set_facecolor(colour)
    patch.set_hatch(hatch)

# Add legends:
player_legend = ax.legend(
    [Line2D([0], [0], color=colour, lw=4) for colour in colours],
    players, title='Players', loc=1)

score_legend = ax.legend(
    [Patch(hatch=hatch, facecolor='white') for hatch in hatches],
    score_types, loc=2, title='Score Type', labelspacing=.65)

for patch in score_legend.get_patches():
    patch.set_height(14)
    patch.set_y(-3)

ax.add_artist(player_legend)
plt.tight_layout()
plt.show()

plot 3 custom legends


Complete Working Example with 5 players from import to plt.show():

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

m_df = pd.DataFrame({
    'Morning': ['Jack', 'Emma', 'Theo', 'Matt', 'Thomas'],
    'Tennis Score': [10, 5, 5, 7, 9],
    'Baseball Score': [2, 6, 8, 2, 4]
})

n_df = pd.DataFrame({
    'Noon': ['Jack', 'Emma', 'Theo', 'Matt', 'Thomas'],
    'Tennis Score': [5, 8, 12, 3, 10],
    'Baseball Score': [4, 2, 9, 5, 6]
})

e_df = pd.DataFrame({
    'Evening': ['Jack', 'Emma', 'Theo', 'Matt', 'Thomas'],
    'Tennis Score': [3, 6, 8, 4, 7],
    'Baseball Score': [3, 7, 3, 9, 5]
})
# concat
keys = ['Morning', 'Noon', 'Evening']
plot_df = pd.concat(
    [df_.assign(id=label)
         .rename(columns={label: 'Player'})
         .set_index('Player')
     for df_, label in zip([m_df, n_df, e_df], keys)]
).reset_index()
# pivot to wide
plot_df = (
    plot_df.pivot(index='id', columns='Player')
        .reindex(keys)
        .swaplevel(0, 1, 1)
        .sort_index(level=0, axis=1)
        .rename_axis(columns=["Player", 'Score Type'])
)

fig, ax = plt.subplots()
plot_df.plot(kind='bar', rot=0, ax=ax, legend=False, xlabel='', ylabel='Score')

players = plot_df.columns.get_level_values(0).unique()
score_types = plot_df.columns.get_level_values(1).unique()
hatches = np.tile(np.repeat(['/', '.'], plot_df.shape[0]), len(players))
colours = np.repeat(['red', 'green', 'blue', 'orange', 'pink'],
                    len(score_types) * plot_df.shape[0])

for patch, colour, hatch in zip(ax.patches, colours, hatches):
    patch.set_facecolor(colour)
    patch.set_hatch(hatch)

ax.legend(loc=1)
plt.tight_layout()
plt.show()

plot 4 complete

Related