Why am I getting "AttributeError: 'tuple' object has no attribute 'savefig'"? - python

Edit 3/15/2017 12:00 PM CDT: I have managed to fix the error in the program and complete the program as it was designed. I would like to thank berna1111 and TigerhawkT3 for their answer submissions, as they allowed me to complete this program. Thanks again, Stack Overflow!
I am attempting to save a series of array-built histograms (arrays made with numpy and histograms using matplotlib) to .png type files. I am receiving the following error message:
Traceback (most recent call last):
File "C:/Users/Ryan/PycharmProjects/NWS/weather_data.py", line 475, in <module>
figure1.savefig("{}_temperature.png".format(filename))
AttributeError: 'tuple' object has no attribute 'savefig'
The section the error refers to is below:
figure1 = plt.hist(temperature_graph_array, color="blue")
figure2 = plt.hist(feelslike_graph_array, color="blue")
figure3 = plt.hist(windspeed_graph_array, color="blue")
figure4 = plt.hist(windgustspeed_graph_array, color="blue")
figure5 = plt.hist(pressure_graph_array, color="blue")
figure6 = plt.hist(humidity_graph_array, color="blue")
figure1.savefig("{}_temperature.png".format(filename), format='png')
figure2.savefig("{}_feelslike.png".format(filename), format='png')
figure3.savefig("{}_windspeed.png".format(filename), format='png')
figure4.savefig("{}_windgustspeed.png".format(filename), format='png')
figure5.savefig("{}_pressure.png".format(filename), format='png')
figure6.savefig("{}_humidity.png".format(filename), format='png')
Why am I receiving this error, and how can I fix it? If someone could let me know I would greatly appreciate it.
Notes:
I have done some google searching and found a few similar errors, but none where the figure was interpreted as a tuple. I do not understand where the tuple part is coming from.
The "_graph_array" items in the histogram creation steps are arrays of dimensions 10 long, 1 tall. 10 total items inside, designated as type Float.
The "filename" variable in the saving step represents a string containing the date and time.

From the documentation for matplotlib.pyplot.hist:
The return value is a tuple (n, bins, patches) or ([n0, n1, ...], bins, [patches0, patches1,...]) if the input contains multiple data.
From the documentation for matplotlib.pyplot.savefig:
Save the current figure.
It looks like you should call savefig in the same way you call hist, not on the result of the hist call.
plt.savefig("{}_temperature.png".format(filename), format='png')
...

I've adapted your code and took the liberty to change the several lines creating a figure by list in comprehension of for loops:
import matplotlib.pyplot as plt
# should be equal when using .pylab
import numpy.random as rnd
# generate_data
n_points = 1000
temperature_graph_array = rnd.random(n_points)
feelslike_graph_array = rnd.random(n_points)
windspeed_graph_array = rnd.random(n_points)
windgustspeed_graph_array = rnd.random(n_points)
pressure_graph_array = rnd.random(n_points)
humidity_graph_array = rnd.random(n_points)
list_of_data = [temperature_graph_array,
feelslike_graph_array,
windspeed_graph_array,
windgustspeed_graph_array,
pressure_graph_array,
humidity_graph_array]
list_of_names = ['temperature',
'feelslike',
'windspeed',
'windgustspeed',
'pressure',
'humidity']
# create the figures:
#figure1 = plt.figure()
#figure2 = plt.figure()
#figure3 = plt.figure()
#figure4 = plt.figure()
#figure5 = plt.figure()
#figure6 = plt.figure()
#list_of_figs = [figure1, figure2, figure3, figure4, figure5, figure6]
## could be:
list_of_figs = [plt.figure() for i in range(6)]
# create the axis:
#ax1 = figure1.add_subplot(111)
#ax2 = figure2.add_subplot(111)
#ax3 = figure3.add_subplot(111)
#ax4 = figure4.add_subplot(111)
#ax5 = figure5.add_subplot(111)
#ax6 = figure6.add_subplot(111)
#list_of_axis = [ax1, ax2, ax3, ax4, ax5, ax6]
## could be:
list_of_axis = [fig.add_subplot(111) for fig in list_of_figs]
# plot the histograms
# notice `plt.hist` returns a tuple (n, bins, patches) or
# ([n0, n1, ...], bins, [patches0, patches1,...]) if the input
# contains multiple data
#hist1 = ax1.hist(temperature_graph_array, color="blue")
#hist2 = ax2.hist(feelslike_graph_array, color="blue")
#hist3 = ax3.hist(windspeed_graph_array, color="blue")
#hist4 = ax4.hist(windgustspeed_graph_array, color="blue")
#hist5 = ax5.hist(pressure_graph_array, color="blue")
#hist6 = ax6.hist(humidity_graph_array, color="blue")
#list_of_hists = [hist1, hist2, hist3, hist4, hist5, hist6]
## could be:
list_of_hists = []
for i, ax in enumerate(list_of_axis):
list_of_hists.append(ax.hist(list_of_data[i], color="blue"))
filename = 'output_graph'
for i, fig in enumerate(list_of_figs):
name = list_of_names[i].capitalize()
list_of_axis[i].set_title(name)
fig.tight_layout()
fig.savefig("{}_{}.png".format(filename,name), format='png')
Will not post the resulting figures, but this gives me 6 little .png files in the same folder as the script.
Even better, you can use a function to do all that to your data:
def save_hist(data, name, filename):
fig = plt.figure()
ax = fig.add_subplot(111)
ax.hist(data, color="blue")
ax.set_title(name)
fig.tight_layout()
fig.savefig("{}_{}.png".format(filename,name), format='png')
plt.close(fig)
filename = 'output_graph_2'
for data, name in zip(list_of_data, list_of_names):
save_hist(data, name, filename)

Related

Overlapping Text in Animation in Python

I'm making Terror Attacks analysis using Python. And I wanted make an animation. I made it but I have a problem the text above the animation overlaps in every frame. How can I fix it?
fig = plt.figure(figsize = (7,4))
def animate(Year):
ax = plt.axes()
ax.clear()
ax.set_title('Terrorism In Turkey\n'+ str(Year))
m5 = Basemap(projection='lcc',resolution='l' ,width=1800000, height=900000 ,lat_0=38.9637, lon_0=35.2433)
lat_gif=list(terror_turkey[terror_turkey['Year']==Year].Latitude)
long_gif=list(terror_turkey[terror_turkey['Year']==Year].Longitude)
x_gif,y_gif=m5(long_gif,lat_gif)
m5.scatter(x_gif, y_gif,s=[Death+Injured for Death,Injured in zip(terror_turkey[terror_turkey['Year']==Year].Death,terror_turkey[terror_turkey['Year']==Year].Injured)],color = 'r')
m5.drawcoastlines()
m5.drawcountries()
m5.fillcontinents(color='coral',lake_color='aqua', zorder = 1,alpha=0.4)
m5.drawmapboundary(fill_color='aqua')
ani = animation.FuncAnimation(fig,animate, list(terror_turkey.Year.unique()), interval = 1500)
ani.save('animation_tr.gif', writer='imagemagick', fps=1)
plt.close(1)
filename = 'animation_tr.gif'
video = io.open(filename, 'r+b').read()
encoded = base64.b64encode(video)
HTML(data='''<img src="data:image/gif;base64,{0}" type="gif" />'''.format(encoded.decode('ascii')))
Output:
#JohanC's recommendation in comments resolved my problem:
Did you consider creating the axes the usual way, as in fig, ax = plt.subplots(figsize = (7,4)) (in the main code, not inside the animate function)? And leaving out the call to plt.axes()?

Combining a text update with n curves being updated using matplotlib animation

I have n curves that I draw using matplotlib's animation. Thanks to a previous question and the answer to it, this works well. Now I want to add some text in the plot which is continuously updated, basically the frame number, but I have no idea how to combine that object with the iterable of artists my animate function needs to return.
Here is my code:
import matplotlib.animation as anim
import matplotlib.pyplot as plt
import numpy as np
tracks = {}
xdata = {}
ydata = {}
n_tracks = 2
n_waypts = 100
for ii in range(n_tracks):
# generate fake data
lat_pts = np.linspace(10+ii*1,20+ii*1,n_waypts)
lon_pts = np.linspace(10+ii*.5,20+ii*.5,n_waypts)
tracks[str(ii)] = np.array( [lat_pts, lon_pts] )
xdata[str(ii)] = []
ydata[str(ii)] = []
fig = plt.figure()
ax1 = fig.add_subplot( 1,1,1, aspect='equal', xlim=(0,30), ylim=(0,30) )
plt_tracks = [ax1.plot([], [], marker=',', linewidth=1)[0] for _ in range(n_tracks)]
plt_lastPos = [ax1.plot([], [], marker='o', linestyle='none')[0] for _ in range(n_tracks)]
plt_text = ax1.text(25, 25, '')
def animate(i):
# x and y values to be plotted
for jj in range(n_tracks):
xdata[str(jj)].append( tracks[str(jj)][1,i] )
ydata[str(jj)].append( tracks[str(jj)][0,i] )
# update x and y data
for jj in range(n_tracks):
plt_tracks[jj].set_data( xdata[str(jj)], ydata[str(jj)] )
plt_lastPos[jj].set_data( xdata[str(jj)][-1], ydata[str(jj)][-1] )
plt_text.set_text('{0}'.format(i))
return plt_tracks + plt_lastPos
anim = anim.FuncAnimation( fig, animate, frames=n_waypts, interval=20, blit=True, repeat=False )
plt.show()
Simply changing the return statement to something like return (plt_tracks + plt_lastPos), plt_text or return (plt_tracks + plt_lastPos), plt_text, does not work. So how do I combine those artists correctly?
The animate function must return an iterable of artists (where an artist is a thing to draw as a result of a plot-call for example). plt_tracks is such an iterable, as well as plt_lastPost. plt_text, however, is a single artist. A possible solution to make the code work is thus changing the return statement to
return plt_tracks + plt_lastPos + [plt_text]
Alternatively, one could also write
return tuple(plt_tracks + plt_lastPos) + (plt_text,)

Double the amount of subplots when using twinx() in matplotlib

Here's my chart:
Unfortunately, this is there too, right below:
This is the code:
fig,ax1 = plt.subplots(6,1, figsize=(20,10),dpi=300)
fig2,ax2 = plt.subplots(6,1, figsize=(20,10),dpi=300)
for index, val in enumerate(datedf.columns):
g = ax1[index].plot(datedf.index, datedf[val], color=colors[index])
ax1[index].set(ylim=[-100,6500])
ax2[index] = ax1[index].twinx()
a = ax2[index].plot(qtydf.index, qtydf[val], color=colors[index], alpha=0.5)
ax2[index].set(ylim=[200,257000])
I tried this answer but I got an error on the first line (too many values to unpack)
Can anyone explain why?
You generate 2 figures, so you end up with 2 figures.
Instead you should do something like:
fig, axes = plt.subplots(6,1, figsize=(20,10),dpi=300)
for index, val in enumerate(datedf.columns):
ax1 = axes[index]
g = ax1.plot(datedf.index, datedf[val], color=colors[index])
ax1.set(ylim=[-100,6500])
ax2 = ax1.twinx()
ax2.plot(qtydf.index, qtydf[val], color=colors[index], alpha=0.5)
ax2.set(ylim=[200,257000])
NB. The code is untested as I don't have the original dataset.

"figure.max_open_warning" in pyplot... "May consume too much memory"

My script works, but when running I receive the error that is displayed in the heading. I do not understand why bc I use plt.clf() after every plot that I open and save. Below is an example of one of the instances I open (and close) a figure
...
roi1 = img[700:830, 730:835]
roiStats1(roi1)
plt.imshow(roi1)
plt.colorbar()
plt.savefig('roi1_'+file[:20]+'.tif')
plt.clf()
###### Make ROI sequential to be able to "bin" the axes #############
roi1y =roi1.reshape(13650, 1)
roi1y_df = pd.DataFrame(roi1y).reset_index().rename(columns= {0: 'Intensity'})
###### Plot Y axis variation of ROI for edge or lane gradient ###########
inlet_bins = np.linspace(-1, 13650, num=10, endpoint=True)
y_binlabels = [1,2,3,4,5,6,7,8,9]
roi1y_df['bins'] = pd.cut(roi1y_df['index'], inlet_bins, labels = y_binlabels)
roi1y_df['Intensity'] = roi1y_df['Intensity'].astype(float)
fig, ax = plt.subplots()
sns.boxplot(x='bins', y='Intensity', data=roi1y_df, ax = ax).set_title('Y-Axis Variation'+file[:20])
plt.savefig('YvarPlot_Inlet_'+file[:20]+'.tif')
plt.clf()
...
So I plt.clf() each time I open and save a figure, but I still get the memory warning
try plt.close() instead of plt.clf()

Animate points with labels with matplotlib

I've got an animation with lines and now I want to label the points.
I tried plt.annotate() and I tried plt.text() but the labes don't move.
This is my example code:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def update_line(num, data, line):
newData = np.array([[1+num,2+num/2,3,4-num/4,5+num],[7,4,9+num/3,2,3]])
line.set_data(newData)
plt.annotate('A0', xy=(newData[0][0],newData[1][0]))
return line,
fig1 = plt.figure()
data = np.array([[1,2,3,4,5],[7,4,9,2,3]])
l, = plt.plot([], [], 'r-')
plt.xlim(0, 20)
plt.ylim(0, 20)
plt.annotate('A0', xy=(data[0][0], data[1][0]))
# plt.text( data[0][0], data[1][0], 'A0')
line_ani = animation.FuncAnimation(fig1, update_line, 25, fargs=(data, l),
interval=200, blit=True)
plt.show()
Can you help me please?
My next step is:
I have vectors with origin in these Points. These vectors change their length and their direction in each animation step.
How can I animate these?
Without animation this works:
soa =np.array( [ [data[0][0],data[1][0],F_A0[i][0][0],F_A0[i][1][0]],
[data[0][1],data[1][1],F_B0[i][0][0],F_B0[i][1][0]],
[data[0][2],data[1][2],F_D[i][0][0],F_D[i][1][0]] ])
X,Y,U,V = zip(*soa)
ax = plt.gca()
ax.quiver(X,Y,U,V,angles='xy',scale_units='xy',scale=1)
First thanks a lot for your fast and very helpful answer!
My Vector animation problem I have solved with this:
annotation = ax.annotate("C0", xy=(data[0][2], data[1][2]), xycoords='data',
xytext=(data[0][2]+1, data[1][2]+1), textcoords='data',
arrowprops=dict(arrowstyle="->"))
and in the 'update-function' I write:
annotation.xytext = (newData[0][2], newData[1][2])
annotation.xy = (data[0][2]+num, data[1][2]+num)
to change the start and end position of the vectors (arrows).
But what is, wehn I have 100 vectors or more? It is not practicable to write:
annotation1 = ...
annotation2 = ...
.
:
annotation100 = ...
I tried with a list:
...
annotation = [annotation1, annotation2, ... , annotation100]
...
def update(num):
...
return line, annotation
and got this error:
AttributeError: 'list' object has no attribute 'axes'
What can I do? Have you any idea?
I'm coming here from this question, where an annotation should be updated that uses both xy and xytext. It appears that, in order to update the annotation correctly, one needs to set the attribute .xy of the annotation to set the position of the annotated point and to use the .set_position() method of the annotation to set the position of the annotation. Setting the .xytext attribute has no effect -- somewhat confusing in my opinion. Below a complete example:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
fig, ax = plt.subplots()
ax.set_xlim([-1,1])
ax.set_ylim([-1,1])
L = 50
theta = np.linspace(0,2*np.pi,L)
r = np.ones_like(theta)
x = r*np.cos(theta)
y = r*np.sin(theta)
line, = ax.plot(1,0, 'ro')
annotation = ax.annotate(
'annotation', xy=(1,0), xytext=(-1,0),
arrowprops = {'arrowstyle': "->"}
)
def update(i):
new_x = x[i%L]
new_y = y[i%L]
line.set_data(new_x,new_y)
##annotation.xytext = (-new_x,-new_y) <-- does not work
annotation.set_position((-new_x,-new_y))
annotation.xy = (new_x,new_y)
return line, annotation
ani = animation.FuncAnimation(
fig, update, interval = 500, blit = False
)
plt.show()
The result looks something like this:
In case that versions matter, this code has been tested on Python 2.7 and 3.6 with matplotlib version 2.1.1, and in both cases setting .xytext had no effect, while .set_position() and .xy worked as expected.
You have the return all objects that changed from your update function. So since your annotation changed it's position you should return it also:
line.set_data(newData)
annotation = plt.annotate('A0', xy=(newData[0][0],newData[1][0]))
return line, annotation
You can read more about matplotlib animations in this tutorial
You should also specify the init function so that the FuncAnimation knows which elements to remove from the plot when redrawing on the first update. So the full example would be:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# Create initial data
data = np.array([[1,2,3,4,5], [7,4,9,2,3]])
# Create figure and axes
fig = plt.figure()
ax = plt.axes(xlim=(0, 20), ylim=(0, 20))
# Create initial objects
line, = ax.plot([], [], 'r-')
annotation = ax.annotate('A0', xy=(data[0][0], data[1][0]))
annotation.set_animated(True)
# Create the init function that returns the objects
# that will change during the animation process
def init():
return line, annotation
# Create the update function that returns all the
# objects that have changed
def update(num):
newData = np.array([[1 + num, 2 + num / 2, 3, 4 - num / 4, 5 + num],
[7, 4, 9 + num / 3, 2, 3]])
line.set_data(newData)
# This is not working i 1.2.1
# annotation.set_position((newData[0][0], newData[1][0]))
annotation.xytext = (newData[0][0], newData[1][0])
return line, annotation
anim = animation.FuncAnimation(fig, update, frames=25, init_func=init,
interval=200, blit=True)
plt.show()
I think I figured out how to animate multiple annotations through a list. First you just create your annotations list:
for i in range(0,len(someMatrix)):
annotations.append(ax.annotate(str(i), xy=(someMatrix.item(0,i), someMatrix.item(1,i))))
Then in your "animate" function you do as you have already written:
for num, annot in enumerate(annotations):
annot.set_position((someMatrix.item((time,num)), someMatrix.item((time,num))))
(You can write it as a traditional for loop as well if you don't like the enumerate way). Don't forget to return the whole annotations list in your return statement.
Then the important thing is to set "blit=False" in your FuncAnimation:
animation.FuncAnimation(fig, animate, frames="yourframecount",
interval="yourpreferredinterval", blit=False, init_func=init)
It is good to point out that blit=False might slow things down. But its unfortunately the only way I could get animation of annotations in lists to work...

Categories

Resources