Basics of Ternary plots with Python’s Plotly | by Thiago Carvalho | Towards Data Science

There are plenty of ways to display three variables in a single visualization. Heatmaps and Colormaps rely on encoding the third variable in the color. Bubble charts are Scatter Plots with the third variable encoded in size, and other solutions may introduce a Z-axis and rely on 3-dimensional representations.
Ternary plots are a less known solution that doesn’t require our user to compare colors, circumference sizes, or 3D distances.
They’re a two-dimensional representation where all the three variables are encoded by their positions to three connected axes, in the shape of a triangle.
This article will go through the basics of how to draw ternary scatter plots using Plotly Express.
Let’s start with a simple plot. We’ll define three variables ‘Statistics’, ‘Business,’ and ‘Design.’ Then we’ll pass those variables to Plotly as ‘a’, ‘b,’ and ‘c.’
import plotly.express as pxstats = [0.1, 0.1, 0.8, 1]bus = [0.8, 0.1, 0.1, 1]design = [0.1, 0.8, 0.1, 1]df = pd.DataFrame({'Statistics':stats, 'Business':bus, 'Design':design})fig = px.scatter_ternary(df, a="Statistics", b="Business", c="Design")fig.show()
Great! Simple as that, and we have our ternary plot.
We can see that there are three scales in our chart, one for each variable. It’s ‘Statistics,’ from bottom to top from left to right ‘Design,’ and from right to left ‘ Business.
Plotly automatically calculates the proportion for the values, so the scales go from zero to one and must always total one — That is to say, this chart type doesn’t allow us to compare totals.
In the example, all records summed to one (0.8, 0.1, 0.1), but the last summed to three (1, 1, 1). By looking at the plot, we could say that the center's point is more balanced but can’t really tell that it has a higher value than the rest.
Just like in any scatter plot, we can encode another numerical variable to our marker size—something like a Ternary Bubble Chart.
stats = [2, 8, 4, 15, 6, 1, 4, 12]bus = [1, 3, 8, 3, 8, 3, 5, 1]design = [7, 2, 2, 3, 6, 1, 4, 1]total = [val + bus[i] + design[i] for i, val in enumerate(stats)]df = pd.DataFrame({'Statistics':stats, 'Business':bus, 'Design':design, 'Experience':total})fig = px.scatter_ternary(df, a="Statistics", b="Business", c="Design", size="Experience")fig.show()
Cool, now we can tell how each variable composes each record, and we can also distinguish them by their total values.
The fourth encoding of our ternary chart doesn’t need to be a total, we could have used any other kind of value for the size, but it’s important to note that this other encoding won’t look equivalent to the others.
Let’s compare ‘Business,’ ‘Statistics,’ ‘Design,’ and ‘Coding’ experience.
We can visualize the data with this plot, but it doesn't feel that ‘Coding’ is the same as the other variables. Since it’s encoded in size, Plotly doesn’t calculate its proportion, and it just feels out of place.
Again, like in any scatter plot, we can add yet another encoding.
Color is great for visualizing categorical variables and could also be used with numerical or ordered variables to create a heatmap.
Let’s add more information about the ‘Team’ of each record.
stats = [0.2, 0.65, 0.3, 0.8, 0.3, 0.1, 0.4, 0.85]bus = [0.1, 0.25, 0.6, 0.1, 0.4, 0.6, 0.2, 0.05]design = [0.7, 0.1, 0.1, 0.1, 0.3, 0.3, 0.4, 0.1]exp = [7, 10, 15, 4, 12, 11, 10, 8]team = ['BI', 'DS', 'DS', 'DS', 'BI', 'BI', 'BI', 'DS']df = pd.DataFrame({'Statistics':stats, 'Business':bus,                    'Design':design, 'Experience':exp,                   'Team': team})fig = px.scatter_ternary(df, a="Statistics", b="Business", c="Design", size="Experience", color='Team')fig.show()
Alright. We built our ternary plot and encoded a couple of other variables to our visualization. Now let’s check another example.
For the following examples, I’ll use VADER (Valence Aware Dictionary and sEntiment Reasoner) for getting a polarity score for some texts.
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzeranalyser = SentimentIntensityAnalyzer()analyser.polarity_scores('hello there, I love hating you')
Vader’s polarity score results in a proportion of three variables (negative, neutral, and positive). We’ll make a Ternary plot to represent those three values.
I’ll use a dataset with Drake’s lyrics, get the polarity scores for those lyrics, and then use Plotly to create our visualization.
import pandas as pd# load datadf = pd.read_csv('data/drake_data.csv')# select albumsalbums = ['Take Care', 'Views']df = df[df['album'].isin(albums)]df.head()
Let’s clean up the track_views field and get the polarity scores. Since we’re here for the ternary plots, we won’t lose too much time cleaning the texts.
# clean track viewstrack_views = []for i, val in df.iterrows():    views = str(df['track_views'][i])    unit = views[-1:]    views = float(views[:-1])    if unit == 'M':        track_views.append(views * 1000)    elif unit == 'K':        track_views.append(views)    else:        track_views.append(0)df['track_views'] = track_views# get polarity scoresneg, pos, neu = [],[],[]for i, val in df.iterrows():    txt = str(df['lyrics'][i])    score = analyser.polarity_scores(txt)    neg.append(score['neg'])    pos.append(score['pos'])    neu.append(score['neu'])df['neg'] = negdf['pos'] = posdf['neu'] = neu
Now we can draw the ternary plots as we did before. This time we’ll also set a limit for our markers' size and assign a value to the name we want to display when the user hovers over the points.
fig = px.scatter_ternary(df, a="neu", b="pos", c="neg",                          color="album", size="track_views",                         hover_name="lyrics_title", size_max=15)fig.show()
We can select our own colors and use a template theme to make it even more unique.
fig = px.scatter_ternary(df, a="neu", b="pos", c="neg",                          color="album", size="track_views",                         hover_name="lyrics_title", size_max=15,                         color_discrete_map={"Views":"#00203F",                                              "Take Care":"#E34646"},                         template="ggplot2")fig.show()
The axis labels are not so clear; let’s replace them with those variables' complete names. To improve our charts readability, we can also add a title, so the viewers know what it's about.
Our legend and colors are quite straightforward, but we’ll encode the album information again with the markers symbol, just for this example.
fig = px.scatter_ternary(df, a="neu", b="pos", c="neg",                          color="album", size="track_views",                         hover_name="lyrics_title", size_max=15,                         labels={"neu": "Neutral",                                 "pos": "Positive",                                 "neg": "Negative"},                         color_discrete_map ={"Views": "#00203F",                                              "Take Care":"#E34646"},                         template="ggplot2", symbol = 'album',                         title="Drake Lyrics Polarity Score")fig.show()
Great. The chart is looking way more meaningful.
The different symbols for the albums could be a nice addition if we didn’t have different sizes. It’s hard enough to compare circles in a chart. With different shapes, it can get misleading.
Lastly, we can customize our texts. Changing the font size, family, and color.
fig = px.scatter_ternary(df, a="neu", b="pos", c="neg",                          color="album", size="track_views",                         hover_name="lyrics_title", size_max=15,                         labels={"neu": "Neutral",                                 "pos": "Positive",                                 "neg": "Negative"},                         color_discrete_map = {"Views": "#00203F",                                                "Take Care": "#E34646"},                         template="ggplot2",                         title="Drake Lyrics Polarity Score")fig.update_layout(    font_family="Courier New",    font_color="#545454",    title_font_family="Courier",    title_font_color="#344050",    legend_title="Album",    legend_title_font_color="#545454",    legend_title_font_size=18,    title_font_size=24)fig.show()
That’s it! We built a Ternary plot and checked some of its applications and limitations. We explored other ways of encoding more variables with size, color, and shape and looked at some styling options from Plotly.
Ternary charts don’t necessarily need to be a scatter plot. You can explore other ways of using this three-axis visualization.
Combining lines, areas, polygons, heatmaps, and other elements can be very interesting and add a lot to your visualization.
Thanks for reading my article! You can find more Python DataViz tutorials here.
References: Wikipedia — Ternary PlotsPlotly — Ternary Plots;Plotly — Figure Labels;Plotly — Styling Plotly Express;Plotly — Texts and Annotations;