Comparing Different Ways of Displaying Categorical Data in Python | by Elena Kosourova | Towards Data Science

While the most popular way of representing categorical data is using a bar plot, there are some other visualization types suitable for this purpose. They all have their pros and cons, as well as limits of their applicability.
In this article, we’ll compare such graphs displaying the same data: the continents by area. The data was obtained from Wikipedia and are represented in mln km2, to avoid unnecessary precision.
import pandas as pddct = {'Oceania': 8.5, 'Europe': 10, 'Antarctica': 14.2, 'South America': 17.8,        'North America': 24.2, 'Africa': 30.4, 'Asia': 44.6}continents = list(dct.keys())populations = list(dct.values())
Let’s start with the most classical way of displaying categorical data: a bar plot that doesn’t even need an introduction.
import matplotlib.pyplot as pltimport seaborn as snsplt.figure(figsize=(10,4))plt.barh(continents, populations, color='slateblue')plt.title('Continents by area', fontsize=27)plt.xlabel('Area, mln km2', fontsize=19)plt.xticks(fontsize=16)plt.yticks(fontsize=16)plt.tick_params(left=False)sns.despine(left=True)plt.show()
From the graph above, we clearly see the hierarchy of the continents by area both qualitatively and quantitatively. No wonder: bar plots hardly have any cons. They are multi-purpose, highly customizable, visually compelling, easy to interpret, familiar to a wide audience, and can be created with any dataviz library. The only thing we have to keep in mind when generating them is to follow good practices: data ordering, selecting appropriate colors, bar orientation, adding annotations, labels, decluttering, etc.
A stem plot is very similar to a bar plot and even has an advantage over the latter since it’s characterized by a maximized data-ink ratio and looks less cluttered. To create a stem plot, we only need the matplotlib library. For a horizontal stem plot (the one with a horizontal baseline and vertical stems), we can use either vlines() in the combination with plot() or directly the stem() function. In the first case, vlines() creates the stems and plot() – the ending points. For a vertical stem plot (the one with a vertical baseline and horizontal stems), we can't use anymore the stem() function but the combination of hlines() and plot().
Let’s create a vertical stem plot for our data:
plt.figure(figsize=(10,4))plt.hlines(y=continents, xmin=0, xmax=populations, color='slateblue')plt.plot(populations, continents, 'o', color='slateblue')plt.title('Continents by area', fontsize=27)plt.xlabel('Area, mln km2', fontsize=19)plt.xticks(fontsize=16)plt.yticks(fontsize=16)plt.xlim(0, None)plt.tick_params(left=False)sns.despine(left=True)plt.show()
Compared to the first chart, we added only one more line of code, and in the case of a horizontal stem plot, it wouldn’t be even necessary if using directly the stem() function. The resulting graph shows the same information but looks cleaner and more elegant than a classical bar plot. This becomes especially valuable when there are many categories to visualize or when their values are comparable.
A pie chart or its version donut chart (a pie chart with an empty core part) is another well-known visualization type widely used for displaying the proportions of individual components of the whole. It can be created and easily customized with many dataviz libraries. Despite being so popular, it’s also one of the most criticized types of plots. The main reasons for it are:
A pie chart is based on angles rather than lengths, which makes it more difficult to be clearly interpreted.
In the case when there are many categories or when their proportions are similar (which would be an ideal candidate for creating a stem plot), a pie chart becomes practically useless for figuring out the order of the proportions or even finding the biggest/smallest value.
Let’s see if a pie chart works well in our case:
plt.figure(figsize=(7,7))plt.pie(populations, labels=continents, startangle=90, autopct='%1.0f%%',         textprops={'fontsize': 15})plt.text(x=-1.15, y=1.5, s='Continents by area, in %', fontsize=27)plt.show()
Since our data consists only of 7 categories, with the values initially ordered and rather various among themselves, and also because we added the percentage of the overall land for each continent, the resulting chart looks informative, comprehensible, and doesn’t encounter the 2 issues mentioned above.
One not-so-obvious weak point of pie charts with respect to bar and stem plots is the abundance of colors. An alternative approach is to fill the wedges with a pattern, but in both cases, it results in a lower data-ink ratio. However, this is a typical issue of all “colorful” plots, as we’ll see soon.
Another thing worth emphasizing is that this type of charts has a more limited functionality than the previous two. Indeed, it can be used only for visualizing proportions of the components in the whole, while for bar and stem plots, the bars/stems are not supposed to constitute the whole. For example, we can make a bar plot for the TOP10 countries by population. Creating a pie chart for this data, being technically possible, would be a bit faulty since the real whole, in this case, includes much more elements (all the other countries).
To create a treemap, we have to install and import the squarify library (pip install squarify). This type of plots is similar to a pie chart in terms that it also displays proportions of individual components of the whole. Instead of wedges, it uses a set of rectangles, the areas of which are proportional to the values of the corresponding categories. This makes a difference and is actually an advantage: mental comparison of areas is certainly much easier than that of angles. Also, unlike a pie chart, it still works well in the case when we have a lot of categories to compare. Below are the weak sides and limitations that a treemap has in common with a pie chart:
It doesn’t work well when the proportions of the categories are similar.
It doesn’t have axes, so we have to rely on a visual comparison of the rectangles or to add the values on each of them.
It implies using a lot of colors which can make the resulting graph look a bit overwhelming.
The components of a treemap are supposed to constitute the whole.
Besides, there is a purely treemap-specific drawback:
The ways of customization of such visualizations are rather limited and not always user-friendly. For example, to tune the label text properties (such as font color or size), we can’t pass in the corresponding arguments directly but only through the text_kwargs dictionary. Also, the pad parameter that adds a gap between the rectangles can only take values True or False, without any possibility to adjust the width of the gap.
Let’s take a look at a treemap for our continents:
import squarifyimport matplotlibplt.figure(figsize=(12,7))squarify.plot(sizes=populations, label=continents, value=populations,              color=[matplotlib.cm.Set2(i) for i in range(7)],              text_kwargs={'fontsize': 17})plt.title('Continents by area, mln km2', fontsize=27)plt.axis('off')plt.show()
Our treemap looks engaging and insightful, we can clearly trace the continents from the biggest to smallest one looking both on the rectangles and values. Again, this is partially due to the fact that we have only 7 categories, a wide range of values, and initial ordering. Probably, though, we could consider putting the values in % rather than in absolute values.
The only necessary parameter for a treemap is sizes, all the others (including label) are optional.
To create a waffle chart, we need a matplotlib-based PyWaffle library: pip install pywaffle, then import both PyWaffle and matplotlib. This type of plots is similar to both pie charts and treemaps since it also illustrates proportions of individual components of the whole, with the advantage that it additionally shows the number of units of each component. Depending on the context, by "unit" we can mean 1, 10, 100, etc. elements of each category (indeed, for our next visualization we'll use a unit of 1 mln km2). Each subset (category) is represented by an area on a waffle chart filled with squares. As it was with a treemap, the area of each category on the grid is proportional to its value.
Waffle charts work best when we have a relatively small set of discrete data and are interested in the internal structure of its components. It’s important to keep in mind the limitations of this type of graphs, though. The weak point related purely to a waffle chart is that the number of units in each category should be reasonably small. Some other limitations are in common with the already mentioned types of visualizations:
With both pie charts and treemaps:
It doesn’t work well when the proportions of the categories are similar.
It doesn’t have axes, so we have to rely on a visual comparison of the areas or add the corresponding annotations on each of them.
It has the same issue with an inevitable abundance of colors.
The components of a waffle chart are supposed to constitute the whole.
It’s highly advisable to sort the data to be used as an input.
With a pie chart:
When there are many categories to be compared, a waffle chart becomes inefficient.
With a treemap:
The ways of customization of a waffle chart are not always straightforward and intuitively understandable.
Let’s see if using a waffle chart is a good idea for our data:
from pywaffle import Wafflefig = plt.figure(FigureClass=Waffle, figsize=(12,7), values=dct, columns=15,                  block_arranging_style='snake',                 legend={'bbox_to_anchor': (1.3, 1), 'fontsize':14, 'frameon': False})plt.title('Continents by area', fontsize=33)plt.show()
The graph above looks perfectly customized and reveals the patterns in a correct way. However, we have to admit that here a waffle chart is not the right choice: even though we have few categories and each of them consists of relatively few units, we just don’t need such detailed information about the internal structure of each subset. Also, our “unit” looks a bit bizarre: 1 mln km2! 😮 And we can’t even call it a discrete value. Hence, our data is just not suitable for this kind of visualizations.
The necessary parameters for a waffle chart are FigureClass, values, and columns (or rows).
One particular variety of a waffle chart is a pictogram chart that uses icons instead of squares. Usually, such icons should be something simple yet meaningful for each category, for example, symbols of stars for showing progress in different spheres. Pictogram charts can be more efficient for displaying categorical data when we want to demonstrate the insights in a more impactful and engaging way. Both waffle and pictogram charts are especially useful when it comes to illustrating statistical data, ratings, progress status, etc.
Finally, the most exotic and almost unexpected way to show categorical data is to create a word cloud. For this purpose, we need the wordcloud library (pip install wordcloud). The traditional use of it is to visualize the text data, with the size of each word indicating its frequency so that we can discern the most important words in a text. However, there is also a lesser-known application of word clouds: having the data with a value of some attribute assigned to each category, we can create a word cloud based not on the word frequency but on that attribute (which, in our case, is the area of each continent). Moreover, using word clouds in this way, we easily avoid many typical known issues related to them. Indeed, unlike the "normal" word clouds:
There is no need to prepare the data in advance, to remove the stop words, etc.
No issue with lacking context.
Since the number of categories is usually relatively small, we can assure suppressing any vertical words (assigning 1 to the prefer_horizontal parameter) to improve the graph readability. Also, we definitely don't need to use a mask in this case.
Some other drawbacks related to all kinds of word clouds still remain:
A word cloud doesn’t allow an immediate clear ranking of the words.
A word cloud lacks a quantitative approach: it’s impossible to translate a font size to a precise value of the attribute in interest. Moreover, in this case, we can’t even add the values directly on the graph.
There is an optical illusion that longer names of categories (especially multi-word ones), or those with ascenders (like k, b, l) or descenders (like j, q, g) seem bigger and hence more important than the names of the categories with the same or comparable values but without such features.
If we use a continuous matplotlib colormap for our word cloud (magma, spring, etc.), we should remember that the color for each category will be selected from it randomly, without any graduality of colors as a potential additional indicator of the category rank.
Another disadvantage is in common with pie charts, treemaps, and waffle charts: a word cloud is not efficient when the proportions of the categories are similar. However, a word cloud has some advantages with respect to those types of visualizations:
The abundance of colors is a resolvable issue: it’s possible to assign a color function or even to make all the words of the same color.
It’s not necessary for the categories to constitute the whole.
There is no need to sort the data beforehand.
A word cloud is still useful for displaying more categories (in comparison with pie and waffle charts).
Word clouds are highly customizable.
Let’s create a word cloud for our continents to see its pros and cons in action:
from wordcloud import WordCloudplt.figure(figsize=(12,8))wordcloud = WordCloud(background_color='white',prefer_horizontal=1).generate_from_frequencies(dct)plt.text(x=120, y=-25, s='Continents by area, mln km2', fontsize=27)plt.imshow(wordcloud)plt.axis('off')plt.show()
We were ready for a word cloud not to be particularly good for mental category ranking or quantitative analysis. Besides, we don’t see many ascenders or descenders. However, what really distracts here is the presence of the double-word categories: North America and South America. Thus, we can conclude that it’s better to use this approach only in the case of one-word categories.
Technically, while all the parameters of WordCloud() are optional in our case, using the generate_from_frequencies() with a dictionary or Series passed in is essential. Interesting that many parameters related to "normal" word clouds become useless for the attribute-based ones, e.g. collocations, stopwords, min_word_length, and max_words.
In this article, we compared various types of visualizations for displaying categorical data. Each of them has its scope of usage, strengths, and weaknesses. Some of these types of graphs are classical and popular (bar plots), some others are very specific and look almost weird (word clouds).
In our case, for comparing the continents by their land area, the visualization types that worked best were bar plot, stem plot, pie chart, and treemap. The other 2 types, waffle chart and word cloud, resulted to be less suitable, which doesn’t mean that they would be so for any other data. Selecting an appropriate chart always depends on a situation and what exactly we want to show with our findings. A right, properly customized visualization can help us to share valuable insights with our audience.
Thank for reading!
You can find interesting also these articles: