I have a class with methods to build some plots. I try to display different plots on one figure. The properties (title, legend...) of the figure are always overwritten by the last plot. I expected that if I have return in my method the behaviour would be different to the method without it, but it seems to not be true.
I would like to figure out what difference makes to have return. The code to illustrate my question is:
import matplotlib.pyplot as plt
import numpy as np
class myClass1(object):
def __init__(self):
self.x = np.random.random(100)
self.y = np.random.random(100)
def plotNReturn1(self):
plt.plot(self.x,self.y,'-*',label='randNxy')
plt.title('Plot No Return1')
plt.legend(numpoints = 1)
def plotNReturn2(self):
plt.plot(self.y,self.x,'-x',label='randNzw')
plt.title('Plot No Return2')
plt.legend(numpoints = 2)
def plotWReturn1(self):
fig = plt.plot(self.x,self.y,'-*',label='randWxy')
fig = plt.title('Plot With Return1')
fig = plt.legend(numpoints = 1)
return fig
def plotWReturn2(self):
fig = plt.plot(self.y,self.x,'-x',label='randWzw')
fig = plt.title('Plot With Return2')
plt.legend(numpoints = 3)
return fig
if __name__=='__main__':
f = myClass1()
p = plt.figure()
p1 = p.add_subplot(122)
p1 = f.plotWReturn1()
p1 = f.plotWReturn2()
print 'method with return: %s: ' % type(p1)
p2 = p.add_subplot(121)
p2 = f.plotNReturn1()
p2 = f.plotNReturn2()
print 'method without return: %s: ' % type(p2)
plt.show()
The only difference I noticed is the type of the output, but I don't know what it means in practice.
method with return: <class 'matplotlib.text.Text'>:
method without return: <type 'NoneType'>:
Is it only about "pythonic" practice or is there anything practical to use any of the style?
Returning a value has only a direct effect for the caller, in this case your __main__ block. If you don't need to reuse some value computed by a function, in your case assigned to p1 or p2, the return doesn't have any impact on behaviour.
Also, series of assignments like
p1 = call1()
p1 = call2()
p1 = call3()
are indicators of bad code style, because only the last value assigned to p1 is going to be available after them.
Anyway, I think you want to plot on subplots, as opposed to the main plot, like so:
import matplotlib.pyplot as plt
import numpy as np
class myClass1(object):
def __init__(self):
self.x = np.random.random(100)
self.y = np.random.random(100)
def plotNReturn1(self, subplot):
subplot.plot(self.x,self.y,'-*',label='randNxy')
subplot.set_title('Plot No Return1')
subplot.legend(numpoints = 1)
def plotNReturn2(self, subplot):
subplot.plot(self.y,self.x,'-x',label='randNzw')
subplot.set_title('Plot No Return2')
subplot.legend(numpoints = 2)
if __name__=='__main__':
f = myClass1()
p = plt.figure()
p1 = p.add_subplot(122)
f.plotNReturn2(p1)
p2 = p.add_subplot(121)
f.plotNReturn2(p2)
plt.show()
Here, subplot is passed to each function, so data should be plotted on it, instead of replacing what you've plotted before.
Python functions return None if they don't have a return statement. Otherwise, they return whatever you tell them to.
In terms of conventions, if a function operates on the arguments passed to it, it is polite to have that function return None. That way, the user knows that the arguments were messed with. (an example of this is list.append -- It modifies the list and returns None).
a = [1,2,3]
print a.append(4) #None
print a #[1, 2, 3, 4]
If you're function isn't going to mess with the stuff passed to it, then it's useful to have it return something:
def square(x):
return x*x
Related
Here is an example plot along with a dataset.
fig, ax = plt.subplots(figsize = (5,5))
x_data = np.arange(0,5,1)
y_data = [10,20,4,1,28]
x_labels = ['Val1', 'Val2', 'A Lengthy', "Unexpected", 'Val5']
ax.bar(x_data, y_data, tick_label = x_labels)
I want to move the xticklabel Unexpected a little to the right. So I thought of using this
for val in ax.get_xticklabels():
if val.get_text() == "Unexpected":
x,y = val.get_position()
print(f'Old Position [{x},{y}]')
val.set_y(-0.03)
val.set_x(x + 0.25)
x,y = val.get_position()
print(f'New Position [{x},{y}]')
Here is the outcome
The label moves downwards but not towards the right.
I want to know why is set_x not working as expected. Is there anything overriding it? I got a solution online that uses transforms. Is that overriding the set_x command?
if your question is on why set_x is not working, you can refer to this post and response from explorerDude on how to use it.
However, if you are trying to get the readability better, a simple solution would be to use rotation of the axis by some degrees.
But, if you require only the Unexpected label to be lower, the below code using set_transform should do the trick... Use dx and dy to move the label by as many pixels as you need.
Code
import matplotlib.transforms as trns
fig, ax = plt.subplots(figsize = (5,5))
x_data = np.arange(0,5,1)
y_data = [10,20,4,1,28]
x_labels = ['Val1', 'Val2', 'A Lengthy', "Unexpected", 'Val5']
ax.bar(x_data, y_data, tick_label = x_labels)
dx = 10/72.; dy = -10/72.
offset = trns.ScaledTranslation(dx, dy, fig.dpi_scale_trans)
for val in ax.get_xticklabels():
if val.get_text() == "Unexpected":
x,y = val.get_position()
val.set_transform(val.get_transform() + offset)
x,y = val.get_position()
Output
As in the link provided by #Redox, the reason is that the call to set_x is overriden when the rendering is done.
It can be seen by making some changes to the custom set_x made by #explorerDude as shown in the link.
import types
SHIFT = 0.1 # Data coordinates
def func(self, x):
print("set_x called with x =", x)
mlt.text.Text.set_x(self, x + SHIFT)
for label in ax.xaxis.get_majorticklabels():
if label.get_text() == "Unexpected":
label.set_x = types.MethodType( lambda self,x: func(self, x), label)
print("mycall")
It's output
mycall
set_x called with x = 3
set_x called with x = 3
set_x called with x = 3
set_x called with x = 3
set_x called with x = 3
set_x called with x = 3
set_x called with x = 3
set_x called with x = 3
set_x called with x = 3
set_x called with x = 3
As seen set_x is called afterwards internally. It's argument is 3 and is calculated internally. So no matter what we write to set_x, it's overriden.
However as seen in func, matplotlib.text.Text.set_x is called with x+SHIFT which changes the position in every call and the final result is as below.
However overriding internal functions seems dangerous and instead using transform as in #Redox's answer or here is better
I have a following function with takes 2 arguments psi,lam and returns 1 array y.
lam=np.arange(0,1,0.1)
psi=np.deg2rad(np.arange(0,361,1))
def test(psi,lam):
y=[]
for i in range(len(lam)):
sin_psi = np.sin(psi)
cos_psi = np.cos(psi)
sin_beta = lam*sin_psi
cos_beta = np.sqrt(1.0 - sin_beta**2)
ssin_pb = sin_psi*sin_beta
y.append((lam*(cos_psi/cos_beta)**2 - ssin_pb)/cos_beta + cos_psi)
plt.plot(psi,y[i])
return y
I would like the function to return range(len(lam))=10 plots of y on the vertical axis against psi on x axis.
However, it seems to be only plotting the same curve multiple times. Not sure what I am missing?
import matplotlib.pyplot as plt
import numpy as np
lam=np.arange(0,1,0.1)
psi=np.deg2rad(np.arange(0,361,1))
def test(angle,var):
sin_psi = np.sin(psi)
cos_psi = np.cos(psi)
sin_beta = var*sin_psi
cos_beta = np.sqrt(1.0 - sin_beta**2)
ssin_pb = sin_psi*sin_beta
return ((var*(cos_psi/cos_beta)**2 - ssin_pb)/cos_beta + cos_psi)
for i in lam:
plt.plot(psi,test(psi,i))
plt.show()
I moved the variable outside of the function, this way you may also use it for other cases. The only other thing is that you should call plt.show() after you're done drawing.
Your code has several problems the main being that the return function was inside the loop interrupting it after the first iteration. Imitating your code structure as closely as possible, we can rewrite the code as:
import numpy as np
import matplotlib.pyplot as plt
def test(psi,lam):
y=[]
for curr_lam in lam:
sin_psi = np.sin(psi)
cos_psi = np.cos(psi)
sin_beta = curr_lam*sin_psi
cos_beta = np.sqrt(1.0 - sin_beta**2)
ssin_pb = sin_psi*sin_beta
val = (curr_lam * (cos_psi/cos_beta)**2 - ssin_pb)/cos_beta + cos_psi
y.append(val)
plt.plot(psi, val)
plt.show()
return y
lam=np.arange(0, 1, 0.1)
psi=np.deg2rad(np.arange(0,361,1))
y = test(psi, lam)
print(y)
Sample output:
As Johan mentioned in the comments, you should also directly iterate over list/arrays. If you need to combine arrays, use
for x1, x2 in zip(arr1, arr2):
If you absolutely need the index value, use
for i, x in enumerate(arr):
I used threading library to plot 2 real-time diagrams using matplotlib.
from fbm import MBM
import matplotlib.pyplot as plt
import threading
plt.style.use('ggplot')
fig, ax = plt.subplots(nrows=2, ncols=1)
def h_90(t):
return 0.9
def h_75(t):
return 0.75
def thread_fgn_9():
x_vec = []
y_vec = []
i = 1
while True:
f = MBM(n=1, hurst=h_90, length=1, method='riemannliouville')
fgn_sample = f.mgn()
x_vec.append(i)
y_vec.append(fgn_sample[0])
i += 1
ax[0].plot(x_vec, y_vec, "g-o")
plt.pause(0.1)
plt.show()
def thread_fgn_75():
x_vec_ = []
y_vec_ = []
i = 1
while True:
f = MBM(n=1, hurst=h_75, length=1, method='riemannliouville')
fgn_sample = f.mgn()
x_vec_.append(i)
y_vec_.append(fgn_sample[0])
i += 1
ax[1].plot(x_vec_, y_vec_, "r-o")
plt.pause(0.2)
plt.show()
if __name__ == "__main__":
plt.ion()
x2 = threading.Thread(target=thread_fgn_75(), name="H_75")
x2.daemon = True
x2.start()
x1 = threading.Thread(target=thread_fgn_9(), name="H_90")
x1.daemon = True
x1.start()
I expect to see to plots being plotted real-time but I see something like below:
can anybody understand what is wrong in my code ?? The code is completed and you can simply just copy/paste in your IDE to run it.
Thanks
================= New change ================
I just changed the main section as below:
if __name__ == "__main__":
x1 = threading.Thread(target=thread_fgn_9(), name="H_90").start()
x2 = threading.Thread(target=thread_fgn_75(), name="H_75").start()
plt.show()
but still the result is the same as before.
======New New change ===============
if __name__ == "__main__":
x1 = threading.Thread(target=thread_fgn_9, name="H_90").start()
x2 = threading.Thread(target=thread_fgn_75, name="H_75").start()
#plt.pause(0.2)
plt.show()
I just erased the parentheses in target=function_name
it seems it is correct but the plot is not showing smoothly. Also I see an error in console like this:
File "/usr/local/lib/python3.9/site-packages/matplotlib/transforms.py", line 312, in xmin
return np.min(self.get_points()[:, 0])
File "<__array_function__ internals>", line 5, in amin
RecursionError: maximum recursion depth exceeded
-------Final Change-----------------------
The best way to do this in matplotlib is below code:
plt.style.use('ggplot')
fig, ax = plt.subplots(nrows=2, ncols=1)
mutex = Lock()
def thread_fgn_9():
print(threading.current_thread().getName())
x_vec = []
y_vec = []
i = 1
while True:
#mutex.acquire()
f = MBM(n=1, hurst=h_90, length=1, method='riemannliouville')
fgn_sample = f.mgn()
x_vec.append(i)
y_vec.append(fgn_sample[0])
i += 1
ax[0].plot(x_vec, y_vec, "g-o")
plt.pause(0.01)
#mutex.release()
def thread_fgn_75():
print(threading.current_thread().getName())
x_vec_ = []
y_vec_ = []
i = 1
while True:
#mutex.acquire()
f = MBM(n=1, hurst=h_75, length=1, method='riemannliouville')
fgn_sample = f.mgn()
x_vec_.append(i)
y_vec_.append(fgn_sample[0])
i += 1
ax[1].plot(x_vec_, y_vec_, "r-o")
plt.pause(0.01)
#mutex.release()
if __name__ == "__main__":
x1 = multiprocessing.Process(target=thread_fgn_9, name="H_90").start()
x2 = multiprocessing.Process(target=thread_fgn_75, name="H_75").start()
plt.show()
I believe the reason is because both processes try to write in one single main plot. In order to have a multiple smooth plot changing over time, we need to take another technique.
thread_fgn_9 is being called and blocking even before it is sent to the thread. Be sure to send the function itself.
plt.pause or plt.show need to be called from the main thread. Additionally, Matplotlib makes no thread safety guarantees in general, so you should avoid this concept entirely unless you know exactly what you are doing. Consider the techniques in this question instead: Fast Live Plotting in Matplotlib / PyPlot
EDIT: I figured out that the Problem always occours if one tries to plot to two different lists of figures. Does that mean that one can not do plots to different figure-lists in the same loop? See latest code for much simpler sample of a problem.
I try to analyze a complex set of data which consists basically about measurements of electric devices under different conditions. Hence, the code is a bit more complex but I tried to strip it down to a working example - however it is still pretty long. Hence, let me explain what you see: You see 3 classes with Transistor representing an electronic device. It's attribute Y represents the measurement data - consisting of 2 sets of measurements. Each Transistor belongs to a group - 2 in this example. And some groups belong to the same series - one series where both groups are included in this example.
The aim is now to plot all measurement data for each Transistor (not shown), then to also plot all data belonging to the same group in one plot each and all data of the same series to one plot. In order to program it in an efficent way without having a lot of loops my idea was to use the object orientated nature of matplotlib - I will have figures and subplots for each level of plotting (initialized in initGrpPlt and initSeriesPlt) which are then filled with only one loop over all Transistors (in MainPlt: toGPlt and toSPlt). In the end it should only be printed / saved to a file / whatever (PltGrp and PltSeries).
The Problem: Even though I specify where to plot, python plots the series plots into the group plots. You can check this yourself by running the code with the line 'toSPlt(trans,j)' and without. I have no clue why python does this because in the function toSPlt I explicetly say that python should use the subplots from the series-subplot-list. Would anyone have an idea to why this is like this and how to solve this problem in an elegent way?
Read the code from the bottom to the top, that should help with understanding.
Kind regards
# -*- coding: utf-8 -*-
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import numpy as np
maxNrVdrain = 2
X = np.linspace(-np.pi, np.pi, 256,endpoint=True)
A = [[1*np.cos(X),2*np.cos(X),3*np.cos(X),4*np.cos(X)],[1*np.tan(X),2*np.tan(X),3*np.tan(X),4*np.tan(X)]]
B = [[2* np.sin(X),4* np.sin(X),6* np.sin(X),8* np.sin(X)],[2*np.cos(X),4*np.cos(X),6*np.cos(X),8*np.cos(X)]]
class Transistor(object):
_TransRegistry = []
def __init__(self,y1,y2):
self._TransRegistry.append(self)
self.X = X
self.Y = [y1,y2]
self.group = ''
class Groups():
_GroupRegistry = []
def __init__(self,trans):
self._GroupRegistry.append(self)
self.transistors = [trans]
self.figlist = []
self.axlist = []
class Series():
_SeriesRegistry = []
def __init__(self,group):
self._SeriesRegistry.append(self)
self.groups = [group]
self.figlist = []
self.axlist = []
def initGrpPlt():
for group in Groups._GroupRegistry:
for j in range(maxNrVdrain):
group.figlist.append(plt.figure(j))
group.axlist.append(group.figlist[j].add_subplot(111))
return
def initSeriesPlt():
for series in Series._SeriesRegistry:
for j in range(maxNrVdrain):
series.figlist.append(plt.figure(j))
series.axlist.append(series.figlist[j].add_subplot(111))
return
def toGPlt(trans,j):
colour = cm.rainbow(np.linspace(0, 1, 4))
group = trans.group
group.axlist[j].plot(trans.X,trans.Y[j], color=colour[group.transistors.index(trans)], linewidth=1.5, linestyle="-")
return
def toSPlt(trans,j):
colour = cm.rainbow(np.linspace(0, 1, 2))
series = Series._SeriesRegistry[0]
group = trans.group
if group.transistors.index(trans) == 0:
series.axlist[j].plot(trans.X,trans.Y[j],color=colour[series.groups.index(group)], linewidth=1.5, linestyle="-", label = 'T = nan, RH = nan' )
else:
series.axlist[j].plot(trans.X,trans.Y[j],color=colour[series.groups.index(group)], linewidth=1.5, linestyle="-")
return
def PltGrp(group,j):
ax = group.axlist[j]
ax.set_title('Test Grp')
return
def PltSeries(series,j):
ax = series.axlist[j]
ax.legend(loc='upper right', frameon=False)
ax.set_title('Test Series')
return
def MainPlt():
initGrpPlt()
initSeriesPlt()
for trans in Transistor._TransRegistry:
for j in range(maxNrVdrain):
toGPlt(trans,j)
toSPlt(trans,j)#plots to group plot for some reason
for j in range(maxNrVdrain):
for group in Groups._GroupRegistry:
PltGrp(group,j)
plt.show()
return
def Init():
for j in range(4):
trans = Transistor(A[0][j],A[1][j])
if j == 0:
Groups(trans)
else:
Groups._GroupRegistry[0].transistors.append(trans)
trans.group = Groups._GroupRegistry[0]
Series(Groups._GroupRegistry[0])
for j in range(4):
trans = Transistor(B[0][j],B[1][j])
if j == 0:
Groups(trans)
else:
Groups._GroupRegistry[1].transistors.append(trans)
trans.group = Groups._GroupRegistry[1]
Series._SeriesRegistry[0].groups.append(Groups._GroupRegistry[1])
return
def main():
Init()
MainPlt()
return
main()
latest example that does not work:
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import numpy as np
X = np.linspace(-np.pi, np.pi, 256,endpoint=True)
Y1 = np.cos(X)
Y2 = np.sin(X)
figlist1 = []
figlist2 = []
axlist1 = []
axlist2 = []
for j in range(4):
figlist1.append(plt.figure(j))
axlist1.append(figlist1[j].add_subplot(111))
figlist2.append(plt.figure(j))#this should be a new set of figures!
axlist2.append(figlist2[j].add_subplot(111))
colour = cm.rainbow(np.linspace(0, 1, 4))
axlist1[j].plot(X,j*Y1, color=colour[j], linewidth=1.5, linestyle="-")
axlist1[j].set_title('Test Grp 1')
colour = cm.rainbow(np.linspace(0, 1, 4))
axlist2[j].plot(X,j*Y2, color=colour[int(j/2)], linewidth=1.5, linestyle="-")
axlist2[j].set_title('Test Grp 2')
plt.show()
Ok, stupid mistake if one thinks of the Background but maybe someone has a similar Problem and is unable to see the cause as I was first. So here is the solution:
The Problem is that the Name of the listobjects like figlist1[j] do not define the figure - they are just pointers to the actual figure object. and if such an object is created by plt.figure(j) one has to make sure that j is different for each figure - hence, in a Loop where multiple figures shall be initialized one Needs to somehow Change the number of the figure or the first object will be overwritten. Hope that helps! Cheers.
I wrote some code to shift an array, and was trying to generalize it to handle non-integer shifts using the "shift" function in scipy.ndimage. The data is circular and so the result should wrap around, exactly as the np.roll command does it.
However, scipy.ndimage.shift does not appear to wrap integer shifts properly. The following code snippet shows the discrepancy:
import numpy as np
import scipy.ndimage as sciim
import matplotlib.pyplot as plt
def shiftfunc(data, amt):
return sciim.interpolation.shift(data, amt, mode='wrap', order = 3)
if __name__ == "__main__":
xvals = np.arange(100)*1.0
yvals = np.sin(xvals*0.1)
rollshift = np.roll(yvals, 2)
interpshift = shiftfunc(yvals, 2)
plt.plot(xvals, rollshift, label = 'np.roll', alpha = 0.5)
plt.plot(xvals, interpshift, label = 'interpolation.shift', alpha = 0.5)
plt.legend()
plt.show()
It can be seen that the first couple of values are highly discrepant, while the rest are fine. I suspect this is an implementation error of the prefiltering and interpolation operation when using the wrap option. A way around this would be to modify shiftfunc to revert to np.roll when the shift value is an integer, but this is unsatisfying.
Am I missing something obvious here?
Is there a way to make ndimage.shift coincide with np.roll?
I dont think there is anything wrong with the shift function. when you use roll, your need to chop an extra element for fair comparision. please see the code below.
import numpy as np
import scipy.ndimage as sciim
import matplotlib.pyplot as plt
def shiftfunc(data, amt):
return sciim.interpolation.shift(data, amt, mode='wrap', order = 3)
def rollfunc(data,amt):
rollshift = np.roll(yvals, amt)
# Here I remove one element (first one before rollshift) from the array
return np.concatenate((rollshift[:amt], rollshift[amt+1:]))
if __name__ == "__main__":
shift_by = 5
xvals = np.linspace(0,2*np.pi,20)
yvals = np.sin(xvals)
rollshift = rollfunc(yvals, shift_by)
interpshift = shiftfunc(yvals,shift_by)
plt.plot(xvals, yvals, label = 'original', alpha = 0.5)
plt.plot(xvals[1:], rollshift, label = 'np.roll', alpha = 0.5,marker='s')
plt.plot(xvals, interpshift, label = 'interpolation.shift', alpha = 0.5,marker='o')
plt.legend()
plt.show()
results in