This question already has answers here:
How to plot in multiple subplots
(12 answers)
Closed 1 year ago.
I am using the Lombscargle function to output the power spectrum for a signal I pass as input, I am able to get the plots one after another but the task at hand is to plot these graphs using subplots in a way that there are 5 rows, 4 cols.
An example for signal would be:
signal = [ '254.24', '254.32', '254.4', '254.84', '254.24', '254.28', '254.84', '253.56', '253.76', '253.32', '253.88', '253.72', '253.92', '251.56', '253.04', '244.72', '243.84', '246.08', '245.84', '249.0', '250.08', '248.2', '253.12', '253.2', '253.48', '253.88', '253.12', '253.4', '253.4']
from scipy.signal import lombscargle
def LSP_scipy(signal):
start_ang_freq = 2 * np.pi * (60/60)
end_ang_freq = 2 * np.pi * (240/60)
SAMPLES = 5000
SAMPLE_SPACING = 1/15
t = np.linspace(0,len(signal)*SAMPLE_SPACING,len(signal))
period_freq = np.linspace(start_ang_freq,end_ang_freq,SAMPLES)
modified_signal_axis = []
modified_time_axis = []
for count,value in enumerate(signal):
if value != 'None':
modified_signal_axis.append(float(value))
modified_time_axis.append(t[count])
prog = lombscargle(modified_time_axis, modified_signal_axis, period_freq, normalize=False, precenter = True)
fig, axes = plt.subplots()
ax.plot(period_freq,prog)
How do I plot these graphs in a matrix format?
Trying loop approach,
See inline comments to add and flatten the subplots.
This is an implementation of flattening the axes array from this answer of the duplicate.
from scipy.signal import lombscargle
from matplotlib.ticker import FormatStrFormatter
import numpy as np
import matplotlib.pyplot as plt
def LSP_scipy(signal):
start_ang_freq = 2 * np.pi * (60/60)
end_ang_freq = 2 * np.pi * (240/60)
SAMPLES = 5000
SAMPLE_SPACING = 1/15
t = np.linspace(0, len(signal)*SAMPLE_SPACING, len(signal))
period_freq = np.linspace(start_ang_freq, end_ang_freq, SAMPLES)
modified_signal_axis = []
modified_time_axis = []
# create the figure and subplots
fig, axes = plt.subplots(5, 6, figsize=(20, 9), sharex=False, sharey=False)
# flatten the array
axes = axes.ravel()
for count, value in enumerate(signal):
if value != 'None':
modified_signal_axis.append(float(value))
modified_time_axis.append(t[count])
prog = lombscargle(modified_time_axis, modified_signal_axis, period_freq, normalize=False, precenter=True)
# plot
axes[count].plot(period_freq, prog)
# format the axes
axes[count].set(title=value)
# some plot have an exponential offset on the yaxis, this turns it off
axes[count].ticklabel_format(useOffset=False)
# some yaxis values are long floats, this formats them to 3 decimal places
axes[count].yaxis.set_major_formatter(FormatStrFormatter('%.3f'))
# format the figure
fig.tight_layout()
signal = [ '254.24', '254.32', '254.4', '254.84', '254.24', '254.28', '254.84', '253.56', '253.76', '253.32', '253.88', '253.72', '253.92', '251.56', '253.04', '244.72', '243.84', '246.08', '245.84', '249.0', '250.08', '248.2', '253.12', '253.2', '253.48', '253.88', '253.12', '253.4', '253.4']
LSP_scipy(signal[:20]) # as per comment, only first 20
You can use for loop and iterate over subplots. A very simple example is shown below.The subplots method creates the figure along with the subplots and store in the ax array.
import matplotlib.pyplot as plt
x = np.linspace(0, 10)
y = range(10)
fig, ax = plt.subplots(nrows=2, ncols=2)
for row in ax:
for col in row:
col.plot(x, y)
plt.show()
# or you can also do
for in range(2): # row=0, col=0
for j in range(2): # row=0, col=1
ax[i, j].plot(x,y) # row=1, col=0
# row=1, col=1
Then one idea is to take the signals into an array of shape=(20,1), where each row corresponds to signal amplitude or some other measurable quantity. Then you could do as below (check the output keeping only the lines till plt.text you will get the idea).
for i in range(1, 21):
plt.subplot(5, 4, i)
plt.text(0.5, 0.5, str((5, 4, i)),
fontsize=18, ha='center')
# Call the function here...get the value of period_freq and prog
period_freq,prog = LSP_scipy(signal[i])
plt.plot(period_freq, prog)
Related
import matplotlib.pyplot as plt
import numpy as np
delta = 0.0001
t = np.arange(0,5+delta,delta)
xt = np.sin(np.pi*t)
fig = plt.figure(1)
ax1= plt.subplot(3,2,1)
ax1.plot(t,xt, "tab:red")
ax1.set(ylabel = "Amplitude")
ax1.set(xlabel = 'Time(s)')
ax1.set(title = 'for n = 1')
ax1.grid()
ax2 = plt.subplot(3,2,2)
ax2.plot(t,xt, "tab:green")
ax2.set(ylabel = "Amplitude")
ax2.set(xlabel = 'Time(s)')
ax2.set(title = 'for n = 2')
ax2.grid()
plt.tight_layout()
plt.show()
Hi this is just a snip of my code but my problem basically is with the x axis of the subplots.
On the axis the values jump from 0-2-4 and I need it to be from 0-1-2-3-4-5.
Is there a way I can get those values to display on the x axis rather than just 0-2-4.
There are several possible ways of doing this. One of the simplest is to manually set the x ticks.
ax1.set_xticks(np.arange(6))
ax2.set_xticks(np.arange(6))
you can set the locator for x axis.
import matplotlib as mpl
ax1.xaxis.set_major_locator(mpl.ticker.MultipleLocator(1))
ax2.xaxis.set_major_locator(mpl.ticker.MultipleLocator(1))
I have some sorted data of which I only show the highest and lowest values in a figure. This is a minimal version of what currently I have:
import matplotlib.pyplot as plt
# some dummy data (real data contains about 250 entries)
x_data = list(range(98, 72, -1))
labels = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
ranks = list(range(1, 27))
fig, ax = plt.subplots()
# plot 3 highest entries
bars_top = ax.barh(labels[:3], x_data[:3])
# plot 3 lowest entries
bars_bottom = ax.barh(labels[-3:], x_data[-3:])
ax.invert_yaxis()
# print values and ranks
for bar, value, rank in zip(bars_top + bars_bottom,
x_data[:3] + x_data[-3:],
ranks[:3] + ranks[-3:]):
y_pos = bar.get_y() + 0.5
ax.text(value - 4, y_pos, value, ha='right')
ax.text(4, y_pos, f'$rank:\ {rank}$')
ax.set_title('Comparison of Top 3 and Bottom 3')
plt.show()
Result:
I'd like to make an additional gap to this figure to make it more visually clear that the majority of data is in fact not displayed in this plot. For example, something very simple like the following would be sufficient:
Is this possible in matplotlib?
Here is a flexible approach that just plots a dummy bar in-between. The yaxis-transform together with the dummy bar's position is used to plot 3 black dots.
If multiple separations are needed, they all need a different dummy label, for example repeating the space character.
import matplotlib.pyplot as plt
import numpy as np
# some dummy data (real data contains about 250 entries)
x_data = list(range(98, 72, -1))
labels = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
ranks = list(range(1, 27))
fig, ax = plt.subplots()
# plot 3 highest entries
bars_top = ax.barh(labels[:3], x_data[:3])
# dummy bar inbetween
dummy_bar = ax.barh(" ", 0, color='none')
# plot 3 lowest entries
bars_bottom = ax.barh(labels[-3:], x_data[-3:])
ax.invert_yaxis()
# print values and ranks
for bar, value, rank in zip(bars_top + bars_bottom,
x_data[:3] + x_data[-3:],
ranks[:3] + ranks[-3:]):
y_pos = bar.get_y() + 0.5
ax.text(value - 4, y_pos, value, ha='right')
ax.text(4, y_pos, f'$rank:\ {rank}$')
# add three dots using the dummy bar's position
ax.scatter([0.05] * 3, dummy_bar[0].get_y() + np.linspace(0, dummy_bar[0].get_height(), 3),
marker='o', s=5, color='black', transform=ax.get_yaxis_transform())
ax.set_title('Comparison of Top 3 and Bottom 3')
ax.tick_params(axis='y', length=0) # hide the tick marks
ax.margins(y=0.02) # less empty space at top and bottom
plt.show()
The following function,
def top_bottom(x, l, n, ax=None, gap=1):
from matplotlib.pyplot import gca
if n <= 0 : raise ValueError('No. of top/bottom values must be positive')
if n > len(x) : raise ValueError('No. of top/bottom values should be not greater than data length')
if n+n > len(x):
print('Warning: no. of top/bottom values is larger than one'
' half of data length, OVERLAPPING')
if gap < 0 : print('Warning: some bar will be overlapped')
ax = ax if ax else gca()
top_x = x[:+n]
bot_x = x[-n:]
top_y = list(range(n+n, n, -1))
bot_y = list(range(n-gap, -gap, -1))
top_l = l[:+n] # A B C
bot_l = l[-n:] # X Y Z
top_bars = ax.barh(top_y, top_x)
bot_bars = ax.barh(bot_y, bot_x)
ax.set_yticks(top_y+bot_y)
ax.set_yticklabels(top_l+bot_l)
return top_bars, bot_bars
when invoked with your data and n=4, gap=4
bars_top, bars_bottom = top_bottom(x_data, labels, 4, gap=4)
produces
Later, you'll be able to customize the appearance of the bars as you like using the Artists returned by the function.
I have 2 lists of figures and their axes.
I need to plot each figure in a single subplot so that the figures become in one big subplot. How can I do that?
I tried for loop but it didn't work.
Here's what I have tried:
import ruptures as rpt
import matplotlib.pyplot as plt
# make random data with 100 samples and 9 columns
n_samples, n_dims, sigma = 100, 9, 2
n_bkps = 4
signal, bkps = rpt.pw_constant(n_samples, n_dims, n_bkps, noise_std=sigma)
figs, axs = [], []
for i in range(signal.shape[1]):
points = signal[:,i]
# detection of change points
algo = rpt.Dynp(model='l2').fit(points)
result = algo.predict(n_bkps=2)
fig, ax = rpt.display(points, bkps, result, figsize=(15,3))
figs.append(fig)
axs.append(ax)
plt.show()
I had a look at the source code of ruptures.display(), and it accepts **kwargs that are passed on to matplotlib. This allows us to redirect the output to a single figure, and with gridspec, we can position individual subplots within this figure:
import ruptures as rpt
import matplotlib.pyplot as plt
n_samples, n_dims, sigma = 100, 9, 2
n_bkps = 4
signal, bkps = rpt.pw_constant(n_samples, n_dims, n_bkps, noise_std=sigma)
#number of subplots
n_subpl = signal.shape[1]
#give figure a name to refer to it later
fig = plt.figure(num = "ruptures_figure", figsize=(8, 15))
#define grid of nrows x ncols
gs = fig.add_gridspec(n_subpl, 1)
for i in range(n_subpl):
points = signal[:,i]
algo = rpt.Dynp(model='l2').fit(points)
result = algo.predict(n_bkps=2)
#rpt.display(points, bkps, result)
#plot into predefined figure
_, curr_ax = rpt.display(points, bkps, result, num="ruptures_figure")
#position current subplot within grid
curr_ax[0].set_position(gs[i].get_position(fig))
curr_ax[0].set_subplotspec(gs[i])
plt.show()
Sample output:
I am plotting seismological data and am creating a figure featuring 16 subplots of different depth slices. Each subplot displays the lat/lon of the epicenter and the color is scaled to its magnitude. I am trying to do two things:
Adjust the scale of all plots to equal the x and y min and max for the area selected. This will allow easy comparison across the plots. (so all plots would range from xmin to xmax etc)
adjust the magnitude colors so they also represent the scale (ie colors represent all available points not just the points on that specific sub plot)
I have seen this accomplished a number of ways but am struggling to apply them to the loop in my code. The data I am using is here: Data.
I posted my code and what the current output looks like below.
import matplotlib.pyplot as plt
import pandas as pd
eq_df = pd.read_csv(eq_csv)
eq_data = eq_df[['LON', 'LAT', 'DEPTH', 'MAG']]
nbound = max(eq_data.LAT)
sbound = min(eq_data.LAT)
ebound = max(eq_data.LON)
wbound = min(eq_data.LON)
xlimit = (wbound, ebound)
ylimit = (sbound, nbound)
magmin = min(eq_data.MAG)
magmax = max(eq_data.MAG)
for n in list(range(1,17)):
km = eq_data[(eq_data.DEPTH > n - 1) & (eq_data.DEPTH <= n)]
plt.subplot(4, 4, n)
plt.scatter(km["LON"], km['LAT'], s = 10, c = km['MAG'], vmin = magmin, vmax = magmax) #added vmin/vmax to scale my magnitude data
plt.ylim(sbound, nbound) # set y limits of plot
plt.xlim(wbound, ebound) # set x limits of plot
plt.tick_params(axis='both', which='major', labelsize= 6)
plt.subplots_adjust(hspace = 1)
plt.gca().set_title('Depth = ' + str(n - 1) +'km to ' + str(n) + 'km', size = 8) #set title of subplots
plt.suptitle('Magnitude of Events at Different Depth Slices, 1950 to Today')
plt.show()
ETA: new code to resolve my issue
In response to this comment on the other answer, here is a demonstration of the use of sharex=True and sharey=True for this use case:
import matplotlib.pyplot as plt
import numpy as np
# Supply the limits since random data will be plotted
wbound = -0.1
ebound = 1.1
sbound = -0.1
nbound = 1.1
fig, axs = plt.subplots(nrows=4, ncols=4, figsize=(16,12), sharex=True, sharey=True)
plt.xlim(wbound, ebound)
plt.ylim(sbound, nbound)
for n, ax in enumerate(axs.flatten()):
ax.scatter(np.random.random(20), np.random.random(20),
c = np.random.random(20), marker = '.')
ticks = [n % 4 == 0, n > 12]
ax.tick_params(left=ticks[0], bottom=ticks[1])
ax.set_title('Depth = ' + str(n - 1) +'km to ' + str(n) + 'km', size = 12)
plt.suptitle('Magnitude of Events at Different Depth Slices, 1950 to Today', y = 0.95)
plt.subplots_adjust(wspace=0.05)
plt.show()
Explanation of a couple things:
I have reduced the horizontal spacing between subplots with subplots_adjust(wspace=0.05)
plt.suptitle does not need to be (and should not be) in the loop.
ticks = [n % 4 == 0, n > 12] creates a pair of bools for each axis which is then used to control which tick marks are drawn.
Left and bottom tick marks are controlled for each axis with ax.tick_params(left=ticks[0], bottom=ticks[1])
plt.xlim() and plt.ylim() need only be called once, before the loop
Finally got it thanks to some help above and some extended googling.
I have updated my code above with notes indicating where code was added.
To adjust the limits of my plot axes I used:
plt.ylim(sbound, nbound)
plt.xlim(wbound, ebound)
To scale my magnitude data across all plots I added vmin, vmax to the following line:
plt.scatter(km["LON"], km['LAT'], s = 10, c = km['MAG'], vmin = magmin, vmax = magmax)
And here is the resulting figure:
I am plotting a confusion matrix with matplotlib with the following code:
from numpy import *
import matplotlib.pyplot as plt
from pylab import *
conf_arr = [[33,2,0,0,0,0,0,0,0,1,3], [3,31,0,0,0,0,0,0,0,0,0], [0,4,41,0,0,0,0,0,0,0,1], [0,1,0,30,0,6,0,0,0,0,1], [0,0,0,0,38,10,0,0,0,0,0], [0,0,0,3,1,39,0,0,0,0,4], [0,2,2,0,4,1,31,0,0,0,2], [0,1,0,0,0,0,0,36,0,2,0], [0,0,0,0,0,0,1,5,37,5,1], [3,0,0,0,0,0,0,0,0,39,0], [0,0,0,0,0,0,0,0,0,0,38] ]
norm_conf = []
for i in conf_arr:
a = 0
tmp_arr = []
a = sum(i,0)
for j in i:
tmp_arr.append(float(j)/float(a))
norm_conf.append(tmp_arr)
plt.clf()
fig = plt.figure()
ax = fig.add_subplot(111)
res = ax.imshow(array(norm_conf), cmap=cm.jet, interpolation='nearest')
cb = fig.colorbar(res)
savefig("confmat.png", format="png")
But I want to the confusion matrix to show the numbers on it like this graphic (the right one). How can I plot the conf_arr on the graphic?
You can use text to put arbitrary text in your plot. For example, inserting the following lines into your code will write the numbers (note the first and last lines are from your code to show you where to insert my lines):
res = ax.imshow(array(norm_conf), cmap=cm.jet, interpolation='nearest')
for i, cas in enumerate(conf_arr):
for j, c in enumerate(cas):
if c>0:
plt.text(j-.2, i+.2, c, fontsize=14)
cb = fig.colorbar(res)
The only way I could really see of doing it was to use annotations. Try these lines:
for i,j in ((x,y) for x in xrange(len(conf_arr))
for y in xrange(len(conf_arr[0]))):
ax.annotate(str(conf_arr[i][j]),xy=(i,j))
before saving the figure. It adds the numbers, but I'll let you figure out how to get the sizes of the numbers how you want them.