The world’s leading publication for data science, AI, and ML professionals.

Plot outside the box – 8 Alternative Circle charts with Python to replace Rectangular charts.

Plotting circle-shaped graphs with Python.

Photo by Daniel Roe on Unsplash
Photo by Daniel Roe on Unsplash

Plotting graphs in a rectangular area, such as a typical bar chart, is common in data visualization. Using these Charts has the advantage of occupying the maximum space.

By the way, showing too many rectangular graphs close to each other, such as creating a dashboard, can make the result unattractive or too dense.

Since rectangular charts are not the only option available, some circle-shaped alternatives can replace them. Using different chart styles may return an aesthetic result.

An example of a circle-shaped chart that can be applied as an alternative to a rectangular chart. Image by author.
An example of a circle-shaped chart that can be applied as an alternative to a rectangular chart. Image by author.

Before continuing, I want to clarify that this article has nothing against using rectangular graphs. The main purpose is to present some ideas. Thus, the readers can decide what fits their uses.

Let’s get started.


Alternative charts

In this article, there will be three types of rectangular charts with eight alternatives:

1. Alternatives to a stacked bar chart.

  • 1 proportional area (aka nested shapes)

  • 2 pie chart

  • 3 donut chart

2. Alternatives to a treemap chart.

  • 4 sunburst chart (aka radial treemap)

3. Alternatives to a bar chart.

  • 5 circular bar chart (aka Race Track Plot)

  • 6 radial bar chart

  • 7 radar chart

  • 8 circle packing


1. Alternatives to a stacked bar chart.

The first rectangular chart to talk about is a stacked bar chart. This one is useful for expressing proportional contribution in comparison to a total. Firstly, we are going to create a simple stacked bar chart. Then the alternatives will be explained.

Let’s create a mock-up dataset with numpy.random. If you already have other datasets to try, you can skip to the visualization part.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

%matplotlib inline

np.random.seed(42)
v1 = np.random.randint(5,45,50)
v2 = np.random.randint(10,60,50)
v3 = np.random.randint(30,80,50)
v4 = np.random.randint(90,100,50)

df = pd.DataFrame(zip(v1,v2,v3,v4),
                  columns=['v1','v2','v3','v4'])
df = df.melt()
df.head()

Create a list and dictionary of colors to use later. The colors in the code below can be modified.

v_list = ['v1', 'v2', 'v3', 'v4']
c_code = ['#F77F00','#56C596','#2A639B','#F6D55C']
dict_color = dict(zip(v_list,c_code))

The next step is grouping by the DataFrame, calculating a percentage column, and adding a color column.

df_gb = df.groupby(by='variable').sum().reset_index()
df_gb['pct']= [round(i,1) for i in df_gb['value']*100/df_gb['value'].sum()]
df_gb['color']= c_code
df_gb

Plot a stacked bar chart to compare with the alternatives later.

groups = ['V']
v1 = [df_gb.pct[0]]
v2 = [df_gb.pct[1]]
v3 = [df_gb.pct[2]]
v4 = [df_gb.pct[3]]
values = np.array([v1, v2, v3, v4])

sns.set_style('darkgrid')
fig, ax = plt.subplots(figsize=(1,6))

for i,c in zip(range(values.shape[0]), df_gb.color):
    ax.bar(groups,values[i], bottom=np.sum(values[:i], axis=0), color=c)
plt.show()
A stacked bar chart. Image by author.
A stacked bar chart. Image by author.

1 Proportional area (aka Nested shapes)

As mentioned earlier, the concept of a stacked bar chart is to show proportional contribution. Thus, we can apply the same point of view using the proportional area to show the contribution compared to a total.

fig, ax = plt.subplots(figsize=(8,8))
ax.scatter(0, 0)
cir = plt.Circle((0, 0), 1, color='lightgray', fill=True)
ax.add_patch(cir)

df_gb.sort_values(by=['pct'], ascending=False, inplace=True)

for i,c,l in zip(df_gb.pct,df_gb.color,df_gb.variable):
    data = np.array(i)
    r = np.sqrt(data/100)
    cir_p = plt.Circle((0, r-1), r, color=c, fill=True)
    ax.add_patch(cir_p)
    ax.axis('off')
    kw = dict(color="white", va="center", ha="center")
    kw.update(dict(fontsize=19))
    ax.text(0, (2*r)-1.08, l+' '+str(i)+'%', **kw)
    ax.set_aspect('equal')

plt.xlim([-1, 1])
plt.xlim([-1, 1])
plt.show()
A proportional area as an alternative to a stacked bar chart. Image by author.
A proportional area as an alternative to a stacked bar chart. Image by author.

A drawback is this alternative requires more space than a stacked bar chart. This is a limitation if the plotting area matters. Another thing to be considered is replacing the bar charts with timelines on the x-axis. Using multiple nested shapes is inconvenient for showing the changes over time.

The results can be expressed separately and combined as a photo collage, a fancy idea for making an infographic.

for i,c,l in zip(df_gb.pct,df_gb.color,df_gb.variable):
    data = np.array(i)
    r = np.sqrt(data/100)

    fig, ax = plt.subplots(figsize=(8,8))
    ax.scatter(0, 0)
    cir = plt.Circle((0, 0), 1, color='lightgray', fill=True)
    cir_p = plt.Circle((0, r-1), r, color=c, fill=True)
    ax.add_patch(cir)
    ax.add_patch(cir_p)
    ax.axis('off')
    kw = dict(color="white", va="center", ha="center")
    kw.update(dict(fontsize=32))
    ax.text(0, r-1, l+'n'+str(i)+'%', **kw)
    ax.set_aspect('equal')

    plt.xlim([-1, 1])
    plt.xlim([-1, 1])
    plt.show()

Voila!!

A separated proportional area as an alternative to a stacked bar chart. Image by author.
A separated proportional area as an alternative to a stacked bar chart. Image by author.

If you want to create a photo collage, you can check my article: ‘9 Visualizations with Python that Catch More Attention than a Bar Chart (link).’


2 Pie chart

A pie chart is a simple option to show data composition in percentages. Applying pie charts as an alternative shares the same drawback as the proportional area, which requires more space. Thus, if the plotting area does not matter, this option is simple to create.

import plotly.express as px
fig = px.pie(df_gb, values='value', names='variable',
             color='variable', color_discrete_map=dict_color)
fig.update_layout(width=600, height=600, margin=dict(l=0, r=0, b=0, t=0))
fig.show()
A pie chart as an alternative to a stacked bar chart. Image by author.
A pie chart as an alternative to a stacked bar chart. Image by author.

3 Donut chart

Basically, a donut chart is a pie chart with a hole. This free space in the center seems to have some benefits since some sources claim that it helps the reader’s eye focus on the arcs and facilitates the narrative. Moreover, it can be used for adding some notes or information.

import plotly.express as px
fig = px.pie(df_gb, values='value', names='variable',
             color='variable', color_discrete_map=dict_color,
             hole=0.6)
fig.update_layout(width=600, height=600, margin=dict(l=0, r=0, b=0, t=0))
fig.show()
A donut chart as an alternative to a stacked bar chart. Image by author.
A donut chart as an alternative to a stacked bar chart. Image by author.

2. Alternatives to a treemap chart.

Technically, a treemap is an effective method to display hierarchical data. It expresses the data in a set of nested rectangles. A treemap chart uses the full plotting area. This one may not be a good choice if you need some free space.

To show a treemap, I will modify the dataset by adding subgroups to each variable. The code below shows how to add a subgroup column and plot the chart. After that, we will see an alternative to a treemap.

df['sub']=[i+'.1' if j%2 else i+'.2' for i,j in zip(df.variable, df.value)]
df_wsub=df.groupby(by=['variable','sub']).mean().reset_index()
df_wsub
import plotly.express as px
fig = px.treemap(df_wsub, path=['variable', 'sub'], values='value',
                 color='variable',color_discrete_map=dict_color)
#fig.update_traces(root_color="lightgrey")
fig.update_layout(width=800, height=600, margin=dict(l=0, r=0, b=0, t=0))
fig.show()
A treemap chart. Image by author.
A treemap chart. Image by author.

4 Sunburst chart (aka radial treemap)

A perfect replacement for a treemap is a sunburst chart. This chart is also called a radial treemap since it is literally a treemap in a circle area. We can create a sunburst chart using Plotly with a few lines of code, as shown below.

import plotly.express as px
fig = px.sunburst(df_wsub, path=['variable', 'sub'], values='value',
                  color='variable',color_discrete_map=dict_color)
fig.update_layout(width=800, height=600, margin=dict(l=0, r=0, b=0, t=0))
fig.show()

Ta-da!!

Sunburst chart as an alternative to a treemap chart. Image by author.
Sunburst chart as an alternative to a treemap chart. Image by author.

Compared with the treemap, you can see that this chart has some more free space around the circle.


3. Alternatives to a bar chart.

Even though a bar chart is a standard chart that is frequently used, plotting too many bar charts can make the result boring. By changing the perspective, the rectangular bars can be replaced with some equivalents.

As an example, I will make a mock-up dataset and use it to plot a simple bar chart. After that, we will talk about some alternatives.

np.random.seed(42)
v1 = np.random.randint(5,50,50)
v2 = np.random.randint(5,50,50)
v3 = np.random.randint(20,60,50)
v4 = np.random.randint(20,60,50)
v5 = np.random.randint(90,100,50)
v6 = np.random.randint(10,50,50)
v7 = np.random.randint(10,50,50)
v8 = np.random.randint(25,60,50)
v9 = np.random.randint(25,60,50)

df = pd.DataFrame(zip(v1,v2,v3,v4,v5,v6,v7,v8,v9),
                  columns=['v1','v2','v3','v4','v5','v6','v7','v8','v9'])
df = df.melt()

#extract color palette
pal = list(sns.color_palette(palette='Spectral', n_colors=9).as_hex())

df_gb = df.groupby(by='variable').sum().reset_index()
df_gb['pct']= [round(i,1) for i in df_gb['value']*100/df_gb['value'].sum()]
df_gb['color']= pal
df_gb

Plot a bar chart.

sns.set_style('darkgrid')
plt.figure(figsize=(9,5))
plt.bar(x=df_gb.variable, height=df_gb.pct,color=df_gb.color)
plt.show()
A bar chart. Image by author.
A bar chart. Image by author.

5 Circular bar chart (aka Race Track Plot)

The first alternative is modifying the bars’ direction to circulate the center. Reading the chart, the bars seem to be racing to complete the circle. Thus, this can produce an eye-catching effect.

Please consider that the circular bar chart has the drawback of the bars’ unequal proportion. Even if they have the same value, those close to the center are always shorter than those outside.

The problem can be handled by labeling each bar to show the user the actual value.

import math
plt.gcf().set_size_inches(8,8)
sns.set_style('darkgrid')

#set max value
max_val = 26
ax = plt.subplot(projection='polar')

for i,p,l in zip(range(len(df_gb)),pal,df_gb.variable):
    ax.barh(i, df_gb['pct'][i]*2*np.pi/max_val, label=l, color=p)

#set the subplot 
ax.set_theta_zero_location('N')
ax.set_theta_direction(1)
ax.set_rlabel_position(0)
ax.set_thetagrids([], labels=[])

labels = [i+' '+str(j)+'%' for i,j in zip(df_gb.variable, df_gb.pct)]
ax.set_rgrids(range(len(df_gb)), labels= labels)

#set the projection
ax = plt.subplot(projection='polar')
plt.legend().set_visible(False)
plt.show()
Circular bar chart as an alternative to a bar chart. Image by author.
Circular bar chart as an alternative to a bar chart. Image by author.

6 Radial bar chart

Besides circulating, the bars can be expressed by moving from the center to the outside. Compared with the circular bar charts, this one has no issues with the proportion of each bar. This is another method to make the result looks attractive.

By the way, the bars not located next to each other are hard to compare. The problems can also be solved by labeling to facilitate the comparison.

plt.figure(figsize=(8,8))
ax = plt.subplot(111, polar=True)
plt.axis()

#set min and max value
ax.axis(ymin=0, ymax=25)

#set heights and width
heights = df_gb['pct']
width = 2*np.pi / len(df_gb)
#set index and angle
indexes = list(range(1, len(df_gb)+1))
angles = [element * width for element in indexes]
bars = ax.bar(x=angles, height=heights, width=width, bottom=0,
              linewidth=1, edgecolor="white", color=pal)
labelPadding = 2

labels = [i+' '+str(j)+'%' for i,j in zip(df_gb.variable, df_gb.pct)]
for bar, angle, height, label in zip(bars, angles, heights, labels):
    rotation = np.rad2deg(angle)
    alignment = ""
    #deal with alignment
    if angle >= np.pi/2 and angle < 3*np.pi/2:
        alignment = "right"
        rotation = rotation + 180
    else: 
        alignment = "left"

    ax.text(x=angle, y=0 + bar.get_height() + labelPadding,
            s=label, ha=alignment, va='center', rotation=rotation, 
            rotation_mode="anchor")

    ax.set_thetagrids([], labels=[])

plt.show()
Radial bar chart as an alternative to a bar chart. Image by author.
Radial bar chart as an alternative to a bar chart. Image by author.

7 Radar chart

In this case, using a radar chart may be a strange idea because this chart is usually used to work with multivariate data. However, by changing the point of view, a radar chart can be applied as an alternative showing categorical data and its values on the axes.

import plotly.express as px
import pandas as pd
fig = px.line_polar(df_gb, r='pct', theta='variable',
                    line_close=True, color_discrete_sequence=[c_code[1]])
fig.update_polars(radialaxis_range=[0,25])
fig.update_layout(width=600, height=600, margin=dict(l=10,r=10,b=10,t=20))
fig.update_traces(fill='toself')
fig.show()
Radar chart as an alternative to a bar chart. Image by author.
Radar chart as an alternative to a bar chart. Image by author.

Technically, the radar chart and the radial bar chart return the same result concept. Instead of showing bars around the center, the radar chart uses a line to compare the values.


8 Circle packing

Lastly, we can convert the rectangular bars into multiple circles and group them as a circle packing. This is another idea for creating an infographic.

Please consider that even though the circle can label with information, too many circles can cause difficulty reading the graph.

Start with calculating each circle’s position with the circlify library.

import circlify
# compute circle positions:
circles = circlify.circlify(df_gb['pct'].tolist(), 
                            show_enclosure=False, 
                            target_enclosure=circlify.Circle(x=0, y=0))

Plot the circle packing.

df_gb.sort_values(by='pct', ascending=True, inplace=True)

fig, ax = plt.subplots(figsize=(8,8), facecolor='white')
ax.axis('off')
lim = max(max(abs(c.x)+c.r, abs(c.y)+c.r,) for c in circles)
plt.xlim(-lim, lim)
plt.ylim(-lim, lim)

# print circles
for circle,v,p,color in zip(circles,df_gb.variable,df_gb.pct,df_gb.color):
    x, y, r = circle
    ax.add_patch(plt.Circle((x, y), r, alpha=0.9,color = color))
    plt.annotate(v+'n'+str(p)+'%',(x,y),size=12,va='center',ha='center')

plt.xticks([])
plt.yticks([])
plt.show()
Circle packing as an alternative to a bar chart. Image by author.
Circle packing as an alternative to a bar chart. Image by author.

Key takeaway

This article has shown nothing new but ideas to replace rectangular charts using circle-shaped graphs as alternatives. On the contrary, these circle charts can also be replaced with rectangular ones.

Selecting the proper graphs depends on many factors, such as the audience, the media, etc. The main idea is to change the graph to make Data Visualization more attractive depending on the uses.

Obviously, there are more charts and ideas than those mentioned in this article. If you have any comments, please feel free to share. I would be happy to see it.

Thanks for reading


These are my data visualization articles that you may find interesting:

  • 9 Visualizations with Python to show Proportions instead of a Pie chart (link)
  • 9 Visualizations with Python that Catch More Attention than a Bar Chart (link)
  • 8 Visualizations with Python to Handle Multiple Time-Series Data (link)
  • 7 Visualizations with Python to Express changes in Rank over time (link)
  • Battle Royale – Comparison of 7 Python Libraries for Interactive Financial Charts (link)

Reference


Related Articles