Currently I came up with the following solution to fix the issue with the colours:
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as mticks
from matplotlib.font_manager import FontProperties
import numpy as np
def draw_pie(dist,
xpos,
ypos,
size,
ax=None):
if ax is None:
fig, ax = plt.subplots(figsize=(7,6))
# The colors, corresponding to the values 1, 2 and 3:
# 1 is tab:blue
# 2 is tab:orange
# 3 is tab:green
# Of course, you can change this
colors = ['tab:blue', 'tab:orange', 'tab:green']
# for incremental pie slices
cumsum = np.cumsum(dist)
cumsum = cumsum/ cumsum[-1]
pie = [0] + cumsum.tolist()
for r1, r2, i in zip(pie[:-1], pie[1:], range(0, len(dist))):
# If no counts present, skip this one
if dist[i] == 0:
continue
angles = np.linspace(2 * np.pi * r1, 2 * np.pi * r2)
x = [0] + np.cos(angles).tolist()
y = [0] + np.sin(angles).tolist()
xy = np.column_stack([x, y])
ax.scatter([xpos], [ypos], marker=xy, s=size, color=colors[i])
return ax
#colors = {'Group A':'red', 'Group B':'green', 'Group C':'blue', 'Group D':'yellow', 'Group E':'yellow', 'Group F':'yellow', 'Group G':'yellow', 'Group H':'yellow'}
# Read dataset
dataset = pd.read_csv('dataset.csv')
fig, ax = plt.subplots(figsize=(9,5))
for (x,y), d in dataset.groupby(['Column 1','Column 2']):
# Only interested in the 'Column 3' column, as this one
# contains the values 1-2-3
d = d['Colour Group Desired']
# Count how often each value (1-2-3) occurs and store
# this in a list (count for value i located at list index
# i-1)
dist = list()
for i in [1,2,3]:
dist.append(d[d==i].count())
# Call your draw_pie function
draw_pie(dist, x, y, 500, ax=ax)
ax.yaxis.set_major_locator(mticks.MultipleLocator(1))
full = plt.Rectangle((-0.05, 4.25), 2.10, 2, color='g', alpha=0.15)
partial = plt.Rectangle((-0.05, 2.25), 2.10, 2, color='orange', alpha=0.15)
low = plt.Rectangle((-0.05, 0), 2.10, 2.25, color='r', alpha=0.15)
ax.add_patch(full)
ax.add_patch(partial)
ax.add_patch(low)
plt.xticks(fontsize=10)
plt.yticks(fontsize=8)
plt.ylim([0, 6.75])
plt.tight_layout()
plt.show()
Small note to start with, I changed all sizes by a factor of 10 such that the plot fits on my screen (e.g. the figsize). You probably want to use your original values again, but this doesn't matter for the question anyway.
The first change I made was to the loop body of the for (x,y), d in dataset.groupby(['Column 1','Column 2']) loop. Instead of using dist = d['Column 3'].value_counts(), I create an empty array. Subsequently, I loop over the values 1, 2 and 3. In each iteration, I check how many rows match the specific value and append the outcome to the list. In this way, I end up with a list of size 3, in which the first element corresponds to the amount of rows that equal 1, the second element corresponds to the amount of rows that equal 2 and the third element corresponds to the amount of rows that equal 3. The advantage is that I can also keep track of values that occur 0 times.
Secondly, I changed the draw_pie function a bit. However, since I do not fully understand the meaning of a group in terms of the colour, I commented out the colors dictionary. It looks as if 1 always corresponds to Group A, 2 always corresponds to Group B and 3 always corresponds to Group C. I made use of this observation and defined another colors variable (in the draw_pie function). Instead of a dictionary, colors is now a list (where the first element corresponds to the value 1, the second element corresponds to the value 2 and the third element corresponds to the value 3). I changed your for loop from for r1, r2 in zip(pie[:-1], pie[1:]) to for r1, r2, i in zip(pie[:-1], pie[1:], range(0, len(dist))). The advantage is that I can now use the iteration variable i to get the proper color from the colors list. In addition, I added a small if statement which checks if there are exactly 0 occurences. If that is the case, I just skip the remainder of the loop and draw nothing (if you don't skip these cases, it will draw a very thin line instead. Try this for yourself by removing it).
If I run the code, I get the following result:

Unfortunately, I was not successful in adding the labels. I tried using the Axes.text method, but I couldn't get the labels to be placed at the proper locations.
Edit
I decided to change the body of the draw_pie function. In this new version, we draw an Axes instance at the desired (xpos, ypos) location. This involves some transformations: first a transformation from data coordinates to display coordinates and subsequently a transformation from display coordinates to figure coordinates. See this tutorial for an explanation. The advantage is that we can now plot a pie chart inside the created axes using the Axes.pie method. This method has some nice options, such as adding labels!
However, there is a catch. Before we start drawing the pie charts, we need to already fix the xlim and the ylim values of the main Axes. If we don't do this (and do it after plotting the pie charts), the pie charts will no longer be located at the proper positions. Therefore, I have moved the code which sets the xlim and ylim values before the first time we call the draw_pie function. I also removed to call to plt.tight_layout(), as this will (unfortunately) also cause the pie charts to no longer be located at the proper locations.
As a small side note, I changed the manner in which the background colors are drawn. Instead of using patches, I now use the Axes.axhspan method. With this method, you can still control the y-locations, but the width will extend infinitely (meaning that the colors remain if you scroll left/right). If you don't want this, you can remove it again :).
See the new version of the code below (I note once more, I changed all sizes such that it fits on my computer screen. You probably want to substitute your original sizes again):
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as mticks
from matplotlib.font_manager import FontProperties
import numpy as np
def draw_pie(dist,
xpos,
ypos,
size,
ax=None,
fig=None):
if ax is None:
fig, ax = plt.subplots(figsize=(7,6))
# Transform xpos and ypos to figure coordinates by the
# following steps:
# 1. First transform xpos and ypos to data coordinates
# 2. Transform the data coordinates back to figure coordinates
xfig, yfig = ax.transData.transform((xpos, ypos))
trans_to_fig = fig.transFigure.inverted()
xfig, yfig = trans_to_fig.transform((xfig, yfig))
# Calculate figure coordinates from the desired pie chart size
# given in pixels
size = trans_to_fig.transform((size, 0))[0]
# Add axes at data coordinates xpos and ypos. On these axes,
# the pie chart will be plotted
ax_pie = fig.add_axes([xfig-0.5*size, yfig-0.5*size, size, size])
# Plot the pie chart (with some special options)
textprops = {'color' : 'w',
'fontweight' : 'bold',
'fontsize' : 10,
'va' : 'center',
'ha' : 'center'
}
labels = [str(i) if not i == 0 else "" for i in dist]
labeldistance = 0.5
if sum(not x==0 for x in dist) == 1: # Ensures we plot the label in the center
labeldistance = 0.0 # if we have only one entry
ax_pie.pie(dist, labels=labels, labeldistance=labeldistance, textprops=textprops)
return ax_pie
# Read dataset
dataset = pd.read_csv('dataset.txt')
fig, ax = plt.subplots(figsize=(9,5))
# Important, limits must be set before calling draw_pie function!
# (Otherwise, the data coordinates will change, which will break
# the transform sequence inside the draw_pie function!)
ax.set_xlim([-0.2, 2.2]) # Tweak these values for the desired output
ax.set_ylim([0, 6.75])
# Make sure the string from 'Column 1' is displayed again
ax.set_xticks([0, 1, 2])
ax.set_xticklabels(['First Line', 'Second Line', 'Third Line'])
# Remainder of formatting
ax.yaxis.set_major_locator(mticks.MultipleLocator(1))
plt.xticks(fontsize=10)
plt.yticks(fontsize=8)
# Define float values for 'Column 1' (easier for transformation,
# we have already put the text back there using ax.set_xticklabels)
column1_to_float = {'First Line':0, 'Second Line':1, 'Third Line':2}
for (x,y), d in dataset.groupby(['Column 1','Column 2']):
# Only interested in the 'Column 3' column, as this one
# contains the values 1-2-3
d = d['Colour Group Desired']
# Count how often each value (1-2-3) occurs and store
# this in a list (count for value i located at list index
# i-1)
dist = list()
for i in [1,2,3]:
dist.append(d[d==i].count())
# Call your draw_pie function
draw_pie(dist, column1_to_float[x], y, 100, ax=ax, fig=fig)
# Plot the colours (note: using axhspan, they extend the full
# horizontal direction, even while scrolling)
ax.axhspan(0 , 2.25, fc='r' , ec=None, alpha=0.15)
ax.axhspan(2.25, 4.25, fc='orange', ec=None, alpha=0.15)
ax.axhspan(4.25, 6.75, fc='g' , ec=None, alpha=0.15)
# Unfortunately, tight_layout can no longer be used. If we do use this,
# the pie charts will no longer be at the proper positions...
# plt.tight_layout()
plt.show()
If I run this code, I get the following output:

Just to mention it, you can decrease the size of the pie charts by adjusting the size argument to the draw_pie function (I just liked the output above :) ). But keep in mind that in this case, you also want to decrease the fontsize specified in the textprops dictionary in the body of the draw_pie function. As an example (size = 63, fontsize=7):
