Automatically get the dimensions or indices of matplotlib gridspec - python

Given a gridspec object in matplotlib, I want to automatically iterate through all its indices so I can add the corresponding Axes automatically, something like:
for i, j in gspec.indices: # whatever those indices are
axs[i,j] = fig.add_subplot(gspec[i][j])
How do I do that, without knowing how many rows or columns the gridspec has in advance?

gspec.get_geometry() returns the number of rows and of columns. Here is some example code:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(constrained_layout=True)
gspec = fig.add_gridspec(3, 4)
nrows, ncols = gspec.get_geometry()
axs = np.array([[fig.add_subplot(gspec[i, j]) for j in range(ncols)] for i in range(nrows)])
t = np.linspace(0, 4 * np.pi, 1000)
for i in range(nrows):
for j in range(ncols):
axs[i, j].plot(np.sin((i + 1) * t), np.sin((j + 1) * t))
plt.show()
If axs isn't needed as numpy array, the conversion to numpy array can be left out.
Note that the code assumes you need a subplot in every possible grid position, which also can be obtained via fig, axs = plt.subplots(...). A gridspec is typically used when you want to combine grid positions to create custom layouts, as shown in the examples of the tutorial.

Related

How to create subplots of all column combinations from two dataframes

I have a made a function which plots input variables against predicted variables.
dummy_data = pd.DataFrame(np.random.uniform(low=65.5,high=140.5,size=(50,4)), columns=list('ABCD'))
dummy_predicted = pd.DataFrame(np.random.uniform(low=15.5,high=17.5,size=(50,4)), columns=list('WXYZ'))
##Plot test input distriubtions
fig = plt.figure(figsize=(15,6))
n_rows = 1
n_cols = 4
counter = 1
for i in dummy_data.keys():
plt.subplot(n_rows, n_cols, counter)
plt.scatter(dummy_data[i], dummy_predicted['Z'])
plt.title(f'{i} vs Z')
plt.xlabel(i)
counter += 1
plt.tight_layout()
plt.show()
How do I create a 4 x 4 subplot of all combinations of 'ABCD' and 'WXYZ'? I can have any number of dummy_data and dummy_predicted columns so some dynamism would be useful.
Use itertools.product from the standard library, to create all combinations of column names, combos.
Use the len of each set of columns to determine nrows and ncols for plt.subplots
Flatten the array of axes to easily iterate through a 1D array instead of a 2D array.
zip combos and axes to iterate through, and plot each group with a single loop.
See this answer in How to plot in multiple subplots.
from itertools import product
import matplotlib.pyplot as plt
import numpy as np
# sample data
np.random.seed(2022)
dd = pd.DataFrame(np.random.uniform(low=65.5, high=140.5, size=(50, 4)), columns=list('ABCD'))
dp = pd.DataFrame(np.random.uniform(low=15.5, high=17.5, size=(50, 4)), columns=list('WXYZ'))
# create combinations of columns
combos = product(dd.columns, dp.columns)
# create subplots
fig, axes = plt.subplots(nrows=len(dd.columns), ncols=len(dp.columns), figsize=(15, 6))
# flatten axes into a 1d array
axes = axes.flat
# iterate and plot
for (x, y), ax in zip(combos, axes):
ax.scatter(dd[x], dp[y])
ax.set(title=f'{x} vs. {y}', xlabel=x, ylabel=y)
plt.tight_layout()
plt.show()
just do a double for loop
n_rows = len(dummy_data.columns)
n_cols = len(dummy_predicted.columns)
fig, axes = plt.subplots(n_rows, n_cols, figsize=(15,6))
for row, data_col in enumerate(dummy_data):
for col, pred_col in enumerate(dummy_predicted):
ax = axes[row][col]
ax.scatter(dummy_data[data_col], dummy_predicted[pred_col])
ax.set_title(f'{data_col} vs {pred_col}')
ax.set_xlabel(data_col)
plt.tight_layout()
plt.show()
Output:

Seperate title for each subplot in a for loop in Python

I am trying to use subplots within a for loop and I can plot all my graphs, but I can't give them individual x and y labels and titles. It is only the last one that it is applied to.
import numpy as np
import astropy
import matplotlib.pyplot as plt
import pandas as pd
#Import 18 filesnames with similar names
from glob import glob
filenames = glob('./*V.asc')
df = [np.genfromtxt(f) for f in filenames]
A = np.stack(df, axis=0)
#Begin subplot
nrows = 3
ncols = 6
fig, ax = plt.subplots(nrows = nrows, ncols = ncols, figsize=(30,15))
#Loop over each filename i, row j and column k
i = 0
for j in range(0, nrows):
for k in range(0, ncols):
ax[j,k].plot(A[i,:,0], A[i,:,1])
plt.title(filenames[i], fontsize = '25')
i += 1
plt.subplots_adjust(wspace=.5, hspace=.5)
fig.show()
I can plot it in seperate plots, so 18 in total and it works fine
for i in range(0, len(A)):
plt.figure(i)
plt.title(filenames[i], fontsize = '30')
plt.plot(A[i,:,0], A[i,:,1])
plt.xlabel('Wavelength [Å]', fontsize = 20)
plt.ylabel('Flux Density [erg/s/cm^2/Å]', fontsize = 20)
plt.xticks(fontsize = 20)
plt.yticks(fontsize = 20)
I update the title each iteration i, same as the subplot, so I don't understand why it doesn't work.
Any input is appreciated!
plt.title() acts on the current axes, which is generally the last created, and not the Axes that you are thinking of.
In general, if you have several axes, you will be better off using the object-oriented interface of matplotlib rather that the pyplot interface. See usage guide
replace:
plt.title(filenames[i], fontsize = '25')
by
ax[j,k].set_title(filenames[i], fontsize = '25')

Matplotlib automatically scale vertical height of subplots for shared x-axis figure

I want to automatically scale the vertical height of subplots for shared x-axis figures based on their data span! I want to compare the relative intensity of the displayed data. If i use the sharey=True kwarg for the subbplots the data is displayed in a way that the relative intensity is recognizable:
import matplotlib.pyplot as plt
from matplotlib import gridspec
import numpy as np
SIZE = (12, 8) #desired overall figure size
# Simple data to display in various forms
x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x ** 2)
y2 = 2*(np.sin(x ** 2))
y3 = 3*(np.sin(x ** 2))
fig, ax = plt.subplots(3,ncols=1, sharex=True, sharey=True)
fig.set_size_inches(SIZE[1], SIZE[0])
fig.subplots_adjust(hspace=0.001)
ax[0].plot(x, y)
ax[1].plot(x, y2)
ax[2].plot(x, y3)
plt.show()
All subplots have the same height now and the data span in the y-Axis is recognizable as the data is displayed with the correct relative proportion.
What i would like to achieve is that the scales of each plot end where the data ends. Essentially eliminating the not used white space. The size of the subplot would than represent the relative height ratios of the data. They should still have the same scaling on the Y axis in order for the viewer to estimate the relative data height ( which cold be a countrate for example).
I found the following links to similar problems but none really helped me to solve my issue:
Link1 Link2
Here an example that determines the ratio for you and creates the subplots accordingly:
import matplotlib.pyplot as plt
from matplotlib import gridspec
import numpy as np
SIZE = (12, 8) #desired overall figure size
# Simple data to display in various forms
x = np.linspace(0, 2 * np.pi, 400)
# the maximum multiplier for the function
N = 3
# the y-ranges:
ys = [i * np.sin(x**2) for i in range(1,N+1)]
# the maximum extent of the plot in y-direction (cast as int)
hs = [int(np.ceil(np.max(np.abs(y)))) for y in ys]
# determining the size of the GridSpec:
gs_size = np.sum(hs)
gs = gridspec.GridSpec(gs_size,1)
# the figure
fig = plt.figure(figsize = SIZE)
# creating the subplots
base = 0
ax = []
for y,h in zip(ys,hs):
ax.append(fig.add_subplot(gs[base:h+base,:]))
base += h
ax[-1].plot(x,y)
##fig, ax = plt.subplots(3,ncols=1, sharex=True, sharey=True)
##fig.set_size_inches(SIZE[1], SIZE[0])
fig.subplots_adjust(hspace=0.001)
##ax[0].plot(x, ys[0])
##ax[1].plot(x, ys[1])
##ax[2].plot(x, ys[2])
plt.show()
The code determines the maximum y-extend for each set of data, casts it into an integer and then divides the figure into subplots using the sum of these extends as scale for the GridSpec.
The resulting figure looks like this:
Tested on Python 3.5
EDIT:
If the maximum and minimum extents of your data are not comparable, it may be better to change the way hs is calculated into
hs = [int(np.ceil(np.max(y))) - int(np.floor(np.min(y))) for y in ys]

Python - Randomly subsamble a range of points to plot

I have two lists, x and y, that I wish to plot together in a scatter plot.
The lists contain too many data points. I would like a graph with much less points. I cannot crop or trim these lists, I need to randomly subsamble a set number of points from both of these lists. What would be the best way to approach this?
You could subsample the lists using
idx = np.random.choice(np.arange(len(x)), num_samples)
plt.scatter(x[idx], y[idx])
However, this leaves the result a bit up to random luck. We can do better by making a heatmap. plt.hexbin makes this particularly easy:
plt.hexbin(x, y)
Here is an example, comparing the two methods:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
np.random.seed(2015)
N = 10**5
val1 = np.random.normal(loc=10, scale=2,size=N)
val2 = np.random.normal(loc=0, scale=1, size=N)
fig, ax = plt.subplots(nrows=2, sharex=True, sharey=True)
cmap = plt.get_cmap('jet')
norm = mcolors.LogNorm()
num_samples = 10**4
idx = np.random.choice(np.arange(len(val1)), num_samples)
ax[0].scatter(val1[idx], val2[idx])
ax[0].set_title('subsample')
im = ax[1].hexbin(val1, val2, gridsize=50, cmap=cmap, norm=norm)
ax[1].set_title('hexbin heatmap')
plt.tight_layout()
fig.colorbar(im, ax=ax.ravel().tolist())
plt.show()
You can pick randomly from x and y using a random index mask
import numpy as np
import matplotlib.pyplot as plt
N = 50
x = np.random.rand(N)
y = np.random.rand(N)
# Pick random 10 samples, 2 means two choices from [0, 1] for the mask
subsample = np.random.choice(2, 10).astype(bool)
plt.scatter(x[subsample], y[subsample])
plt.show()
Alternatively you can use hist2d to plot a 2D histogram, which uses densities instead of data points
plt.hist2d(x, y) # No need to subsample
You can use random.sample():
max_points = len(x)
# Assuming you only want 50 points.
random_indexes = random.sample(range(max_points), 50)
new_x = [x[i] for i in random_indexes]
new_y = [y[i] for i in random_indexes]

Shade 'cells' in polar plot with matplotlib

I've got a bunch of regularly distributed points (θ = n*π/6, r=1...8), each having a value in [0, 1]. I can plot them with their values in matplotlib using
polar(thetas, rs, c=values)
But rather then having just a meagre little dot I'd like to shade the corresponding 'cell' (ie. everything until halfway to the adjacent points) with the colour corresponding to the point's value:
(Note that here my values are just [0, .5, 1], in really they will be everything between 0 and 1. Is there any straight-forward way of realising this (or something close enough) with matplotlib? Maybe it's easier to think about it as a 2D-histogram?
This can be done quite nicely by treating it as a polar stacked barchart:
import matplotlib.pyplot as plt
import numpy as np
from random import choice
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
for i in xrange(12*8):
color = choice(['navy','maroon','lightgreen'])
ax.bar(i * 2 * np.pi / 12, 1, width=2 * np.pi / 12, bottom=i / 12,
color=color, edgecolor = color)
plt.ylim(0,10)
ax.set_yticks([])
plt.show()
Produces:
Sure! Just use pcolormesh on a polar axes.
E.g.
import matplotlib.pyplot as plt
import numpy as np
# Generate some data...
# Note that all of these are _2D_ arrays, so that we can use meshgrid
# You'll need to "grid" your data to use pcolormesh if it's un-ordered points
theta, r = np.mgrid[0:2*np.pi:20j, 0:1:10j]
z = np.random.random(theta.size).reshape(theta.shape)
fig, (ax1, ax2) = plt.subplots(ncols=2, subplot_kw=dict(projection='polar'))
ax1.scatter(theta.flatten(), r.flatten(), c=z.flatten())
ax1.set_title('Scattered Points')
ax2.pcolormesh(theta, r, z)
ax2.set_title('Cells')
for ax in [ax1, ax2]:
ax.set_ylim([0, 1])
ax.set_yticklabels([])
plt.show()
If your data isn't already on a regular grid, then you'll need to grid it to use pcolormesh.
It looks like it's on a regular grid from your plot, though. In that case, gridding it is quite simple. If it's already ordered, it may be as simple as calling reshape. Otherwise, a simple loop or exploiting numpy.histogram2d with your z values as weights will do what you need.
Well, it's fairly unpolished overall, but here's a version that rounds out the sections.
from matplotlib.pylab import *
ax = subplot(111, projection='polar')
# starts grid and colors
th = array([pi/6 * n for n in range(13)]) # so n = 0..12, allowing for full wrapping
r = array(range(9)) # r = 0..8
c = array([[random_integers(0, 10)/10 for y in range(th.size)] for x in range(r.size)])
# The smoothing
TH = cbook.simple_linear_interpolation(th, 10)
# Properly padding out C so the colors go with the right sectors (can't remember the proper word for such segments of wedges)
# A much more elegant version could probably be created using stuff from itertools or functools
C = zeros((r.size, TH.size))
oldfill = 0
TH_ = TH.tolist()
for i in range(th.size):
fillto = TH_.index(th[i])
for j, x in enumerate(c[:,i]):
C[j, oldfill:fillto].fill(x)
oldfill = fillto
# The plotting
th, r = meshgrid(TH, r)
ax.pcolormesh(th, r, C)
show()

Categories

Resources