
Color blindness, or more precisely color vision deficiency, affects up to 8% of men and 0.5% of women. The most common condition is red-green color blindness, a congenital disorder in which people lack the receptors for either red or green colors in their retinas. As a result, they cannot distinguish color pairs such as red and green, cyan and grey, and blue and purple.
It is important for data scientists to consider color-blind users when choosing color palettes for graphs. A significant proportion of users will have some form of color vision deficiency, and they may understand graphs differently than intended. In fact, I understood the importance of this when my direct supervisor was unable to read my line graphs, and it turned out that he had difficulty distinguishing between red and green.
In this article, I will share
- A Python simulator for the most common forms of color blindness
- An Accessibility test for the default color palettes in matplotlib and seaborn
- Creating accessible color palettes with ColorBrewer
What does color blindness look like?
Color vision is made possible by cone cells in the retina. There are three different types of cone cells that receive red, green, and blue light, respectively. People with red-green deficiency lack either the cones that detect red light or the cones that detect green light. Lack of both cones, leaving only the receptor for blue light, is rare, as is lack of the blue-receptive cone and total achromatopsia, the lack of all color receptors.
For those who can see all colors, it is difficult to imagine how a graph looks to a colorblind person. Color blindness simulators are useful tools for checking the accessibility of your graphics. The simulation in the following panel is made with the DaltonLens online color simulator, using the Brettel algorithm.

Python simulators
The DaltonLens project provides implementations of several algorithms for simulating color vision deficiency. The source code is available on Github and the library itself can be installed using
pip install daltonlens
We use the Python Image Library (PIL) and a test image consisting of a graph with two lines. The test image is generated by the following lines of code and saved as a png image file.
from matplotlib import pyplot as plt
import numpy as np
# Create a test image featuring two line plots
fig, ax = plt.subplots(1, 1)
ax.plot(x, y1, label='data 1', color='red')
ax.plot(x, y2, label='data 2', color='green')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.legend()
plt.savefig('./plots/2024-dalton-lines.jpg', bbox_inches='tight')
We then load the test image, and color blindness simulators transform the RGB values so that the transformed image is similar to the perception of a person with color vision deficiency.
from daltonlens import convert, simulate, generate
import PIL
# Load the original image and convert it to a numpy array
im = np.asarray(PIL.Image.open('./plots/2024-dalton-lines.jpg').convert('RGB'))
# Create a simulator using the Brettel 1997 algorithm.
simulator = simulate.Simulator_Brettel1997()
# Apply the simulator to the input image to get a simulation of deutan anomaly ("green-blindness")
greenblind_im = simulator.simulate_cvd (im, simulate.Deficiency.DEUTAN, severity=1.0)
# Create an image from the transformed array
im2 = PIL.Image.fromarray(protan_im)
A side-by-side comparison of the two images shows that the red and green lines on the left are barely visible in the simulated green-blind versions (center and right).

The daltonlens
library contains simulators for different types of color vision deficiency. They can be varied by passing instances of simulate.Deficiency.{DEUTAN, TRITAN, PROTAN}
to the simulator.
DEUTAN
corresponds to green-blindness (about 6% of the male and 0.39% of the female population).PROTAN
corresponds to red-blindness (about 2% of the male and 0.04% of the female population).TRITAN
corresponds to blue-blindness (very rare, less than 0.002% affected).
In addition, different algorithms can be selected, although the Bretter and Viennot algorithms are recommended on their website.
How does the colorblindness simulation work?
Under the hood, the algorithms perform matrix transformations. An RGB image can be represented as an array, where each pixel corresponds to an array of 3 elements representing the red, green, and blue values of the given pixel.
This RGB pixel array is transformed into a representation in which the three types of cones in the retina (L, M, and S) form the coordinate system. In this representation, the pixel values are transformed according to the deficient cone and the severity of the color vision deficiency.
Finally, the array is transformed back to RGB so that it can be displayed correctly on monitors.
How accessible are default color schemes?
Python’s default library for plotting graphs is matplotlib. The default color scheme for line plots consists of eight colors. The following figure shows line plots in all eight colors. When we simulate how this graph would look to a green-blind person, we see that the colors are generally still distinguishable, although they appear closer together than in the original graph. For example, blue and purple (C0 and C4) are barely distinguishable. The same holds for red, green, and brown (C2, C3, C5).

In this case, an accessible graph would use line styles (dashed, dotted, and solid). Also, I would not use too many lines in a graph because it gets cluttered.
Two-dimensional data is displayed using heat maps. The following code generates sample data and uses a color map that varies between red, yellow, and green. It is then processed to simulate green blindness. We use the severity parameter to vary the degree of color vision deficiency, in this case between 75% and 100%.
# create an example figure
fig, ax = plt.subplots(1, 1)
xx,yy = np.meshgrid(np.linspace(0, 2*np.pi, 40), np.linspace(0, 2*np.pi, 40))
zz = np.cos(xx) + np.sin(yy)
img = ax.imshow(zz, cmap='RdYlGn')
plt.colorbar(img)
ax.set_xlabel('x')
ax.set_ylabel('y')
fig.tight_layout()
plt.savefig('./plots/2024-dalton-mpl-heat-rdylgn.jpg')
# simulate 75% green-blindness
im = np.asarray(PIL.Image.open('./plots/2024-dalton-mpl-heat-rdylgn.jpg').convert('RGB'))
simulator = simulate.Simulator_Brettel1997()
greenblind_im = simulator.simulate_cvd (im, simulate.Deficiency.DEUTAN, severity=0.75)
im2 = PIL.Image.fromarray(greenblind_im)
im2
# simulate full green-blindness
im = np.asarray(PIL.Image.open('./plots/2024-dalton-mpl-heat-rdylgn.jpg').convert('RGB'))
simulator = simulate.Simulator_Brettel1997()
greenblind_im = simulator.simulate_cvd (im, simulate.Deficiency.DEUTAN, severity=1.0)
im3 = PIL.Image.fromarray(greenblind_im)
im3
The resulting graphs show that this color map is a poor choice for colorblind people. The positive and negative values appear in the same shade.

Fortunately, there are color palettes that are more suitable for colorblind people. The next panel shows a heatmap using the viridis color map, which cycles from yellow to blue.

Statistical graphs, such as bar charts, often use color to improve readability. We use the seaborn plotting library and the default color palette to create a bar plot of toy data.
# create example bar plot
import pandas as pd
import seaborn as sns
fig, ax = plt.subplots(1, 1)
x = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
y = np.random.rand(len(x))
data = pd.DataFrame(dict(x=x,y=y))
# plot with seaborn
sns.barplot(data=data, x=x, y=y)
ax.set_xlabel('Category')
ax.set_ylabel('Value')
fig.tight_layout()
plt.savefig('./plots/2024-dalton-bar.jpg')
# simulate green-blindness
im = np.asarray(PIL.Image.open('./plots/2024-dalton-bar.jpg').convert('RGB'))
simulator = simulate.Simulator_Brettel1997()
greenblind_im = simulator.simulate_cvd (im, simulate.Deficiency.DEUTAN, severity=1.0)
im2 = PIL.Image.fromarray(greenblind_im)
im2
The resulting bar plot is displayed in seaborn’s default color palette, which is the same as in matplotlib. I find the colors to categories C, D, F (green, red, and brown) to be very close to each other in the color-blind version to the right. In this case, the colors do not add much to the plot, and I would recommend showing all bars in the same color.

ColorBrewer palettes
When I want to create my own color palette and not rely on the default palettes available in matplotlib and seaborn, I usually turn to the ColorBrewer website. Although the site was originally created for cartographic data, it has a handy feature for color-blind friendly palettes.
Users can create color palettes with up to 12 different colors. The resulting palettes can be exported as hex colors for use in plotting tools such as matplotlib.

Summary
Color vision deficiency is common. About 8% of people are affected to some degree, with green-blindness and red-blindness being the most common types. Graphs communicate their meaning through color, for example, to display multiple lines in a plot and to show 2D heat maps.
Color-blindness simulators are a useful tool for testing graphs for accessibility. They can be imported directly into Python code for quick use in any project.
When choosing colors and palettes, a color blindness check is useful to increase the accessibility of your graphs and ensure that they can convey the message to any user.

References
- Review of Open Source Color Blindness Simulations: https://daltonlens.org/opensource-cvd-simulation/
- Color-Blindness Simulator: https://daltonlens.org/colorblindness-simulator
- ColorBrewer: https://colorbrewer2.org/