Professional 3-way Venn diagrams | Tailor labels and legend | Towards Data Science

As an avid python enthusiast, I recently came across the task of generating a 3-way Venn diagram. The quantity of time I spent on this seemingly simple task illuminated areas of sparsity in my working knowledge of matplotlib. May this tutorial aid others in efficiently customizing matplotlib embedded text to generate graphics ready for publication, dashboards, or reports. The full Jupyter Notebook is hosted on GitHub.
Let’s begin with data stored in pandas DataFrames. As a standard data structure, we wish to quickly visualize categorical data from three DataFrames or Series. For the purpose of this example, we will consider ten total people distributed across three groups: A, B, and C. The goal is to visualize these groupings using Venn diagrams.
import pandas as pdgroupA =['person1','person2','person3','person4','person5',         'person6','person7']groupB = ['person1','person2','person3', 'person8','person9']groupC = ['person1','person2', 'person4', 'person9','person10']dfA = pd.DataFrame(groupA,columns=[‘A’])dfB = pd.DataFrame(groupB,columns=[‘B’])dfC = pd.DataFrame(groupC,columns=[‘C’])
The ideal python data structure for analyzing groupings is the “set.” A set is an unordered grouping of unique items. In the first line, we call the column A of pandas DataFrame dfA as a series. Then, we convert the series to a set.
If your data column has duplicated items, you will need to drop them or transform their names prior to using this method. If you are unsure, call with sum(dfA.A.duplicated()). If this number is greater than zero, address duplicates prior to proceeding.
A = set(dfA.A)B = set(dfB.B)C = set(dfC.C)
Subsets of A, B, and C can be found using the set operators & and - to compute intersections (shared components) and differences. A_rest contains the individuals only found in A. AB_only contains the individuals in both A and B not found in C. ABC_overlap contains individuals who have membership in all three groups.
AB_overlap = A & B  #compute intersection of set A & set BAC_overlap = A & CBC_overlap = B & CABC_overlap = A & B & CA_rest = A - AB_overlap - AC_overlap #see left graphicB_rest = B - AB_overlap - BC_overlapC_rest = C - AC_overlap - BC_overlapAB_only = AB_overlap - ABC_overlap   #see right graphicAC_only = AC_overlap - ABC_overlapBC_only = BC_overlap - ABC_overlap
These variables can now be used to generate the Venn using the matplotlib_venn package. When customizing to your data, keep track of the group order!
from collections import Counterfrom matplotlib_venn import venn2, venn3import matplotlib.pyplot as pltsets = Counter()               #set order A, B, C   sets['100'] = len(A_rest)      #100 denotes A on, B off, C off sets['010'] = len(B_rest)      #010 denotes A off, B on, C offsets['001'] = len(C_rest)      #001 denotes A off, B off, C on sets['110'] = len(AB_only)     #110 denotes A on, B on, C offsets['101'] = len(AC_only)     #101 denotes A on, B off, C on sets['011'] = len(BC_only)     #011 denotes A off, B on, C on sets['111'] = len(ABC_overlap) #011 denotes A on, B on, C onlabels = ('Group A', 'Group B', 'Group C')  plt.figure(figsize=(7,7)) ax = plt.gca() venn3(subsets=sets, set_labels=labels, ax=ax,set_colors=          ('darkviolet','deepskyblue','blue'),alpha=0.7)    plt.show()
This diagram is nice, but it looks more amateur than professional. To jazz it up, we can customize just a several aspects of the visual. First, we can modify the set label colors. See the bolded section of added code.
plt.figure(figsize=(7,7))ax = plt.gca()colors = ['darkviolet','deepskyblue','blue']v = venn3(subsets=sets, set_labels=labels, ax=ax,set_colors=          colors,alpha=0.7)   i = 0for text in v.set_labels:  text.set_color(colors[i])  i+=1plt.show()
Now that the set label colors correspond with the circle sections, we can also modify the number font. For additional clarity, we can change the color, fontsize, and fontweight of the subset label on the Venn diagram.
plt.figure(figsize=(7,7))ax = plt.gca()colors = ['darkviolet','deepskyblue','blue']v = venn3(subsets=sets, set_labels=labels, ax=ax,set_colors=          colors,alpha=0.7)   i = 0for text in v.set_labels:  text.set_color(colors[i])  i+=1for text in v.subset_labels:  text.set_color('white')  text.set_fontsize(16)  text.set_fontweight('bold')plt.show()
Once satisfied with these fonts, we might wish to eliminate the colored labels altogether for a more formal effect. Create the legend by generating two lists, handles and labels.
Handles is generated by iterating through the patches of the Venn using the get_patch_id()method in the Venn3 class and adding the components to an empty list. In my experience, it is best to print the handles list on the graphic, and manually write the label list to match.
plt.figure(figsize=(7,7))ax = plt.gca()v = venn3(subsets=sets, set_labels=('','',''), ax=ax,set_colors=          colors,alpha=0.7)  for text in v.subset_labels:  text.set_color('white')  text.set_fontsize(16)  text.set_fontweight('bold')h = [] for i in sets:    h.append(v.get_patch_by_id(i)) l = ['A only','C only','B only','A & C shared','A & B shared',     'B & C shared','A & B & C shared']ax.legend(handles=h, labels=l, title="Legend",loc='lower left')  plt.title('Membership across Three Groups')plt.show()
Remove elements of the legend that are self-explanatory, redundant, or unimportant. This will guide your audience to the remaining elements you wish to emphasize. To do this, we manually modify the handles and labels lists generated above.
Please note the handles list hhas a way of shifting around when counts change. Conveniently, I could call slices of hand l using h[0:3] and l[0:3] for this example. However, if you need to eliminate color patches from the centers of these two lists, you can apply the del h[2]operator to remove elements at specific indexes from handles and labels prior to calling them as arguments in ax.lengend(). For instance, that would remove the element at index 2 from list h. Just remember to keep the lengths of these lists equal.
plt.figure(figsize=(7,7)) ax = plt.gca() v = venn3(subsets=sets, set_labels=('','',''), ax=ax,             set_colors=colors, alpha=0.7) for text in v.subset_labels:       text.set_color('white')       text.set_fontsize(16)       text.set_fontweight('bold')  ax.legend(handles=h[0:3], labels=l[0:3], title="Legend",          loc='lower left')  plt.title('Membership across Three Groups') plt.show()
These methods can also be applied to 2-way diagrams, which are substantially simpler. The notation of the venn2 class follows that of venn3. All of the above methods can be applied to this base class using the same format.
sets = Counter()               #set order A, B   sets['10'] = len(A-AB_overlap) #10 denotes A on, B off sets['01'] = len(B-AB_overlap) #01 denotes A off, B on sets['11'] = len(AB_overlap)   #11 denotes A on, B on labels = ('Group A', 'Group B')  plt.figure(figsize=(7,7)) ax = plt.gca() venn2(subsets=sets, set_labels=labels, ax=ax,set_colors=          ('darkviolet','deepskyblue'),alpha=0.7) plt.title('Membership across Two Groups') plt.show()
That concludes this tutorial.
To some, customizing text content, placement, color, size, or weight in graphics may seem unnecessary. For practical use, it is. However, in formal presentation settings, a clean, clear graphic is crucial. If a picture speaks 1,000 words, use these techniques to make them count.
For more, check out the full notebook on GitHub. Interested in related articles? Read more from the author.