Related
I am trying to plot some 336 data points and am encountering an issue with my use of pythons plt.hist() function. I would like to use more than eight bins for my data, but when I do a lot of whitespace is introduced. For example, here is a plot with bins = 8
and with bins = 24
Does anyone know why this is and how I can best represent my data with more bins? Many thanks, ~S.
Sample code:
tumbles = np.array(df['Tumbles'])
fig, axs = plt.subplots(1, 1,
tight_layout = True)
N, bins, patches = axs.hist(tumbles, bins = 24, edgecolor= "black")
axs.grid(b = True, color ='grey',
linestyle ='-.', linewidth = 0.5,
alpha = 0.6)
plt.xlabel("Time (s)", size = 14)
plt.ylabel("Frequency", size = 14)
plt.title('Histogram ofTimes', size = 18)
plt.show()
I feel like your data is distributed in a way that the empty space between bars are simply bars with height 0 (a lack of samples). In such a case you just don't need more bins.
Please include your code
With this setup I get the same problem:
import matplotlib.pyplot as plt
plt.hist([1, 2, 2, 3, 4, 5, 5, 5, 5, 5, 6, 7, 9], bins=20)
plt.show()
It would be a bit more effort, but if you want a bit more control over the number of bins and the range of each bin, you might set up the bin parameter in your histogram definition as a list. This was alluded to above, but here is a snippet of code illustrating that.
import matplotlib.pyplot as plt
data = [0.02, 0.02, 0.02, 0.27, 0.27, 0.03, 0.03, 0.04, 0.044, 0.044, 0.05, 0.05, 0.06, 0.07, 0.08, 0.08, 0.08, 0,10, 0.10, 0.11, 0.12, 0.13, 0.13, 0.14, 0.15, 0.17, 0,18, 0.19, 0.20, 0.20, 0.22, 0.23, 0.23, 0.23, 0.23, 0.24, 0.26, 0.26, 0.28, 0.29, 0.30, 0.32]
fig, ax = plt.subplots()
N, bins, values = ax.hist(data, [0.000,0.015,0.030,0.045,0.060,0.075,0.090,0.105,0.120,0.135,0.150,0.165,0.180,0.195,0.210,0.225,0.240,0.255,0.270,0.285,0.300,0.315,0.330,0.345], linewidth=1)
plt.bar_label(values)
plt.xlabel("Time (s)", size = 14)
plt.ylabel("Frequency", size = 14)
plt.title('Histogram of Times', size = 18)
plt.show()
The data is just a small subset to establish some data points to produce a histogram. Following was the histogram created in this fashion.
You might give that a try adjusting for the range each bin should have.
Looking at the slider_demo.py, the author has the following code:
axcolor = 'lightgoldenrodyellow'
axfreq = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor=axcolor)
axamp = plt.axes([0.25, 0.15, 0.65, 0.03], facecolor=axcolor)
sfreq = Slider(axfreq, 'Freq', 0.1, 30.0, valinit=f0, valstep=delta_f)
samp = Slider(axamp, 'Amp', 0.1, 10.0, valinit=a0)
where, in sfreq, for example, the values 0.1 and 30.0 are valmin and valmax, respectively, and the slider values are incremented by delta_f.
I would like to, instead, define an array of values
valarray = [0.1, 1, 3, 5, 15, 20, 27, 30]
where this array of values are the only values selected, displayed, or available as one moves the slider. This does not appear to be an explicit option for this widget.
It would also be helpful, but not necessary, that I am able to update this list dynamically.
Thanks in advance for your help and assistance.
You can set the major ticks of the Slider with this line:
axfreq.set_xticks(np.array([0.1, 1, 3, 5, 15, 20, 27, 30]), minor = False)
eventually, you can also set minor ticks by setting minor = True.
I am trying to put multiple matplotlib subplots into a big axis, where tick labels on the big axis correspond to some parameter values for which the data in each subplot has been obtained. Here's an example,
import matplotlib.pyplot as plt
data = {}
data[(10, 10)] = [0.45, 0.30, 0.25]
data[(10, 20)] = [0.2, 0.5, 0.3]
data[(20, 10)] = [0.1, 0.3, 0.6]
data[(20, 20)] = [0.6, 0.15, 0.25]
data[(30, 10)] = [0.4, 0.35, 0.25]
data[(30, 20)] = [0.5, 0.1, 0.4]
# x and y coordinates for the big plot
x_coords = list(set([k[0] for k in data.keys()]))
y_coords = list(set([k[1] for k in data.keys()]))
labels = ['Frogs', 'Hogs', 'Dogs']
explode = (0.05, 0.05, 0.05) #
colors = ['gold', 'beige', 'lightcoral']
fig, axes = plt.subplots(len(y_coords), len(x_coords))
for row_topToDown in range(len(y_coords)):
row = (len(y_coords)-1) - row_topToDown
for col in range(len(x_coords)):
axes[row][col].pie(data[(x_coords[col], y_coords[row_topToDown])], explode=explode, colors = colors, \
autopct=None, pctdistance = 1.4, \
shadow=True, startangle=90, radius=0.7, \
wedgeprops = {'linewidth':1, 'edgecolor':'Black'}
)
axes[row][col].axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle.
axes[row][col].set_title('(' + str(x_coords[col]) + ', ' + str(y_coords[row_topToDown]) + ')')
fig.tight_layout()
plt.show()
and here's how I'd like the output to look like:
I see two options:
A. use a single axes
You may plot all pie charts to the same axes. Use the center and radius argument to scale the pies in data coordinates. This could look as follows.
import matplotlib.pyplot as plt
data = {}
data[(10, 10)] = [0.45, 0.30, 0.25]
data[(10, 20)] = [0.2, 0.5, 0.3]
data[(20, 10)] = [0.1, 0.3, 0.6]
data[(20, 20)] = [0.6, 0.15, 0.25]
data[(30, 10)] = [0.4, 0.35, 0.25]
data[(30, 20)] = [0.5, 0.1, 0.4]
labels = ['Frogs', 'Hogs', 'Dogs']
explode = [.2]*3
colors = ['gold', 'beige', 'lightcoral']
radius = 4
margin = 2
fig, ax = plt.subplots()
for x,y in data.keys():
d = data[(x,y)]
ax.pie(d, explode=explode, colors = colors, center=(x,y),
shadow=True, startangle=90, radius=radius,
wedgeprops = {'linewidth':1, 'edgecolor':'Black'})
ax.annotate("({},{})".format(x,y), xy = (x, y+radius),
xytext = (0,5), textcoords="offset points", ha="center")
ax.set_frame_on(True)
xaxis = list(set([x for x,y in data.keys()]))
yaxis = list(set([y for x,y in data.keys()]))
ax.set(aspect="equal",
xlim=(min(xaxis)-radius-margin,max(xaxis)+radius+margin),
ylim=(min(yaxis)-radius-margin,max(yaxis)+radius+margin),
xticks=xaxis, yticks=yaxis)
fig.tight_layout()
plt.show()
B. use inset axes
You can put each pie in its own axes and position the axes in data coordinates. This is facilitated by using mpl_toolkits.axes_grid1.inset_locator.inset_axes. The main difference to the above is that you may use a non-equal aspect of the parent axes, and that it's not possible to use tight_layout.
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
data = {}
data[(10, 10)] = [0.45, 0.30, 0.25]
data[(10, 20)] = [0.2, 0.5, 0.3]
data[(20, 10)] = [0.1, 0.3, 0.6]
data[(20, 20)] = [0.6, 0.15, 0.25]
data[(30, 10)] = [0.4, 0.35, 0.25]
data[(30, 20)] = [0.5, 0.1, 0.4]
labels = ['Frogs', 'Hogs', 'Dogs']
explode = [.05]*3
colors = ['gold', 'beige', 'lightcoral']
radius = 4
margin = 2
fig, axes = plt.subplots()
for x,y in data.keys():
d = data[(x,y)]
ax = inset_axes(axes, "100%", "100%",
bbox_to_anchor=(x-radius, y-radius, radius*2, radius*2),
bbox_transform=axes.transData, loc="center")
ax.pie(d, explode=explode, colors = colors,
shadow=True, startangle=90,
wedgeprops = {'linewidth':1, 'edgecolor':'Black'})
ax.set_title("({},{})".format(x,y))
xaxis = list(set([x for x,y in data.keys()]))
yaxis = list(set([y for x,y in data.keys()]))
axes.set(aspect="equal",
xlim=(min(xaxis)-radius-margin,max(xaxis)+radius+margin),
ylim=(min(yaxis)-radius-margin,max(yaxis)+radius+margin),
xticks=xaxis, yticks=yaxis)
plt.show()
For how to put a legend outside the plot, I would refer you to How to put the legend out of the plot. And for how to create a legend for a pie chart to How to add a legend to matplotlib pie chart?
Also Python - Legend overlaps with the pie chart may be of interest.
I was using this code to create an interactive plot (2d), and it works.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
fig, ax = plt.subplots()
plt.subplots_adjust(left=0.25, bottom=0.25)
t = np.arange(0.0, 1.0, 0.001)
a0 = 5
f0 = 3
s = a0*np.sin(2*np.pi*f0*t)
l, = plt.plot(t, s, lw=2, color='red')
plt.axis([0, 1, -10, 10])
axcolor = 'lightgoldenrodyellow'
axfreq = plt.axes([0.25, 0.1, 0.65, 0.03], axisbg=axcolor)
axamp = plt.axes([0.25, 0.15, 0.65, 0.03], axisbg=axcolor)
sfreq = Slider(axfreq, 'Freq', 0.1, 30.0, valinit=f0)
samp = Slider(axamp, 'Amp', 0.1, 10.0, valinit=a0)
def update(val):
amp = samp.val
freq = sfreq.val
l.set_ydata(amp*np.sin(2*np.pi*freq*t))
fig.canvas.draw_idle()
sfreq.on_changed(update)
samp.on_changed(update)
resetax = plt.axes([0.8, 0.025, 0.1, 0.04])
button = Button(resetax, 'Reset', color=axcolor, hovercolor='0.975')
def reset(event):
sfreq.reset()
samp.reset()
button.on_clicked(reset)
rax = plt.axes([0.025, 0.5, 0.15, 0.15], axisbg=axcolor)
radio = RadioButtons(rax, ('red', 'blue', 'green'), active=0)
def colorfunc(label):
l.set_color(label)
fig.canvas.draw_idle()
radio.on_clicked(colorfunc)
plt.show()
I then tried to modify it to create an interactive 3d plot by simply changing the axes to axes3d. I added the import statement shown below and replaced the definition of "fig" and "ax" with those shown below to become 3d.
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
The plot no longer updates, and I can't figure out why. It seems that the function fig.canvas.draw_idle() doesn't work on 3d graphs, but I don't have another way of updating the graph.
Any help would be appreciated,
Thanks!
You can see that the function fig.canvas.draw_idle() does exist and works as expected by looking at the colors which become updated.
The problem lies in the set_ydata function, which works differently in 3d space.
Assuming that you want the y coordinate to update and the z-coordinate to be constant, set_data will be given the constant values, while an additional property set_3d_properties() needs to be set to control the y-coordinate.
Here is the working example code:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.widgets import Slider, Button, RadioButtons
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
plt.subplots_adjust(left=0.25, bottom=0.25)
t = np.arange(0.0, 1.0, 0.001)
### create constant z-coordinate
z = np.zeros_like(t) # <------------ here
a0 = 5
f0 = 3
s = a0*np.sin(2*np.pi*f0*t)
l, = plt.plot(t, s, lw=2, color='red')
plt.axis([0, 1, -10, 10])
axfreq = plt.axes([0.25, 0.1, 0.65, 0.03], axisbg="w")
axamp = plt.axes([0.25, 0.15, 0.65, 0.03], axisbg="w")
sfreq = Slider(axfreq, 'Freq', 0.1, 30.0, valinit=f0)
samp = Slider(axamp, 'Amp', 0.1, 10.0, valinit=a0)
def update(val):
amp = samp.val
freq = sfreq.val
#set constant z coordinate
l.set_data(t, z) # <------------ here
# set values to y-coordinate
l.set_3d_properties(amp*np.sin(2*np.pi*freq*t), zdir="y") #<------------ here
fig.canvas.draw_idle()
sfreq.on_changed(update)
samp.on_changed(update)
resetax = plt.axes([0.8, 0.025, 0.1, 0.04])
button = Button(resetax, 'Reset', color=axcolor, hovercolor='0.975')
def reset(event):
sfreq.reset()
samp.reset()
button.on_clicked(reset)
rax = plt.axes([0.025, 0.5, 0.15, 0.15], axisbg=axcolor)
radio = RadioButtons(rax, ('red', 'blue', 'green'), active=0)
def colorfunc(label):
l.set_color(label)
fig.canvas.draw_idle()
radio.on_clicked(colorfunc)
plt.show()
I've extended following example to use multiple subplots. Here is my code:
#!/usr/bin/python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
########################################
t = np.arange(0.0, 1.0, 0.001)
a0 = 5
f0 = 3
s = a0*np.sin(2*np.pi*f0*t)
########################################
plt.close('all')
fig, ax = plt.subplots(nrows=2, ncols=1)
plt.subplots_adjust(bottom=0.30)
########################################
ax[0].plot(t,s, lw=2, color='red', label="red")
ax[1].plot(t,s, lw=2, color='green', label="green")
########################################
# plt.axis([0, 1, -10, 10])
ax[0].set_xlim([0, 1])
ax[0].set_ylim([-10, 10])
ax[1].set_xlim([0, 1])
ax[1].set_ylim([-10, 10])
########################################
axcolor = 'lightgoldenrodyellow'
f1 = plt.axes([0.25, 0.20, 0.65, 0.03], axisbg=axcolor)
a1 = plt.axes([0.25, 0.15, 0.65, 0.03], axisbg=axcolor)
f2 = plt.axes([0.25, 0.1, 0.65, 0.03], axisbg=axcolor)
a2 = plt.axes([0.25, 0.05, 0.65, 0.03], axisbg=axcolor)
sf1 = Slider(f1, 'Freq1', 0.1, 30.0, valinit=f0)
sa1 = Slider(a1, 'Amp1', 0.1, 10.0, valinit=a0)
sf2 = Slider(f2, 'Freq2', 0.1, 30.0, valinit=f0)
sa2 = Slider(a2, 'Amp2', 0.1, 10.0, valinit=a0)
########################################
def update1(val):
amp = sa1.val
freq = sf1.val
# DOES NOT WORKS - set_ydata DOES NOT EXISTS
# ax[1].set_ydata(amp*np.sin(2*np.pi*freq*t))
# ax[2].set_ydata(amp*np.sin(2*np.pi*freq*t))
# WORKS BUT IT SEEMS SLOW
s = amp*np.sin(2*np.pi*freq*t)
ax[0].clear()
ax[0].plot(t,s, lw=2, color='red', label="red")
ax[0].set_xlim([0, 1])
ax[0].set_ylim([-10, 10])
# THIS HAS NO EFFECT ON SPEED
fig.canvas.draw_idle()
sf1.on_changed(update1)
sa1.on_changed(update1)
def update2(val):
amp = sa2.val
freq = sf2.val
# DOES NOT WORKS - set_ydata DOES NOT EXISTS
# ax[1].set_ydata(amp*np.sin(2*np.pi*freq*t))
# ax[2].set_ydata(amp*np.sin(2*np.pi*freq*t))
# WORKS BUT IT SEEMS SLOW
s = amp*np.sin(2*np.pi*freq*t)
ax[1].clear()
ax[1].plot(t,s, lw=2, color='green', label="green")
ax[1].set_xlim([0, 1])
ax[1].set_ylim([-10, 10])
# THIS HAS NO EFFECT ON SPEED
fig.canvas.draw_idle()
sf2.on_changed(update2)
sa2.on_changed(update2)
plt.show()
The only problem is that it is slower (when the slider is clicked) than the original. I suspect this is caused by bunch of code that is used in update1 and update2 functions. I do not know how to rewrite it more effectively. The original example is using the set_ydata function but seems that subplots does not have this function. I've been also thinking about one update function for all four sliders, but I do not know if is it possible to distinguish the object on which the update was triggered and handle it inside the function. Thanks
You try to call set_ydata on the ax[0] (or ax[1], respectively). This obviously does not work, as these are axes instances. set_ydata is a method of Line2D artists. In the example that you have linked a line l is created in the beginning, which receives the new y data in the update function. You forgot that in your code.
So you need to create the lines:
line0, = ax[0].plot(t,s, lw=2, color='red')
line1, = ax[1].plot(t,s, lw=2, color='green')
in the beginning and then in your update functions you can call:
line0.set_ydata(amp*np.sin(2*np.pi*freq*t))
or
line1.set_ydata(amp*np.sin(2*np.pi*freq*t))
Note: The idea here is to only change the y coordinates of your lines. With your workaround, you deleted the entire graph and recreated it. This is more work and therefore it makes things slower.