This question already has an answer here:
legend alignment in matplotlib
(1 answer)
Closed 2 years ago.
In the legend of my plot, I want to display the (best) values of the functions as well, next to the 'normal' legend.
My current try:
Which I have coded like this:
for j, pol in enumerate(pols):
val = vidics[1][pol]
axis[1,i].axhline(val, label=f'{pol_label[j]}: {val:.1f}', color='r', linestyle = st[j])
sns.lineplot(x= 'variable',y='value',data=pd.DataFrame(r_FI_3).melt(), label = f'FI: {score_FI_3:.1f}', ax=axis[1,i])
sns.lineplot(x= 'variable',y='value',data=pd.DataFrame(r_PO_3).melt(), label = f'PO: {score_PO_3:.1f}', ax=axis[1,i])
The for loop creates the horizontal lines.
The sns lines create the blue and orange lines.
What I would like to have is that the scores (the values in the legend) are right aligned.
Doing this manually by adding spaces in the fstrings is not feasible as I've many of these small plots filled in a loop. I tried adding blanks/fills in the fstring, e.g. the following label for the second sns line:
label = f'{"PO": < 10}: {score_PO_3:.1f}'
Which adds blanks untill the string got a length of 10 (I think?). I thought If I would do that for all the strings, the right would align out but as not all the characters in the string have the same width this also does not work and looks ugly.
Any ideas?
Furthermore, a the larger code, for reference:
pols = ['optimal', 'preventive', 'corrective']
pol_label = ['VIoptm', 'VIprev', 'VIcorr']
vidics = [vi2, vi3, vi4]
labels = ['4', '5', '6', '7']
dics2_PO = [s4_PO_2, s5_PO_2, s6_PO_2, s7_PO_2]
dics2_FI = [s4_FI_2, s5_FI_2, s6_FI_2, s7_FI_2]
dics3_PO = [s4_PO_3, s5_PO_3, s6_PO_3, s7_PO_3]
dics3_FI = [s4_FI_3, s5_FI_3, s6_FI_3, s7_FI_3]
dics4_PO = [s4_PO_4, s5_PO_4, s6_PO_4, s7_PO_4]
dics4_FI = [s4_FI_4, s5_FI_4, s6_FI_4, s7_FI_4]
figlabel = [['a', 'b', 'c', 'd'], ['e', 'f', 'g', 'h'], ['i', 'j', 'k','l']]
fig, axis = plt.subplots(3,4, figsize=(17,10))
st = ['-', '-.' , ':']
for i in range(4):
# 2 comp
dic2FI = dics2_FI[i]
dic2PO = dics2_PO[i]
r_FI_2 = dic2FI['ar']
r_PO_2 = dic2PO['ar']
t_PO_2 = np.array(dic2PO['t']).sum()
best_r_FI = np.array(dic2FI['best_r'])
best_r_PO = np.array(dic2PO['best_r'])
score_FI_2 = best_r_FI.mean()
score_PO_2 = best_r_PO.mean()
sd_FI_2 = best_r_FI.std()
sd_PO_2 = best_r_PO.std()
print(f'2comp, agent {labels[i]} FI : {score_FI_2:.1f} +- {sd_FI_2:.1f} ')
print(f'2comp, agent {labels[i]} PO : {score_PO_2:.1f} +- {sd_PO_2:.1f} ')
print(f'2comp, agent {labels[i]} PO , time: {t_PO_2:.0f}')
for j, pol in enumerate(pols):
val = vidics[0][pol]
axis[0,i].axhline(val, label=f'{pol_label[j]}: {val:.1f}', color='r', linestyle = st[j])
sns.lineplot(x= 'variable',y='value',data=pd.DataFrame(r_FI_2).melt(), label = f'FI: {score_FI_2:.1f}', ax=axis[0,i])
sns.lineplot(x= 'variable',y='value',data=pd.DataFrame(r_PO_2).melt(), label = f'PO: {score_PO_2:.1f}', ax=axis[0,i])
axis[0,i].set_ylim((-30,-8))
axis[0,i].legend(loc='lower right')
axis[0,i].set_xlabel('Training Iterations')
axis[0,i].set_xticks([4,9])
axis[0,i].set_xticklabels(['50k', '100k'])
axis[0,i].set_title(f'{figlabel[0][i]}) 2 comp, agent {labels[i]}')
if i==0:
axis[0,i].set_ylabel('Average Reward')
else:
axis[0,i].set_ylabel('')
# 3 comp
dic3FI = dics3_FI[i]
dic3PO = dics3_PO[i]
r_FI_3 = dic3FI['ar']
r_PO_3 = dic3PO['ar']
t_PO_3 = np.array(dic3PO['t']).sum()
best_r_FI = np.array(dic3FI['best_r'])
best_r_PO = np.array(dic3PO['best_r'])
score_FI_3 = best_r_FI.mean()
score_PO_3 = best_r_PO.mean()
sd_FI_3 = best_r_FI.std()
sd_PO_3 = best_r_PO.std()
print(f'3comp, agent {labels[i]} FI : {score_FI_3:.1f} +- {sd_FI_3:.1f} ')
print(f'3comp, agent {labels[i]} PO : {score_PO_3:.1f} +- {sd_PO_3:.1f} ')
print(f'3comp, agent {labels[i]} PO , time: {t_PO_3:.0f}')
for j, pol in enumerate(pols):
val = vidics[1][pol]
axis[1,i].axhline(val, label=f'{pol_label[j]}: {val:.1f}', color='r', linestyle = st[j])
sns.lineplot(x= 'variable',y='value',data=pd.DataFrame(r_FI_3).melt(), label = f'FI: {score_FI_3:.1f}', ax=axis[1,i])
sns.lineplot(x= 'variable',y='value',data=pd.DataFrame(r_PO_3).melt(), label = f'PO: {score_PO_3:.1f}', ax=axis[1,i])
axis[1,i].set_ylim((-60,-15))
axis[1,i].legend(loc='lower right')
axis[1,i].set_xlabel('Training Iterations')
axis[1,i].set_xticks([4,9])
axis[1,i].set_xticklabels(['100k', '200k'])
axis[1,i].set_title(f'{figlabel[1][i]}) 3 comp, agent {labels[i]}')
if i==0:
axis[1,i].set_ylabel('Average Reward')
else:
axis[1,i].set_ylabel('')
# 4 comp
dic4FI = dics4_FI[i]
dic4PO = dics4_PO[i]
r_FI_4 = dic4FI['ar']
r_PO_4 = dic4PO['ar']
t_PO_4 = np.array(dic4PO['t']).sum()
best_r_FI = np.array(dic4FI['best_r'])
best_r_PO = np.array(dic4PO['best_r'])
score_FI_4 = best_r_FI.mean()
score_PO_4 = best_r_PO.mean()
sd_FI_4 = best_r_FI.std()
sd_PO_4 = best_r_PO.std()
print(f'4comp, agent {labels[i]} FI : {score_FI_4:.1f} +- {sd_FI_4:.1f} ')
print(f'4comp, agent {labels[i]} PO : {score_PO_4:.1f} +- {sd_PO_4:.1f} ')
print(f'4comp, agent {labels[i]} PO , time: {t_PO_4:.0f}')
for j, pol in enumerate(pols):
val = vidics[2][pol]
axis[2,i].axhline(val, label=f'{pol_label[j]}: {val:.1f}', color='r', linestyle = st[j])
sns.lineplot(x= 'variable',y='value',data=pd.DataFrame(r_FI_4).melt(), label = f'FI: {score_FI_4:.1f}', ax=axis[2,i])
sns.lineplot(x= 'variable',y='value',data=pd.DataFrame(r_PO_4).melt(), label = f'PO: {score_PO_4:.1f}', ax=axis[2,i])
axis[2,i].set_ylim((-70,-20))
axis[2,i].legend(loc='lower right')
axis[2,i].set_xlabel('Training Iterations')
axis[2,i].set_xticks([4,9])
axis[2,i].set_xticklabels(['200k', '400k'])
axis[2,i].set_title(f'{figlabel[2][i]}) 4 comp, agent {labels[i]}')
if i==0:
axis[2,i].set_ylabel('Average Reward')
else:
axis[2,i].set_ylabel('')
plt.tight_layout()
Which creates the following figure:
Take a look at where you assign the label value:
label=f'{pol_label[j]}: {val:.1f}'
In this format all is left aligned. It's comparabale with e.g.:
print('{:<2}: {:<8}'.format(*['label','value']))
which means something like, 'left align everything, but let the first word have an available space of 2 (counting from left), and the second a space of 8'
To fix I would try to keep the first word left-aligned but the second right-aligned, given an appropriate available space. In the above line, simply change the second sign, and possibly the space:
print('{:<2}: {:>8}'.format(*['label','value']))
to achieve (output):
label: value
(Label is left aligned, value is right-aligned)
reference
Related
For a project at work I'm working on a code that reads a csv-file and generates a plot.
My problem is, I work with multiple csv-files but all of them contain 10-40 rows in the beginning, filled with device and sensor information.
I would wish for my code to detect where the first line of values are and to start reading the values from there into my array. But since my experience with Python is very low, I couldnt find a good solution.
If you can recommend me specific methods or change my code, feel free to comment.
Thanks to everyone taking their time to help me
import matplotlib.pyplot as plt
import csv
a = []
b = []
c = []
d = []
e = []
with open('PATH','r') as csvfile:
lines = csv.reader(csvfile, delimiter=',')
for row in lines:
a.append(float(row [0]))
b.append(float(row [1]))
#c.append(float(row [2]))
#d.append(float(row [3]))
#e.append(float(row [4]))
f = plt.figure()
f.set_figwidth(12)
f.set_figheight(8)
#plt.plot(X-Achse, Y-Achse, linewidth=* , color = ' ', label = " ")
plt.plot(a, b, linewidth=0.35, color = 'b', label = "Sensor 1")
#plt.plot(a, c, linewidth=0.35, color = 'g', label = "Sensor 2")
plt.title('Pressure Report', fontsize = 20)
plt.xlabel('Time(s)')
plt.ylabel('Pressure(bar)')
plt.grid()
plt.legend()
plt.show()
You can skip the lines using conditional statements as below:
count = 1
for row in lines:
if (count < 10 and count > 40):
a.append(float(row [0]))
b.append(float(row [1]))
count += 1
What is working for me are the following changes. It may not be the fastest and best solution, but it does what its supposed to.
import matplotlib.pyplot as plt
import csv
path = 'PATH'
line_number = 0
list_of_results = []
count = 1
a = []
b = []
c = []
d = []
e = []
with open(path, 'r') as read_obj:
for line in read_obj:
line_number += 1
if "0.000000000" in line:
list_of_results.append((line_number))
firstline = list_of_results[0]-1
with open(path,'r') as csvfile:
lines = csv.reader(csvfile, delimiter=',')
for row in lines:
if (count > firstline):
a.append(float(row [0]))
b.append(float(row [1]))
#c.append(float(row [2]))
#d.append(float(row [3]))
#e.append(float(row [4]))
count += 1
f = plt.figure()
f.set_figwidth(12)
f.set_figheight(8)
#plt.plot(X-Achse, Y-Achse, linewidth=* , color = ' ', label = " ")
plt.plot(a, b, linewidth=0.35, color = 'b', label = "Sensor 1")
#plt.plot(a, c, linewidth=0.35, color = 'g', label = "Sensor 2")
plt.title('Pressure Report', fontsize = 20)
plt.xlabel('Time(s)')
plt.ylabel('Pressure(bar)')
#plt.axis([x_min, x_max, y_min, y_max])
#plt.axis([350, 380, -6, 2.2])
plt.grid()
plt.legend()
plt.show()
I have a dataset that is a list of lists.
Each list is a category to be plotted as a box plot.
Each list has a list of up to 9 components to be plotted into subplots.
The functions I am using is below was based on this answer. I pulled it out of my work and added some mock data. Should be a minimal example below.
neonDict = {
0:0, 1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8
}
import matplotlib as mpl
import matplotlib.pyplot as plt
def coloredBoxPlot(axis, data,edgeColor,fillColor):
bp = axis.boxplot(data,vert=False,patch_artist=True)
for element in ['boxes', 'whiskers', 'fliers', 'means', 'medians', 'caps']:
plt.setp(bp[element], color=edgeColor)
for patch in bp['boxes']:
patch.set(facecolor=fillColor)
return bp
def plotCalStats(data, prefix='Channel', savedir=None,colors=['#00597c','#a8005c','#00aeea','#007d50','#400080','#e07800'] ):
csize = mpl.rcParams['figure.figsize']
cdpi = mpl.rcParams['figure.dpi']
mpl.rcParams['figure.figsize'] = (12,8)
mpl.rcParams['figure.dpi'] = 1080
pkdata = []
labels = []
lstyles = []
fg, ax = plt.subplots(3,3)
for pk in range(len(neonDict)):
px = pk // 3
py = pk % 3
ax[px,py].set_xlabel('Max Pixel')
ax[px,py].set_ylabel('')
ax[px,py].set_title(str(neonDict[pk]) + ' nm')
pkdata.append([])
for cat in range(len(data)):
bp = ''
for acal in data[cat]:
for apeak in acal.peaks:
pkdata[apeak].append(acal.peaks[apeak][0])
for pk in range(9):
px = pk // 3
py = pk % 3
bp = coloredBoxPlot(ax[px,py], pkdata[pk], colors[cat], '#ffffff')
if len(data[cat]) > 0:
#print(colors[cat])
#print(bp['boxes'][0].get_edgecolor())
labels.append(prefix+' '+str(cat))
lstyles.append(bp['boxes'][0])
fg.legend(lstyles,labels)
fg.suptitle('Calibration Summary by '+prefix)
fg.tight_layout()
if savedir is not None:
plt.savefig(savedir + 'Boxplots.png')
plt.show()
mpl.rcParams['figure.figsize'] = csize
mpl.rcParams['figure.dpi'] = cdpi
return
class acal:
def __init__(self):
self.peaks = {}
for x in range(9):
self.peaks[x] = (np.random.randint(20*x,20*(x+1)),)
mockData = [[acal() for y in range(100)] for x in range(6)]
#Some unused channels
mockData[2] = []
mockData[3] = []
mockData[4] = []
plotCalStats(mockData)
So the issue is that the plot colors do not match the legend. Even if I restrict the data to only add a label if data exists (ensuring thus there is no issue with calling boxplots with an empty data set and not getting an appropriate PathPatch.
The printouts verify the colors are correctly stored in the PathPatch. (I can add my digits -> hex converter) if that is questioned.
Attached is the output. One can see I get a purple box but no purple in the legend. Purple is the 4th category which is empty.
Any ideas why the labels don't match the actual style? Thanks much!
EDITS:
To address question on 'confusing'.
I have six categories of data, each category is coming from a single event. Each event has 9 components. I want to compare all events, for each individual component, for each category on a single plot as shown below.
Each subplot is a individual component comprised from the series of data for each categorical (Channel).
So the link I have provided, (like I said, is adapted from) shows how to create a single box plot on one axis for 2 data sets. I've basically done the same thing for 6 data sets on 9 axis, where 3 data sets are empty (but don't have to be, I did it to illustrate the issue. If I have all 6 data sets there, how can you tell the colors are messed up?????)
Regarding the alpha:
The alphas are always 'ff' when giving only RGB data to matplotlib. If I call get_edgecolors, it will return a tuple (RGBA) where A = 1.0.
See commented out print statement.
EDIT2:
If I restrict it down to a single category, it makes the box plot view less confusing.
Single Example (see how box plot color is orange, figure says it's blue)
All colors off
Feel like this used to work....
Uncertain how the error presented as it did, but the issue has to do with reformatting the data before creating the box plot.
By removing pkdata.append([]) during the creation of the subplots before looping the categories and adding:
pkdata = [[],[],[],[],[],[],[],[],[]] during each iteration of the category loop fixed the issue. The former was sending in all previous channel data...
Output is now better. Full sol attached.
Likely, since the plot uses data from pkdata, the empty channel (data[cat]) plotted previous data (from data[cat-1]) as that was still in pkdata (actually, all previous data[cat] was still in pkdata) which was then plotted. I only check data[cat] for data on each loop to add to the legend. The legend was set up for channels 0,1,5, for example.. but we saw data for channel: 0 as 0, 0+1 as 1, 0+1 as 2, 0+1 as 3, 0+1 as 4, 0+1+5 as 5... thus channel 4 (purple) had data to plot but wasn't added to the legend. Giving the impression of 'misaligned' legends but rather unlegend data...
The single channel data is actually all 6 channels overlapping, the final channel 5 color being orange, overlapping all previous, namely the original channel 0 data to whom the data belongs and was properly added to the legend.
neonDict = {
0:0, 1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8
}
import matplotlib as mpl
import matplotlib.pyplot as plt
def getHex(r,g,b,a=1.0):
colors = [int(r * 255 ),int(g * 255 ),int(b * 255 ),int(a * 255) ]
s = '#'
for x in range(4):
cs = hex(colors[x])
if len(cs) == 3:
cs = cs + '0'
s += cs.replace('0x','')
return s
def getRGB(colstr):
try:
a = ''
r = int(colstr[1:3],16) / 255
g = int(colstr[3:5],16) / 255
b = int(colstr[5:7],16) / 255
if len (colstr) == 7:
a = 1.0
else:
a = int(colstr[7:],16) / 255
return (r,g,b,a)
except Exception as e:
print(e)
raise e
return
def compareHexColors(col1,col2):
try:
## ASSUME #RBG or #RBGA
## If less than 7, append the ff for the colors
if len(col1) < 9:
col1 += 'ff'
if len(col2) < 9:
col2 += 'ff'
return col1.lower() == col2.lower()
except Exception as e:
raise e
return False
def coloredBoxPlot(axis, data,edgeColor,fillColor):
bp = axis.boxplot(data,vert=False,patch_artist=True)
for element in ['boxes', 'whiskers', 'fliers', 'means', 'medians', 'caps']:
plt.setp(bp[element], color=edgeColor)
for patch in bp['boxes']:
patch.set(facecolor=fillColor)
return bp
def plotCalStats(data, prefix='Channel', savedir=None,colors=['#00597c','#a8005c','#00aeea','#007d50','#400080','#e07800'] ):
csize = mpl.rcParams['figure.figsize']
cdpi = mpl.rcParams['figure.dpi']
mpl.rcParams['figure.figsize'] = (12,8)
mpl.rcParams['figure.dpi'] = 1080
pkdata = []
labels = []
lstyles = []
fg, ax = plt.subplots(3,3)
for pk in range(len(neonDict)):
px = pk // 3
py = pk % 3
ax[px,py].set_xlabel('Max Pixel')
ax[px,py].set_ylabel('')
ax[px,py].set_title(str(neonDict[pk]) + ' nm')
for cat in range(len(data)):
bp = ''
pkdata = [[],[],[],[],[],[],[],[],[]]
for acal in data[cat]:
for apeak in acal.peaks:
pkdata[apeak].append(acal.peaks[apeak][0])
for pk in range(9):
px = pk // 3
py = pk % 3
bp = coloredBoxPlot(ax[px,py], pkdata[pk], colors[cat], '#ffffff')
if len(data[cat]) > 0:
print(compareHexColors(colors[cat],getHex(*bp['boxes'][0].get_edgecolor())))
labels.append(prefix+' '+str(cat))
lstyles.append(bp['boxes'][0])
fg.legend(lstyles,labels)
fg.suptitle('Calibration Summary by '+prefix)
fg.tight_layout()
if savedir is not None:
plt.savefig(savedir + 'Boxplots.png')
plt.show()
mpl.rcParams['figure.figsize'] = csize
mpl.rcParams['figure.dpi'] = cdpi
return
class acal:
def __init__(self,center):
self.peaks = {}
for x in range(9):
self.peaks[x] = [10*x + (center) + (np.random.randint(10)-1)/2.0,0,0]
mockData = [[acal(x) for y in range(1000)] for x in range(6)]
#Some unused channels
mockData[2] = []
mockData[3] = []
mockData[4] = []
plotCalStats(mockData)
I am learning with python code and I have some issues:
https://github.com/Slothfulwave612/Football-Analytics-Using-Python/blob/master/03.%20Analyzing%20Event%20Data/pass_map.py
My dubt is really simple:
I would like to apply a for expresion in order to apply the pass code to multiple football matches.
import matplotlib.pyplot as plt
import json
from pandas.io.json import json_normalize
from FCPython import createPitch
pitch_length_X = 120
pitch_width_Y = 80
(fig,ax) = createPitch(pitch_length_X, pitch_width_Y,'yards','gray')
## the code integrated in order to analyze multiple matches
P1TMP = [16205, 16131, 16265]
for i in P1TMP:
## match id for our El Clasico
match_id = int(i)
home_team = 'Barcelona'
player_name = 'Lionel Andrés Messi Cuccittini'
## this is the name of our event data file for
## our required El Clasico
file_name = str(match_id) + '.json'
## loading the required event data file
my_data = json.load(open('/content/drive/My Drive/20200515 CHIRINGUITO/events/' + file_name, 'r', encoding='utf-8'))
## get the nested structure into a dataframe
## store the dataframe in a dictionary with the match id as key
df = json_normalize(my_data, sep='_').assign(match_id = file_name[:-5])
## making the list of all column names
column = list(df.columns)
## all the type names we have in our dataframe
all_type_name = list(df['type_name'].unique())
## creating a data frame for pass
## and then removing the null values
## only listing the player_name in the dataframe
pass_df = df.loc[df['type_name'] == 'Pass', :].copy()
pass_df.dropna(inplace=True, axis=1)
pass_df = pass_df.loc[pass_df['player_name'] == player_name, :]
## creating a data frame for ball receipt
## removing all the null values
## and only listing Barcelona players in the dataframe
breceipt_df = df.loc[df['type_name'] == 'Ball Receipt*', :].copy()
breceipt_df.dropna(inplace=True, axis=1)
breceipt_df = breceipt_df.loc[breceipt_df['team_name'] == 'Barcelona', :]
pass_comp, pass_no = 0, 0
## pass_comp: completed pass
## pass_no: unsuccessful pass
## iterating through the pass dataframe
for row_num, passed in pass_df.iterrows():
if passed['player_name'] == player_name:
## for away side
x_loc = passed['location'][0]
y_loc = passed['location'][1]
pass_id = passed['id']
summed_result = sum(breceipt_df.iloc[:, 14].apply(lambda x: pass_id in x))
if summed_result > 0:
## if pass made was successful
color = 'blue'
label = 'Successful'
pass_comp += 1
else:
## if pass made was unsuccessful
color = 'green'
label = 'Unsuccessful'
pass_no += 1
## plotting circle at the player's position
shot_circle = plt.Circle((pitch_length_X - x_loc, y_loc), radius=2, color=color, label=label)
shot_circle.set_alpha(alpha=0.2)
ax.add_patch(shot_circle)
## parameters for making the arrow
pass_x = 120 - passed['pass_end_location'][0]
pass_y = passed['pass_end_location'][1]
dx = ((pitch_length_X - x_loc) - pass_x)
dy = y_loc - pass_y
## making an arrow to display the pass
pass_arrow = plt.Arrow(pitch_length_X - x_loc, y_loc, -dx, -dy, width=1, color=color)
## adding arrow to the plot
ax.add_patch(pass_arrow)
## computing pass accuracy
pass_acc = (pass_comp / (pass_comp + pass_no)) * 100
pass_acc = str(round(pass_acc, 2))
## adding text to the plot
plt.text(20, 85, '{} pass map vs Real Madrid'.format(player_name), fontsize=15)
plt.text(20, 82, 'Pass Accuracy: {}'.format(pass_acc), fontsize=15)
## handling labels
handles, labels = plt.gca().get_legend_handles_labels()
by_label = dict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys(), loc='best', bbox_to_anchor=(0.9, 1, 0, 0),fontsize=12)
## editing the figure size and saving it
fig.set_size_inches(12, 8)
fig.savefig('{} passmap.png'.format(match_id), dpi=200)
## showing the plot
plt.show()
I only have edited the code in order to analayze multiple matches with a for expresion.
P1TMP = [16205, 16131, 16265]
for i in P1TMP:
And the results:
In The first image the result is almost perfect, but the Kind of passes´s filter is not working.
enter image description here
In the second image the passes are a mix of the passes of the first match and the second match. I only want the passes of the second match.
enter image description here
And in the third is the mix of the match nº1 +nº2 + n3º. I need the passes of the third :
enter image description here
Thanks in advance for your support.
Best Regards
So it's combining all the matches onto 1 because the figure is "drawing" on top of the previous one. There's a few other things you need to change too.
The away team will not always be Real Madrid, so make that dynamic
Adjust that in the figure text text so it's not always "vs. Real Madrid"
Save the file as something dynamic so they don't overwrite
Instead of doing plt.text to put in the titles (which is fine if you want to annotate at a specific x,y coordinates), use plt.title() and plt.suptitle(). It'll center and just make it a nicer layout of the text
You us i for you match id variable when iterating, but then you don't change/include that in the loop
This is the main issue: (fig,ax) = createPitch(pitch_length_X, pitch_width_Y,'yards','gray')
is what is creating your "blank canvas" to plot on. So this needs to be called before each plot. It's like grabbing a new blank sheet of paper to draw on. If you just use the first initial sheet, then everything will go on that 1 sheet. So move that into your for loop
Code
import matplotlib.pyplot as plt
import json
from pandas.io.json import json_normalize
from FCPython import createPitch
## Note Statsbomb data uses yards for their pitch dimensions
pitch_length_X = 120
pitch_width_Y = 80
## match id for our El Clasico
match_list = [16205, 16131, 16265]
teamA = 'Barcelona' #<--- adjusted here
for match_id in match_list:
## calling the function to create a pitch map
## yards is the unit for measurement and
## gray will be the line color of the pitch map
(fig,ax) = createPitch(pitch_length_X, pitch_width_Y,'yards','gray') #< moved into for loop
player_name = 'Lionel Andrés Messi Cuccittini'
## this is the name of our event data file for
## our required El Clasico
file_name = str(match_id) + '.json'
## loading the required event data file
my_data = json.load(open('Statsbomb/data/events/' + file_name, 'r', encoding='utf-8'))
## get the nested structure into a dataframe
## store the dataframe in a dictionary with the match id as key
df = json_normalize(my_data, sep='_').assign(match_id = file_name[:-5])
teamB = [x for x in list(df['team_name'].unique()) if x != teamA ][0] #<--- get other team name
## making the list of all column names
column = list(df.columns)
## all the type names we have in our dataframe
all_type_name = list(df['type_name'].unique())
## creating a data frame for pass
## and then removing the null values
## only listing the player_name in the dataframe
pass_df = df.loc[df['type_name'] == 'Pass', :].copy()
pass_df.dropna(inplace=True, axis=1)
pass_df = pass_df.loc[pass_df['player_name'] == player_name, :]
## creating a data frame for ball receipt
## removing all the null values
## and only listing Barcelona players in the dataframe
breceipt_df = df.loc[df['type_name'] == 'Ball Receipt*', :].copy()
breceipt_df.dropna(inplace=True, axis=1)
breceipt_df = breceipt_df.loc[breceipt_df['team_name'] == 'Barcelona', :]
pass_comp, pass_no = 0, 0
## pass_comp: completed pass
## pass_no: unsuccessful pass
## iterating through the pass dataframe
for row_num, passed in pass_df.iterrows():
if passed['player_name'] == player_name:
## for away side
x_loc = passed['location'][0]
y_loc = passed['location'][1]
pass_id = passed['id']
summed_result = sum(breceipt_df.iloc[:, 14].apply(lambda x: pass_id in x))
if summed_result > 0:
## if pass made was successful
color = 'blue'
label = 'Successful'
pass_comp += 1
else:
## if pass made was unsuccessful
color = 'red'
label = 'Unsuccessful'
pass_no += 1
## plotting circle at the player's position
shot_circle = plt.Circle((pitch_length_X - x_loc, y_loc), radius=2, color=color, label=label)
shot_circle.set_alpha(alpha=0.2)
ax.add_patch(shot_circle)
## parameters for making the arrow
pass_x = 120 - passed['pass_end_location'][0]
pass_y = passed['pass_end_location'][1]
dx = ((pitch_length_X - x_loc) - pass_x)
dy = y_loc - pass_y
## making an arrow to display the pass
pass_arrow = plt.Arrow(pitch_length_X - x_loc, y_loc, -dx, -dy, width=1, color=color)
## adding arrow to the plot
ax.add_patch(pass_arrow)
## computing pass accuracy
pass_acc = (pass_comp / (pass_comp + pass_no)) * 100
pass_acc = str(round(pass_acc, 2))
## adding text to the plot
plt.suptitle('{} pass map vs {}'.format(player_name, teamB), fontsize=15) #<-- make dynamic and change to suptitle
plt.title('Pass Accuracy: {}'.format(pass_acc), fontsize=15) #<-- change to title
## handling labels
handles, labels = plt.gca().get_legend_handles_labels()
by_label = dict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys(), loc='best', bbox_to_anchor=(0.9, 1, 0, 0), fontsize=12)
## editing the figure size and saving it
fig.set_size_inches(12, 8)
fig.savefig('{} passmap.png'.format(match_id), dpi=200) #<-- dynamic file name
## showing the plot
plt.show()
I'm making a plot to compare band structure calculations from two different methods. This means plotting multiple lines for each set of data. I want to have a set of widgets that controls each set of data separately. The code below works if I only plot one set of data, but I can't get the widgets to work properly for two sets of data.
#!/usr/bin/env python3
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, TextBox
#cols = ['blue', 'red', 'green', 'purple']
cols = ['#3f54bf','#c14142','#59bf3f','#b83fbf']
finam = ['wan_band.dat','wan_band.pwx.dat']
#finam = ['wan_band.dat'] # this works
lbot = len(finam)*0.09 + 0.06
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=lbot)
ax.margins(x=0) # lines go to the edge of the horizontal axes
def setlines(lines, txbx1, txbx2):
''' turn lines on/off based on text box values '''
try:
mn = int(txbx1) - 1
mx = int(txbx2) - 1
for ib in range(len(lines)):
if (ib<mn) or (ib>mx):
lines[ib].set_visible(False)
else :
lines[ib].set_visible(True)
plt.draw()
except ValueError as err:
print('Invalid range')
#end def setlines(cnt, lines, txbx1, txbx2):
def alphalines(lines, valin):
''' set lines' opacity '''
maxval = int('ff',16)
maxval = hex(int(valin*maxval))[2:]
for ib in range(bcnt):
lines[ib].set_color(cols[cnt]+maxval)
plt.draw()
#end def alphalines(lines, valtxt):
lines = [0]*len(finam) # 2d list to hold Line2Ds
txbox1 = [0]*len(finam) # list of Lo Band TextBoxes
txbox2 = [0]*len(finam) # lsit of Hi Band TextBoxes
alslid = [0]*len(finam) # list of Line Opacity Sliders
for cnt, fnam in enumerate(finam):
ptcnt = 0 # point count
fid = open(fnam, 'r')
fiit = iter(fid)
for line in fiit:
if line.strip() == '' :
break
ptcnt += 1
fid.close()
bandat_raw = np.loadtxt(fnam)
bcnt = int(np.round((bandat_raw.shape[0] / (ptcnt))))
print(ptcnt)
print(bcnt)
# get views of the raw data that are easier to work with
kbandat = bandat_raw[:ptcnt,0] # k point length along path
ebandat = bandat_raw.reshape((bcnt,ptcnt,2))[:,:,1] # band energy # k-points
lines[cnt] = [0]*bcnt # point this list element to another list
for ib in range(bcnt):
#l, = plt.plot(kbandat, ebandat[ib], c=cols[cnt],lw=1.0)
l, = ax.plot(kbandat, ebandat[ib], c=cols[cnt],lw=1.0)
lines[cnt][ib] = l
y0 = 0.03 + 0.07*cnt
bxht = 0.035
axbox1 = plt.axes([0.03, y0, 0.08, bxht]) # x0, y0, width, height
axbox2 = plt.axes([0.13, y0, 0.08, bxht])
txbox1[cnt] = TextBox(axbox1, '', initial=str(1))
txbox2[cnt] = TextBox(axbox2, '', initial=str(bcnt))
txbox1[cnt].on_submit( lambda x: setlines(lines[cnt], x, txbox2[cnt].text) )
txbox2[cnt].on_submit( lambda x: setlines(lines[cnt], txbox1[cnt].text, x) )
axalpha = plt.axes([0.25, y0, 0.65, bxht])
alslid[cnt] = Slider(axalpha, '', 0.1, 1.0, valinit=1.0)
salpha = alslid[cnt]
alslid[cnt].on_changed( lambda x: alphalines(lines[cnt], x) )
#end for cnt, fnam in enumerate(finam):
plt.text(0.01, 1.2, 'Lo Band', transform=axbox1.transAxes)
plt.text(0.01, 1.2, 'Hi Band', transform=axbox2.transAxes)
plt.text(0.01, 1.2, 'Line Opacity', transform=axalpha.transAxes)
plt.show()
All the widgets only control the last data set plotted instead of the individual data sets I tried to associate with each widget. Here is a sample output:
Here the bottom slider should be changing the blue lines' opacity, but instead it changes the red lines' opacity. Originally the variables txbox1, txbox2, and alslid were not lists. I changed them to lists though to ensure they weren't garbage collected but it didn't change anything.
Here is the test data set1 and set2 I've been using. They should be saved as files 'wan_band.dat' and 'wan_band.pwx.dat' as per the hard coded list finam in the code.
I figured it out, using a lambda to partially execute some functions with an iterator value meant they were always being evaluated with the last value of the iterator. Switching to functools.partial fixed the issue.
I want to plot the top n features in RandomForestClassifier() in bokeh without specifying the column name explicitly in the y variable.
So firstly, instead of typing the column name in variable y, it can take the column name and value directly from the top feature of the randomclassifier.
y = df['new']
x = df.drop('new', axis=1)
rf = RandomForestClassifier()
rf.fit(x,y)
#Extract the top feature from above and plot in bokeh
source = ColumnDataSource(df)
p1 = figure(y_range=(0, 10))
# below I would like it to use the top feature in RandomClassifier
# instead of explicitly writing the column name, horsePower,
# from the top features column
p1.line(
x = 'x',
y = 'horsePower',
source=source,
legend = 'Car Blue',
color = 'Blue'
)
Instead of specifying the first feature only, or the second feature only, we can build a for loop that plots the n top features in bokeh. I imagine it to be something close to this
for i in range(5):
p.line(x = 'x', y = ???? , source=source,) #top feature in randomClassifier
p.circle(x = 'x', y = ???? , source=source, size = 10)
row = [p]
output_file('TopFeatures')
show(p)
I have already extracted the top 15 features from the RandomForestClassifier of the model and printed the first 15 using
new_rf = pd.Series(rf.feature_importances_,index=x.columns).sort_values(ascending=False)
print(new_rf[:15])
Simply iterate through the index values of pandas series, new_rf, since its index is column names:
# TOP 1 FEATURE
p1.line(
x = 'x',
y = new_rf.index[0],
source = source,
legend = 'Car Blue',
color = 'Blue'
)
# TOP 5 FEATURES
for i in new_rf[:5].index:
output_file("TopFeatures_{}".format(i))
p = figure(y_range=(0, 10))
p.line(x = 'x', y = i, source = source)
p.circle(x = 'x', y = i, source = source, size = 10)
show(p)