Department | 2023 | 2024 |
---|---|---|
str | i64 | i64 |
"Customer Service" | 80 | 70 |
"Finance" | 60 | 70 |
"HR" | 75 | 80 |
"IT" | 45 | 55 |
"Operations" | 85 | 78 |
"Procurement" | 32 | 55 |
"Production" | 20 | 35 |
"Sales" | 60 | 65 |
How to create charts from The Economist magazine using plotly
Introduction
We at Conterval have always been fans of the charts from The Economist magazine. No publication does a better job of creating static visualizations you can use in print. We love their charts because they’re simple, yet they manage to convey the relevant message contained in the data. More importantly, they adhere to Ben Shneiderman’s point that:
The purpose of visualization is insight, not pictures.
It turns out that having too much graphic detail on your charts is not as effective at conveying the message as having a simple chart. The Economist is the master of simple, yet informative charts.
Unfortunately, many people focus on the chart’s aesthetics—how it looks to the human eye. In so doing, they neglect the message contained in the data, which they are supposed to convey to the audience.
Although we may not know the specific software or graphing library used by The Economist for their charts, their simplicity makes them easy to recreate with any competent graphing tool. In this case, we’ll recreate two of their charts using the Python graphing library Plotly.
Dumbbell plot
A dumbbell plot is a dot plot that uses straight lines to connect two points for each group. This type of chart is ideal for illustrating changes in a variable over two time points or highlighting the range of a variable across multiple groups. The Economist dumbbell chart below has two groups (1970 and 2020) joined by two dots. These years are points in time.
Suits dataset
Since we didn’t have the data used to create the chart above, we put together some fake department data about a company from one of our favorite TV shows, Suits.
Below is a dataframe showing customer satisfaction scores for each department of the fictional firm Pearson Specter Litt for the years 2023 and 2024.
And now here’s a Plotly recreation of our dumbbell plot.
= '#E9EDF0'
BG_COLOR = '#E3120B'
RED = '#5e5c64'
GREY
= go.Figure()
fig
for i in range(df.shape[0]):
fig.add_trace(go.Scatter(=[df["2023"][i], df["2024"][i]],
x=[df["Department"][i], df["Department"][i]],
y='lines+markers',
mode=dict(color='#598080', width=4), # Thicker line
line=dict(size=16), # Bigger dots
marker=False
showlegend
))
fig.add_trace(go.Scatter(=[df["2023"][i]],
x=[df["Department"][i]],
y='markers',
mode=dict(color=GREY, size=16), # Bigger dots
marker=False
showlegend
))
fig.add_trace(go.Scatter(=[df["2024"][i]],
x=[df["Department"][i]],
y='markers',
mode=dict(color=RED, size=16), # Bigger dots
marker=False
showlegend
))
# Update layout to customize appearance
fig.update_layout(="<b>Engagement score declined in<br>customer service & operations",
title=dict(size=26),
title_font=.9,
title_y=BG_COLOR,
plot_bgcolor=BG_COLOR,
paper_bgcolor=600,
height=dict(t=180, b=80),
margin=dict(
xaxis="top", # Move x-axis to the top
side=dict(size=18, family="Inter") # Increase the font size of the x-axis ticks
tickfont
),=dict(
yaxis=dict(size=16, family="Inter")
tickfont
),=[
shapesdict(
type="line",
="paper",
xref="paper",
yref=-0.12, y0=1.5,
x0=0.022, y1=1.5,
x1=dict(
line=RED,
color=10
width
)
)],=dict(family="Inter") # Set the global font to "Inter"
font
)
fig.add_annotation(dict(
="<b>2023",
text=0.1, # x position (0 means far left)
x=0.72, # y position (adjust as necessary)
y="paper",
xref="paper",
yref=False, # No arrow
showarrow=dict(
font="Inter", # Font family
family=22, # Font size
size=GREY # Font color
color
),="left"
align
),
)
fig.add_annotation(dict(
="<b>2024",
text=0.6, # x position (0 means far left)
x=0.72, # y position (adjust as necessary)
y="paper",
xref="paper",
yref=False, # No arrow
showarrow=dict(
font="Inter", # Font family
family=22, # Font size
size=RED # Font color
color
),="left"
align
),
)
fig.add_layout_image(dict(
=f"data:image/png;base64,{encoded_image}",
source="paper",
xref="paper",
yref=.94,
x=-0.18,
y="right",
xanchor="bottom",
yanchor=0.22,
sizex=0.22,
sizey="above"
layer
)
)
fig.add_annotation(dict(
="Source: Pearson Specter Litt",
text=-0.14, # x position (0 means far left)
x=-0.175, # y position (adjust as necessary)
y="paper",
xref="paper",
yref=False, # No arrow
showarrow=dict(
font="Inter", # Font family
family=14, # Font size
size=GREY # Font color
color
),="left"
align
),
)
fig.show()
Disregard the differences between the data in my chart and The Economist’s chart, and focus on the style. You’ll see that the styles are identical. This highlights Plotly’s versatility as a graphing library—allowing you to fully customize any chart to match your preferences.
Stacked bar plot
A stacked bar chart is a type of bar graph where each bar is divided into segments that represent different subcategories. It shows how each subcategory contributes to the total value of a larger category, making it easy to see the breakdown of the whole.
What makes this chart unique is the placement of the major group names above each bar, rather than beside them. This design choice offers two key advantages: it makes it easier to associate each category with its corresponding bar and saves space, resulting in a more compact plot. Additionally, the data varies significantly—for example, from 11 to 998—which would distort the scale in a regular bar chart. By opting for a stacked bar chart, The Economist effectively addressed this issue, demonstrating thoughtful and intentional design.
Military dataset
Fortunately, all the data needed to recreate the The Economist stacked bar plot is visible in the plot. Here’s a dataframe of that data showing the category of military technology for the United States and China.
Category | China | United States |
---|---|---|
str | i64 | i64 |
"Total battle force" | 370 | 291 |
"Principle surface combatants" | 92 | 122 |
"Aircraft-carriers" | 2 | 11 |
"Combat aircaft" | 456 | 988 |
"Helicopters" | 116 | 689 |
And now here’s a Plotly recreation of our stacked bar plot.
= (data
df =pl.col('China') / pl.sum_horizontal('China','United States'),
.with_columns(China_pct=pl.col('United States') / pl.sum_horizontal('China','United States'),
US_pct=pl.concat_str([pl.lit('<b>'), pl.col('Category')]))
Category
)
# Create the percentage stacked bar chart using Plotly Graph Objects
import plotly.graph_objects as go
# Adding China's df as percentage with left-aligned text
= go.Figure()
fig
# China's df with actual number label
fig.add_trace(go.Bar(=[1, 2, 3, 4, 5], # Use a numerical y-axis instead of category labels
y=df['China_pct'],
x=.5,
width='China',
name=dict(color='#E3120B'),
marker='h',
orientation=df['China'], # Actual number label
text='inside', # Place text inside the bar
textposition='start' # Left-align the text inside the bars
insidetextanchor
))
# United States' df with actual number label
fig.add_trace(go.Bar(=[1, 2, 3, 4, 5], # Match the same y-values for the US df
y=df['US_pct'],
x='United States',
name=dict(color='#9a9996'), # Set the color of the bars
marker=.5,
width='h',
orientation=df['United States'], # Actual number label
text='inside', # Place text inside the bar
textposition='end' # Right-align the text inside the bars
insidetextanchor
))
# Adding annotations for the category labels above the bars
for i, category in enumerate(df['Category']):
fig.add_annotation(=0, # Align annotation at the start (left)
x=i+1.45, # Adjust the y value to move the annotation slightly above the bar
y=category, # The category label
text=False, # Disable arrows
showarrow='left', # Left-align the annotation
xanchor='middle', # Center the annotation vertically
yanchor=0 # No horizontal shift
xshift
)
# Update layout for percentage stacking and legend positioning
fig.update_layout(=600,
height=dict(t=200, b=50),
margin='stack',
barmode="<b>China's military tech</b><br><b>still lags the West</b>",
title=.9,
title_y=0.03,
title_x=dict(size=26),
title_font# xaxis_title='Percentage of Total Production',
='', # Remove the category labels from y-axis
yaxis_title=dict(
xaxis=[0, 25, 50, 75, 100], # Set tick marks for percentage
tickvals=False, # Hide grid lines
showgrid=False # Hide tick labels
showticklabels
),=[
shapesdict(
type="line",
="paper",
xref="paper",
yref=0, y0=1.52,
x0=0.1, y1=1.52,
x1=dict(
line=RED,
color=10
width
)
)],# Consolidating yaxis settings here
=dict(
yaxis=False, # Hide the default y-axis labels
showticklabels=False,
showgrid=[1, 2, 3, 4, 5], # Keep the numerical y-axis values
tickvals=['', '', '', '', ''], # Hide y-axis text to avoid overlap
ticktextrange=[0.5, 5.5] # Adjust the range to add some padding
),
# Adjust the gap between the bars
=0.65, # Increase this to widen the gap between bars
bargap
# Adjust legend positioning
=dict(
legend=.96, # Horizontal position (1 is far right)
x=1.04, # Vertical position (1 is top)
y='right', # Anchors the legend box to the right side of the plot
xanchor='top', # Anchors the legend box to the top of the plot
yanchor='h' # Set the legend items to be horizontal
orientation
),
='#E9EDF0',
plot_bgcolor='#E9EDF0',
paper_bgcolor
# Set the font to "Inter"
=dict(
font="Inter",
family=12, # Adjust the size as needed
size="Black" # Adjust the color as needed
color
),
)
fig.add_annotation(dict(
="Sources: IISS; US Department of Defence",
text=-0.005, # x position (0 means far left)
x=-0.12, # y position (adjust as necessary)
y="paper",
xref="paper",
yref=False, # No arrow
showarrow=dict(
font="Inter", # Font family
family# size=12, # Font size
="#9a9996" # Font color
color
),="left"
align
),
)
fig.add_annotation(dict(
="<b> Navy balance, December 2022 or latest available",
text=-0.01, # x position (0 means far left)
x=1.2, # y position (adjust as necessary)
y="paper",
xref="paper",
yref=False, # No arrow
showarrow=dict(
font="Inter", # Font family
family=18, # Font size
size="#1a1a1a" # Font color
color
),="left"
align
),
)
fig.add_layout_image(dict(
=f"data:image/png;base64,{encoded_image}",
source="paper", # Reference the x position relative to the plotting area
xref="paper", # Reference the y position relative to the plotting area
yref=.95, # x-coordinate (1 means far right)
x=-0.12, # y-coordinate (0 means bottom)
y="right", # Anchor the image from the right
xanchor="bottom", # Anchor the image from the bottom
yanchor=0.2, # Set the width of the image (adjust as necessary)
sizex=0.2, # Set the height of the image (adjust as necessary)
sizey="above" # Make sure the image is displayed above the plot
layer
)
)
fig.show()
And there you have it! We’ve successfully recreated charts inspired by The Economist magazine using Plotly for graphing and Polars for data manipulation.
If you need custom plots for your data, whether for print or digital media, feel free to reach out to us!