Clustered & Overlapped Bar Charts with Plotly Express | by Darío Weitz | Towards Data Science

Clustered (Grouped) Bar Charts display numerical information about the relative proportion that exists between a main category and its subgroups (subcategories) that belong to a second categorical variable. Similar to Stacked Bar Graphs, they should be used for Comparisons and Proportions but with emphasis on Composition. They are particularly effective when a whole is divided into multiple parts. They enable to make comparisons across subcategories whilst Stacked Bar Graphs make comparisons within subcategories.
Clustered Bar Charts are represented by means of sets of rectangular bars that can be oriented horizontally or vertically. Each principal category is divided into a cluster of bars representing subcategories of the second categorical variable. The quantity of each subcategory is shown by the length or height of those rectangular bars that are located side by side forming a cluster, with gaps between clusters slightly wider than a single standard bar.
Overlapped (Overlapping) Bar Charts are used to make comparisons between different items or categories. They compare only two numerical variables per item or category in a single diagram. The numerical variables must be closely related to merit a comparison. They are also used to show trends over time based on similar premises. They should not be used for Relationship or Distribution analysis. The conceptual idea is to contrast numerical values ​​of two variables that overlapped one over the other allows describing the message with greater expositional power.
We worked with a public dataset downloaded from the community Kaggle [1]. The dataset contains records related to Video Games Sales & Game Ratings Data Scraped from VzCharts. We particularly selected a csv file with 422 records about Video Games sales on the Microsoft Xbox One console. We would like to know how sales were distributed in different regions of the world in the period 2013–2018. We would also like to know which were the best-selling genres.
First, we imported Plotly Express as px, the module plotly.graph_objects as go, the Pandas library as pd and converted our csv file into a dataframe:
import pandas as pdimport plotly.express as pximport plotly.graph_objects as godf = pd.read_csv(path + 'XboxOne_GameSales2.csv', index_col=False,                                header = 0, sep = ';', engine='python')
The screenshot below shows the first ten records of the dataset:
Initially, we eliminated rows with N/A values in the Year column, then we deleted rows with 0 values in the Global column, and finally, we grouped the remaining records based on the Year column and sum each group.
df.dropna(subset = ['Year'], inplace = True)df.drop(df[df['Global'] == 0.0].index, inplace = True)df_clust = df.groupby(['Year']).sum().reset_index()
After these three lines of code, we are ready to draw a clustered bar chart.
For the clustered (grouped) bar chart in this article, the Plotly function is go.Bar() and the corresponding parameters are: x= the name of a column in the dataframe representing the timeline while y= is the name of a column in the dataframe representing a particular region. Also, we set the width of the bars and the text of the legends.
We used the method fig.add_trace() to add new traces (bars) to the figure object Figure(). We repeated the procedure four times, one for each region to be drawn.
anchos = [0.2] * 6fig = go.Figure()fig.add_trace(go.Bar(x = df_clust['Year'],                     y = df_clust['North America'],                      width = anchos, name = 'North America'))fig.add_trace(go.Bar(x = df_clust['Year'],                     y = df_clust['Europe'],                     width = anchos, name = 'Europe'))fig.add_trace(go.Bar(x = df_clust['Year'],                      y = df_clust['Japan'],                     width = anchos, name = 'Japan'))fig.add_trace(go.Bar(x = df_clust['Year'],                      y = df_clust['Rest of World'],                     width = anchos, name = 'Rest of World'))
We updated the chart with update.layout: set the title, the size of the font, and the figure dimensions with width and height.
We must choose barmode = ‘group’ in order to draw a clustered bar chart. Remember that we selected barmode = ‘stack’ for a stacked bar chart.
Then we updated the x-axis and the y-axis (text, font, tickfont). We saved the chart as a static png file and finally, we drew the chart using the default template (plotly).
fig.update_layout(title =  "XboxOne World Sales (MM) ",                  barmode = 'group', title_font_size = 40,                  width = 1600, height = 1400)fig.update_xaxes(title_text = 'Year',         title_font=dict(size=30, family='Verdana', color='black'),         tickfont=dict(family='Calibri', color='darkred', size=25))fig.update_yaxes(title_text = "Sales",         title_font=dict(size=30, family='Verdana', color='black'),          tickfont=dict(family='Calibri', color='darkred', size=25))fig.write_image(path + "figclust1.png")fig.show()
It’s a nice graph but we are unable to clearly establish which values correspond to Japan. Furthermore, the size of the legends is not adequate for a correct visualization.
So we incorporated in each trace the parameter text with the numerical values corresponding to each region. We used the method update_traces() to set the properties (template, position, and font characteristics) of those numerical annotations. Finally, we used the method update_layout(legend=go.layout.Legend() to establish the location on the screen (x,y), the order in which the legend items are displayed (traceorder), and finally, the family, size, and color of the legends. Please note that go.Legend has been deprecated in favor of go.layout.Legend.
anchos = [0.2] * 6fig = go.Figure()fig.add_trace(go.Bar(x = df_clust['Year'],                      y = df_clust['North America'],                     width = anchos, name = 'North America',                     text = df_clust['North America']))fig.add_trace(go.Bar(x = df_clust['Year'],                      y = df_clust['Europe'],                     width = anchos, name = 'Europe',                     text = df_clust['Europe']))fig.add_trace(go.Bar(x = df_clust['Year'],                      y = df_clust['Japan'],                     width = anchos, name = 'Japan',                     text = df_clust['Japan']))fig.add_trace(go.Bar(x = df_clust['Year'],                      y = df_clust['Rest of World'],                     width = anchos, name = 'Rest of World',                     text = df_clust['Rest of World']))fig.update_layout(title =  "XboxOne World Sales (MM) ",                  barmode = 'group',title_font_size = 40,                  width = 1600, height = 1400)fig.update_layout(legend=go.layout.Legend(            x=1,y=1,            traceorder= normal",            font=dict(family="Verdana",size= 22, color = "black")))fig.update_traces(texttemplate='%{text:.2f}',textposition='outside',            textfont=dict(size=60, family='Verdana', color='black'))fig.update_xaxes(title_text = 'Year',           title_font=dict(size=30,family='Verdana',color='black'),            tickfont=dict(family='Calibri', color='darkred',size=25))fig.update_yaxes(title_text = "Sales",            title_font=dict(size=30,family='Verdana',color='black'),            tickfont=dict(family='Calibri', color='darkred',size=25))         fig.write_image(path + "figclust1.png")fig.show()
Now we can make a good comparison, not only visually but also in numbers.
To improve the storytelling, we decided to analyze sales by genre in the two top-selling regions:
First, we grouped by genre and sum the columns corresponding to North America and Europe. As we knew from the previous chart that North America was the top-selling region, we sorted the column [“North America”] inplace and in descending order. We obtained 16 different genres, too many for proper visualization. Therefore, we filtered out only those whose sales in the period 2013–2018 exceeded five million copies.
df_genre = df.groupby(['Genre'], as_index = False)                       ["North America", 'Europe'].sum()df_genre.sort_values(by=['North America'], inplace = True,                      ascending = False)df_genre = df_genre[df_genre['North America'] > 5]
We chose barmode = ‘overlay’ to get an overlapped bar chart since we have only two numerical variables per category.
anchos = 0.5fig = go.Figure()fig.add_trace(go.Bar(x = df_genre['Genre'],                      y = df_genre['North America'],                     width = anchos, name = 'North America',                     text  = df_genre['North America']))fig.add_trace(go.Bar(x = df_genre['Genre'],                     y = df_genre['Europe'],                     width = anchos, name = 'Europe',                     text = df_genre['Europe']))fig.update_layout(title = "XboxOne Sales by Genre (MM)",                  barmode = 'overlay',title_font_size = 40,                  width = 1600, height = 1400)fig.update_layout(legend=go.layout.Legend(             x=1,y=1, traceorder= "normal",             font=dict(family="Verdana",size= 22, color = "black")))fig.update_xaxes(          title_text = 'Genre',           title_font=dict(size=30,family='Verdana',color='black'),          tickfont=dict(family='Calibri', color='darkred', size=25))fig.update_yaxes(          title_text = "Sales",          title_font=dict(size=30, family='Verdana', color='black'),          tickfont=dict(family='Calibri', color='darkred', size=25))fig.write_image(path + "figover1.png")fig.show()
As expected, the shooter genre is the favorite of North Americans, with sales double that of its immediate successor. Proportionally, the Europeans only match them in the racing genre.
We can improve the chart with a minor change: set the offset property to 0.0005 in the second bar. This property shifts the position where the bar is drawn (in position axis units).
fig.add_trace(go.Bar(x = df_genre['Genre'],                      y = df_genre['Europe'],offset = 0.0005,                     width = anchos, name = 'Europe',                     text= df_genre['Europe']))
Graphs such as those shown in figure 4 are called Partially Overlapped Bar Charts. Conceptually, they are equivalent to clustered (grouped) bar charts when the rectangles representing the different data sets begin to overlap instead of being located side by side. Overlapping bars imply the extreme case where a rectangle overlaps 100% ahead of another rectangle. Undoubtedly, audiences will find it very difficult to make comparisons with three or more partially overlapping bars. Besides, overlapped and partially overlapped bar charts only display a good message if longer bars are always behind shorter ones.
You might use a clustered bar graph when you want to make direct comparisons across parts of a whole. On the other hand, overlapped bar graphs enable to do excellent comparisons between two closely related numerical variables.
If you find this article of interest, please read my previous (https://medium.com/@dar.wtz):
Area Charts with Plotly Express, Traces & Layout
towardsdatascience.com
Scatter Plots with Plotly Express, Trendlines & Faceting