Python - plt.savefig in a nested for loop - python

I would like to create a pdf containing 12 figures/subplot processed in a nested 'for' loop.
The 12 figures are produced from 4 metrics applied to 3 different variables (3*4=12).
Here is the code that I am using:
#########VARIABLES#############################
df = pd.read_csv('scores.csv',delimiter=',')
sta='Jony'
cvars=['Qle','Qh','NEE']
exps=['ctr','lai']
expsC={'ctr':'k','lai':'b'}
stats=['MBE','SD','NME','pcorr']
##################FUNCTION#########################
def extract_score(df,station,cvar,exp,score):
a=df[(df.station == station) & (df.cvar == cvar) &
(df.exp == exp) ]
return a[score].values[0]
def extract_score_exp(df,station,cvar,score,expS=None):
out=[]
if expS is None:
exps=np.unique(df.exp)
else:
expsf=np.unique(df.exp)
exps=[]
for ee in expsf:
if expS in ee:
exps.append(ee)
for exp in exps:
out.append(extract_score(df,station,cvar,exp,score))
return exps,out
def get_val_exp(exps,pos=1):
return [float(bb.split('_')[pos]) for bb in exps]
#####################PLOT###################################
for j,cvar in enumerate(cvars):
fig=plt.figure()
ax1 = plt.subplot2grid((3,1), (j,0))
for i,stat in enumerate(stats):
ax2 = plt.subplot2grid((1,4), (0,i))
for exp in exps:
xscore0=extract_score(df,sta,cvar,exp,stat)
xx0=250
expsP,xscore=extract_score_exp(df,sta,cvar,stat,expS='_'+exp)
xx=get_val_exp(expsP)
ax2.plot(xx,xscore,'.',c=expsC[exp])
ax2.plot(xx0,xscore0,'x',c=expsC[exp])
ax.set_title(stat,fontsize=8)
plt.show()
plt.savefig('/home/david/test_pals/result/output/test.pdf')
plt.close()
The code runs fine and the ´plt.show()´ command actually shows what I want, as seen on the following picture:
But unfortunately, the ´plt.savefig´ command just save the last 1*4 subplot produced:
I tried different place in the loop for the ´plt.savefig´ but I did not succeed to save the 4*3 figures in one pdf. Does anyone has indication why?

Related

More flexibility in defining input arguments for a function in python?

I have the following function which I'm using to run analysis.
When I set run_analysis_1 = True, it should run the whole first piece of analysis, which outputs plots 1, 2 and 3. When I set run_analysis_2 = True, it should run the whole second piece of analysis, which outputs plots 4, 5 and 6.
def save_locally(self,
run_all_analysis: bool = False,
run_analysis_1: bool = False,
run_analysis_2: bool = False):
if run_analysis_1 = True
plot_1 # some function that creates plot 1 + saves to local folder
plot_2 # some function that creates plot 2 + saves to local folder
plot_3 # some function that creates plot 3 + saves to local folder
if run_analysis_2 = True
plot_4 # some function that creates plot 4 + saves to local folder
plot_5 # some function that creates plot 5 + saves to local folder
plot_6 # some function that creates plot 6 + saves to local folder
I would like a lot of flexibility in choosing what plots I would like to run, such that, I am able to:
Run the whole of a piece of analysis (for example, run all components of analysis 1 to output plots 1,2 and 3)
Run a part of the analysis (for example, just run plot 1 from analysis 1)
So it looks something like the below...
save_locally(run_analysis_1.plot_1=True, run_analysis_2.all=True)
Is there a way to do this?
Something like this might work for you;
Store all plots from an analysis in its own class
Custom all attribute to store all defined plots in one list
Change signature of save_locally to take *args
Would let you call your function pretty cleanly like this:
save_locally(Analysis1.plot1, Analysis1.plot2, Analysis2.all)
from itertools import chain
class _Analysis:
all = ...
def __init_subclass__(cls, **kwargs):
cls.all = [value for key, value in cls.__dict__.items() if not key.startswith("_")]
class Analysis1(_Analysis):
plot1 = "a"
plot2 = "b"
plot3 = "c"
class Analysis2(_Analysis):
plot4 = "d"
plot5 = "e"
plot6 = "f"
def save_locally(*plots):
plots = chain(*plots) # Flatten - lets us write Analysis.all without *
for plot in plots:
print(plot, end=" ") # Do whatever with plot
save_locally(Analysis1.plot1, Analysis1.plot2, Analysis2.all)
>>> a b d e f

Multi-Line Graph: Stuck with the json-dict format for the lines

Hello I am totally new to using Python for data visualisation, I have this json response:
{
"max365": 83.87,
"current365": 83.87,
"min365": 75.29,
"max180": 76.94,
"current180": 76.94,
"min180": 56.43,
"max90": 98.66,
"current90": 98.66,
"min90": 63.29,
"max30": 138.14,
"current30": 136,
"min30": 66.77,
"max14": 156.93,
"current14": 122.88,
"min14": 72.56,
"max7": 168.9,
"current7": 122.68,
"min7": 74.08,
"max0": 267.5,
"current0": 81.28,
"min0": 36.07 }
max, current and min are the lines I would like to plot on a multi-line graph, but I am struggling with the data in this date/time-grouping format with the response.
I've added a screenshot of a graph here that I am essentially trying to reverse engineer:
I've seen some useful posts for generic line graphs, but my issue here is mostly linking all the max/current/mins onto their own lines whilst I have 0/7/14/30/90/180/365 grouping/intersecting each of them in the response.
Hope I've explained it well enough. Any help would be greatly appreciated.
Python standard library has a json module, you need to import just the loads method, and massage a little bit the data.
… and here it is the code — I'd like to underline that we need to split the labels to get out the real label and the sequence info(¿are the numbers time? I made an educated guess), so for each label we construct a list of lists, each element a time and a value, then we sort the lists inside each labelled list and finally we plot the three lines.
from matplotlib.pyplot import subplots
from json import loads
def split_num(s):
num = []
for c in reversed(s):
if c.isdigit():
num += c
else:
break
if num:
return s[:-len(num)], ''.join(reversed(num))
else:
return s, ''
json = '''{
"max365": 83.87,"current365": 83.87,"min365": 75.29,
"max180": 76.94,"current180": 76.94,"min180": 56.43,
"max90": 98.66,"current90": 98.66,"min90": 63.29,
"max30": 138.14,"current30": 136,"min30": 66.77,
"max14": 156.93,"current14": 122.88,"min14": 72.56,
"max7": 168.9,"current7": 122.68,"min7": 74.08,
"max0": 267.5,"current0": 81.28,"min0": 36.07 }'''
jdict = loads(json)
data = {}
for k in jdict:
name, num = split_num(k)
data[name] = data.setdefault(name, []) + [[int(num), float(jdict[k])]]
for k in data: data[k] = sorted(data[k])
fig, ax = subplots()
for k in data:
ax.plot(*zip(*data[k]), label=k)
ax.legend()
fig.show()

Excel to PDF export using python: Getting split graphs

While converting from EXCEL (which contains Text and graphs) to PDF, I am getting the following in PDF report(one graph broke in two different pages).
Actual:
Expected:
'''code'''
def make_plot(self,chart_data):
#see report function: add_chart(report,chart_data) that built chart_data
try:
subplot(111)
subplots_adjust(bottom=0.3)
file = tempfile.mktemp(suffix=".png")
clf() #clear plot data
title(chart_data[1][0])
xlabel(chart_data[1][1])
ylabel(chart_data[1][2])
x=chart_data[2]
xcount=len(chart_data[2])
ycount=xcount-3
y = list()
iter=0
for ydata in chart_data:
if iter>=3: #y data starts on 3 array
y=ydata[2]
plot(x,y,color=chart_data[iter][1], label=chart_data[iter][0])
## y[:] = []
iter=iter+1
legend(loc=(-0.15,-0.35))
savefig(file, bbox_inches="tight", facecolor='w', dpi=120)
self.insert_image(file) #insert image from file into Excel
except:
ErrStr = "Error: make_plot(self,chart_data), " + chart_data[1][0]
print ErrStr
self.insert_comment(["COMMENT",ErrStr])
print sys.exc_info()
self.pass_status = 0
finally:
if(os.path.exists(file)):
os.remove(file)
def add_chart(report,chart_data):
#report created by ReportSetupTest("name",list) dict [TestName][pf]
#add_chart(report,[["Title","X Axis","Y Axis"], [x1,x2,xn],["ledged 1","color 1",[y1,y2,yn]],#["ledged 2","color 2",[2y1,2y2,2yn]],["ledged n","color n",[ny1,ny2,nyn]]])
chartlist =list()
chartlist=["CHART"]
for dat in chart_data:
chartlist.append(dat)
report[len(report) - 1]['pf'].append(chartlist)
How to make actual like Expected? Is it because of code or pdf problem? The images're displayed properly in EXCEL but the problem only gets in PDF. Can someone suggest me what to do?

Why doesn't this code save my figures with titles?

I'm producing some figures with the following code:
def boxplot_data(self,parameters_file,figure_title):
data = pandas.read_csv(parameters_file)
header = data.keys()
number_of_full_subplots = len(header)/16
remainder = len(header)-(16*number_of_full_subplots)
try:
for i in range(number_of_full_subplots+1):
fig =plt.figure(i)
txt = fig.suptitle(figure_title+' (n='+str(len(data[header[0]]))+') '+'Page '+str(i)+' of '+str(number_of_full_subplots),fontsize='20')
txt.set_text(figure_title+' (n='+str(len(data[header[0]]))+') '+'Page '+str(i)+' of '+str(number_of_full_subplots))
for j in range(16):
plt.ioff()
plt.subplot(4,4,j)
plt.boxplot(data[header[16*i+j]])
plt.xlabel('')
mng=plt.get_current_fig_manager()
mng.window.showMaximized()
plt.savefig(str(i)+'.png',bbox_inches='tight',orientation='landscape')
plt.close(fig)
plt.ion()
except IndexError:
txt = fig.suptitle(figure_title+' (n='+str(len(data[header[0]]))+') '+'Page '+str(i)+' of '+str(number_of_full_subplots),fontsize='20')
txt.set_text(figure_title+' (n='+str(len(data[header[0]]))+') '+'Page '+str(i)+' of '+str(number_of_full_subplots))
print '{} full figures were created and 1 partially filled \
figure containing {} subplots'.format(number_of_full_subplots,remainder)
This produces and saves the figures to file in the properly formatted manner however, no matter what I do the code seems to bypass the fig.suptitle line(s) and consequently I can't give my figure a title. Apologies if it seems there is a lot going on in this function that I haven't explained but does anybody have an explanation as to why this code refuses to give my figures titles?
Your problem is not that suptitle is bypassed, but that you are never saving the figure that you call suptitle on. All your calls to savefig are within the inner loop and as such are saving only the subplots. You can actually watch this happening if you open the png file while your code is running - you see each of the 16 sub axes being added one by one.
Your code looks unnecessarily complicated. For instance, I don't think you need to use ion and ioff. Here is a simple example of how to do what I think you want, followed by a translation of your code to fit that (Obviously i can't test, because I don't have your data)
import matplotlib.pyplot as plt
test_y=range(10)
test_x=[8,13,59,8,81,2,5,6,2,3]
def subplotsave_test():
for i in range(5):
fig = plt.figure(i)
txt = fig.suptitle('Page '+str(i)+' of '+str(5),fontsize='20')
for j in range(16):
plt.subplot(4,4,j+1)
plt.plot(test_y,test_x)
plt.savefig(str(i)+'.png',bbox_inches='tight',orientation='landscape')
if __name__ == '__main__':
subplotsave_test()
One tip I have found works for me - do a plt.show() wherever you intend to save the figure and ensure it looks like you want beforehanad and then replace that call with plt.savefig()
Possible translation of your function
def boxplot_data(self,parameters_file,figure_title):
data = pandas.read_csv(parameters_file)
header = data.keys()
number_of_full_subplots = len(header)/16
remainder = len(header)-(16*number_of_full_subplots)
for i in range(number_of_full_subplots+1)
fig =plt.figure(i)
fig.suptitle(figure_title+' (n='+str(len(data[header[0]]))+') '+'Page '+str(i)+' of '+str(number_of_full_subplots),fontsize='20')
for j in range(16):
plt.subplot(4,4,j+1)
if 16*i + j < len(header):
plt.boxplot(data[header[16*i+j]])
plt.xlabel('')
#You might want the showMaximized() call here - does nothing
#on my machine but YMMV
else:
print '{} full figures were created and 1 partially filled \
figure containing {} subplots'.format(number_of_full_subplots,remainder)
break
plt.savefig(str(i)+'.png',bbox_inches='tight',orientation='landscape')
plt.close(fig)

error in dynamic bar chart generation on python

Going by the this example,
http://matplotlib.org/examples/pylab_examples/barchart_demo.html
I wanted to generated the dynamic bar chart.so far I have following script.
import sys
import matplotlib.pyplot as plt
import numpy as np
groups = int(sys.argv[1])
subgroup = int(sys.argv[2])
fig, ax = plt.subplots()
index = np.arange(groups)
print index
bar_width = 1.0 / (subgroup + 1)
print bar_width
mainlist = []
for x in range(0, groups):
#print x
templist = []
for num in range(0, subgroup):
templist.append(num+1)
#print templist
mainlist.append(templist)
print mainlist
for cnt in range(0,subgroup):
plt.bar(index + (bar_width * cnt), mainlist[cnt], bar_width)
plt.savefig('odd_bar_chart.png')
This works fine when i pass same values for groups and subgroup,
> odd_bar_chart.py 3 3
> odd_bar_chart.py 2 2
but if i pass different values like this,
odd_bar_chart.py 3 2
odd_bar_chart.py 2 3
it gives following error
AssertionError: incompatible sizes: argument 'height' must be length {first argument} or scalar
Now I dont know hw height comes in picture ?
can anybody tell me whats wrong here ?
Take a look at the docs for plt.bar. Here the first two arguments are left and height referring the value of the left hand side of the bar and it's height.
Your error message is informing you that the second argument, height should be either the same length as the first or a scalar (single value).
The error:
In your iteration at the end you plot the height mainlist[cnt] against the left locations index + (bar_width * cnt). Clearly you are trying to adjust the x location to spatially separate the bar plots using the bar_with*cnt so this is a scalar. The length then of the left is given by index, which is generated from index = np.arange(groups) and so will have length group. But the length of the heights is given by subgroup, this is done when templist (which has length subgroup) is appended to mainlist.
So your error comes in the way you are generating your data. It is usually better to either stick something in by hand (as they have done in the example you referenced), or use something form numpy.random to generate a set of random numbers.

Categories