Marimekko Charts with Plotly Express | by Darío Weitz | Towards Data Science

Suppose you are involved in a business plan competition. You need to present a compelling business case because investors make their decisions based on your storytelling.
You must include visually interesting graphics and images. But this is where we can make a double mistake: 1) that the audience will see our figures and immediately understand the idea we are showing; 2) that the audience, even in the professional environment, can receive an important sequence of complex visualizations and in the act grasp trends, patterns, correlations, proportions, distributions in the way we have selected to do it.
Remember: data visualization is a tool for communication. So, we must always ask ourselves: am I communicating clearly? Am I using a type of figure that leaves no room for doubt? These questions are particularly important if we are using variable width bar charts.
There are different types of variable width bar charts but two are the most popular: 1) Bar Mekko chart; 2) Marimekko chart (Marimekko is a Finnish textile brand that has large and varied shape patterns, as well as simple and bright colors and styles).
They differ in the following: a Bar Mekko chart (BMc) is like a “standard” bar chart, but its bars are of variable width; a Marimekko chart (Mc) is like a 100% stacked bar chart but its bars are of variable width, it is a normalized column graph with variable bar widths.
Both charts are used to show two numerical variables for each category present in the data set; the goal is to make a comparison between the categories, but not between the numerical variables. They are widely used in Dashboards or in Marketing, Sales, and Business Presentations.
The vertical axis has a numerical scale (100% on a Marimekko Chart) and represents one of the quantitative variables. The horizontal axis can be either numerical or categorical. If it is numerical, each rectangle has a width proportional to the value of the second quantitative variable, a different color, and a legend that identifies it. If it is categorical, the width of each bar also indicates the value of the second quantitative variable.
Unlike the standard bar chart, no space is left between the bars (Be careful not to confuse Mekko charts with Histograms.) In both, BMc and Mc, the entire width of the horizontal axis is occupied.
The Mekko Bar chart is an alternative to classic bar charts allowing you to reduce the number of charts in a business presentation. To achieve this, the chart encodes one of the numerical variables by the height of the bars and the other quantitative variable by the width of the bars.
The Marimekko Chart is an alternative to clustered bar charts (grouped bar charts) allowing to reduce their number in a business presentation: they show numerical information related to subgroups or subcategories within the main categories.
Do not confuse Mcs with spineplots. In its most rigorous definition, a spineplot is a one-dimensional, horizontal stacked bar graph used to display frequencies, proportions, or percentages of two cross-classified categorical variables in contingency tables. The confusion arises because some spineplots visualization tools allow a vertical orientation, which they call mosaic plots. This term is also erroneously attributed to the Marimekko Charts but should be reserved for those charts that allow examining the relationship between two or more categorical variables through variable-width rectangles.
Plotly, a computing company headquartered in Montreal, Canada, developed plotly.py, an interactive, open-source visualization tool for Python. In 2019, the company released Plotly 4.0 which includes Plotly Express, a high-level wrapper fully compatible with the rest of the Plotly ecosystem.
Plotly provides a group of classes called graph objects that may be used to construct figures. The plotly.graph_objects module contains a hierarchy of Python classes. Figure is a primary class. Figure has a data attribute and a layout attribute. The data attribute has more than 40 objects, each one refers to a specific type of chart (trace) with its corresponding parameters. The layout attribute specifies the properties of the figure as a whole (axes, title, shapes, legends, etc.).
We worked with a dataset downloaded from Kaggle. The dataset contains records related to Video Games Sales & Game Ratings Data Scraped from VzCharts. We particularly selected three csv files: 1) 1031 records about Video Games sales on the Playstation 4 platform by Sony [1]; 2) 422 records about Video Games sales on the Microsoft Xbox One console [2]; 3) 11563 records about Video Games sales from different publishers [3]. We would like to know how sales of the different platforms were distributed in several regions of the planet.
First, we imported Plotly Express as px, the module plotly.graph_objects as go, and the libraries Numpy & Pandas as np and pd respectively.
import plotly.graph_objects as goimport plotly.express as pximport numpy  as npimport pandas as pd
Then we used the read_csv function to read the comma-separated values (csv) files into their corresponding dataframes.
df1 = pd.read_csv(path + 'XboxOne_GameSales2.csv',                   index_col =False, header = 0,                   sep = ';',   engine='python')df2 = pd.read_csv(path + 'PS4_GamesSales2.csv',                   index_col = False, header = 0,                  sep = ';', engine='python')df3 = pd.read_csv(path + 'Video_Games_Sales.csv',                   index_col = False, header = 0,                  sep = ';', engine='python')
The screenshot below shows the first ten records of the dataset named df1 (corresponding to XboxOne):
The dataset named df2 has the same number and names of columns as df1, but df3 (Video_Games_Sales) has a very different layout:
For our computations we only need the columns that store the sales in the four regions of interest (North America, Europe, Japan, & Rest of World). In df1 and df2 we simply select the corresponding four columns, but df3 requires pre-processing to equalize the data. The pre-processing task consists of only selecting the Nintendo sales and renaming the four selected columns:
df1 = df1[['North America', 'Europe', 'Japan', 'Rest of World']]df2 = df2[['North America', 'Europe', 'Japan', 'Rest of World']]df3 = df3.drop(df3[(df3.Publisher != 'Nintendo')].index)df3 = df3[['NA_Sales', 'EU_Sales', 'JP_Sales', 'Other_Sales']]df3 = df3.rename(columns={'NA_Sales': 'North America',                           'EU_Sales': 'Europe',                          'JP_Sales': 'Japan',                          'Other_Sales' :'Rest of World'})
Then we applied the function sum() to the three dataframes. Remember that the function sum() returns the sum of the values for the requested axis.
Again, df3 requires a pre-processing because before applying the function sum(), we must convert column data stored as object into numbers using pd.to_numeric, errors=’coerce’. Option errors=’coerce’ converts non numeric values to NaN.
Finally, we concatenated the three files and calculated the respective percentages in a new dataframe named df4.
sum_1 = df1.sum(axis = 0, skipna = True)sum_2 = df2.sum(axis = 0, skipna = True)df3 = df3.apply(pd.to_numeric, errors='coerce')sum_3 = df3.sum(axis = 0, skipna = True)list_df = [sum_1, sum_2, sum_3]df4 = pd.concat(list_df , axis = 1).reset_index()df4.columns = ['region', 'xbox', 'ps4', 'nint']df4['perc_xbox'] = (df4['xbox'] / ( df4['xbox'] + df4['ps4'] +                     df4['nint'])) * 100df4['perc_ps4']  = (df4['ps4']  / ( df4['xbox'] + df4['ps4'] +                     df4['nint'])) * 100df4['perc_nint'] = (df4['nint'] / ( df4['xbox'] + df4['ps4'] +                     df4['nint'])) * 100
In order to draw a variable width bar chart, we used a dictionary named data for the stack ordering, another dictionary named colors for the bar colors and one Series (widths) and one variable (width_x) to assign the width of the bars on the horizontal axis:
data = {"Nintendo": df4['perc_nint'],        "Xbox"    : df4['perc_xbox'],        "PS4"     : df4['perc_ps4']        }colors= {"Nintendo": 'blue',         "Xbox"    : 'green',         "PS4"     : 'darkred'        }widths  = sum_3x_width = sum_3.sum()
The conceptual idea with plotly.graph_objects is to use .add_trace(go.Bar()) to create the figure and then add methods such as .update_layout(), .update_xaxes, .update_yaxes to manipulate the figure. Finally, we export the figure with .write_image() and render it with .show().
Note that add_trace(go.Bar()) is inside a for loop according to the order we decided in the dictionary named data.
We updated the chart with update.layout: set the title text, title font, and legend font, and set the figure dimensions (width and height). barmode = ‘stack’ determines that bars are stacked on top of one another.
Then we updated the x-axis and the y-axis (text, font, tickfont). Set the y-axis range between 0 and 100% and the x-axis range between 0 and x_width.
fig = go.Figure()for key in data:fig.add_trace(go.Bar(name = key,                     y = data[key],                     x = np.cumsum(widths) - widths,                     width = widths, offset = 0,                     marker = {'color' : colors[key]},                     customdata = data[key],                     texttemplate = "%{y:.2f}",                     textposition = "inside",                     textangle = 0,textfont_color = "white",                     textfont_size = 30))fig.update_layout(title_text ="Sales of Consoles per Region",                  barmode = "stack",                  title_font_size  = 40,legend_font_size = 30,                  width = 1400, height = 1400)fig.update_xaxes(title_text=                 'Width of each bar=Sales of Nintendo in MM',                 range = [0, x_width],                   title_font_size = 30,                                    tickvals = np.cumsum(widths)-widths/2,                 ticktext =["%s<br>%d"% l, w)for l,w                             in zip(labels, widths)],                 tickfont = dict(family='Calibri',                                 color='black', size=25))fig.update_yaxes(title_text = 'Percentage (%)',                 range = [0,100],                 title_font=dict(size=35,                                 family='Verdana', color='darkred'))fig.write_image(path + "figmarimekko1.png")fig.show()
Figure 1 describes in a single chart the accumulated sales of Nintendo video games separated into different regions and the percentages corresponding to the different consoles. The elements that characterize a Marimekko chart can be seen: a rectangular area divided into smaller rectangles of varying width; vertically stacked rectangles; a horizontal axis that occupies the entire width of the chart; a vertical axis with a percentage scale; total sales per specific brand (Nintendo) on the lower baseline; different bar widths that allow calculating the relative contributions of each region to the total sales.
But the choice of the stacking order was completely arbitrary. So, what happens if we change that order? We just have to make a few simple changes to the code:
data = {"Xbox"    : df4['perc_xbox'],        "PS4"     : df4['perc_ps4'],        "Nintendo": df4['perc_nint']        }widths  = sum_1x_width = sum_1.sum()
In Figure 2, the different widths of the rectangles are given by the different XboxOne sales in the regions of interest. Although the sales percentages remain unchanged between the different consoles, the audience receives a very different message, particularly in the case of Japan.
The same is true if the horizontal axis is segmented by PS4 console sales:
data = {"PS4"     : df4['perc_ps4'],        "Nintendo": df4['perc_nint'],        "Xbox"    : df4['perc_xbox']        }widths  = sum_2x_width = sum_2.sum()
This is the main drawback with Marimekko charts: not only are they difficult to read and interpret because they are based on audiences’ ability to decode numerical information by comparing areas, but they can also mislead the audience to wrong conclusions.
As Alberto Cairo wisely pointed out [4]: “Just as we can lie with statistics, we can also lie with charts”
If you find this article of interest, please read my previous (https://medium.com/@dar.wtz):
“Scatter Plots with Plotly Express, Trendlines & Faceting”
towardsdatascience.com
“Histograms with Plotly Express, Themes & Templates”