Matplotlib: How to recreate `6 petal` polar diagram - python

For an assignment, I have to recreate the following plot (including all labels and ticks):
This is what I have tried so far with my code
import numpy as np
import matplotlib.pyplot as plt
nmax=101 # choose a high number to "smooth out" lines in plots
x = np.linspace(0,20,nmax) # create an array x
y_br = np.sin(3*x) # y for the bottom right subplot
fig = plt.figure()
ax4 = plt.subplot(224, projection = 'polar')
ax4.plot(x, y_br, 'tab:blue')
But if you were to run this yourself, this does not replicate the plot. What function could be used here and how can tick marks be changed in polar plots? Thanks in advance?

Related

Python: Recreating rose plot

For an assignment, I am trying to recreate the rose plot
Here is my code:
import numpy as np
import matplotlib.pyplot as plt
nmax=101 # choose a high number to "smooth out" lines in plots
x_angle = np.linspace(0,2*np.pi,nmax) # create an array x for bottom right
y_br = abs(np.cos(3*x_angle)) # y for the bottom right subplot
# bottom right subplot controls
plt.plot(x_angle, y_br, 'tab:blue')
Is there a way to get it to look any closer to the original plot (e.g. make the petals narrower, set tick marks to 0.0, 0.5, 1.0)?
You can use polar projection for this type of plot:
for the ticks, it is set by rticks
for the petals, it is controlled by the nmax feature
Here is a code that gives a better drawing:
import numpy as np
import matplotlib.pyplot as plt
nmax=int(1e5) # choose a high number to "smooth out" lines in plots
x_angle = np.linspace(0,2*np.pi,nmax) # create an array x for bottom right
y_br = abs(np.cos(3*x_angle)) # y for the bottom right subplot
fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.plot(x_angle, y_br, lw= 3)
ax.set_rticks([0, 0.5, 1]) # Less radial ticks

Matplotlib: orthographic projection of 3D data (in 2D plot)

I'm trying to plot 3D data in 2D using orthographic projection. Here is partially what I'm looking for:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
fig = plt.figure(figsize=(10,10),facecolor='white')
axs = [fig.add_subplot(223)]
axs.append(fig.add_subplot(224))#,sharey=axs[0]))
axs.append(fig.add_subplot(221))#,sharex=axs[0]))
rng = np.random.default_rng(12345)
values = rng.random((100,3))-.5
values[:,1] = 1.6*values[:,1]
values[:,2] = .5*values[:,2]
for ax,axis in zip(axs,['y','x','z']):
axis1,axis2={'x':(1,2),'y':(0,2),'z':(0,1)}[axis]
ax.add_patch(plt.Circle([0,0], radius=.2, color='pink',zorder=-20))
ax.scatter(values[:,axis1],values[:,axis2])
axs[0].set_xlabel('x')
axs[2].set_ylabel('y')
axs[1].set_xlabel('y')
axs[0].set_ylabel('z')
fig.subplots_adjust(.08,.06,.99,.99,0,0)
plt.show()
There are some issues with this plot and the fixes I tried: I would need 'equal' aspect so that the circles are actually circle. I would also need the circles to be of the same size in each subplot. Finally, I would like the space to be optimized (i.e. with as little white space inside and between the subplots as possible).
I have tried sharing the axis between the subplots, then doing .axis('scaled') or .set_aspect('equal','box',share=True) for each axes, but the axis end up not being properly shared, and the circle in each subplot end up of different sizes. And while it crops the subplots to the data, it leaves a lot of space between the subplots. .axis('equal') or .set_aspect('equal','datalim',share=True) without axis shared leaves white space inside the subplots, and with shared axis, it leaves out some data.
Any way to make it work? And it would be perfect if it can work on matplotlib 3.4.3.
You can use a common xlim, ylim for your subplots and set your equal ratio with ax.set_aspect(aspect='equal', adjustable='datalim'):
See full code below:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
fig = plt.figure(figsize=(10,10),facecolor='white')
axs = [fig.add_subplot(223)]
axs.append(fig.add_subplot(224))#,sharey=axs[0]))
axs.append(fig.add_subplot(221))#,sharex=axs[0]))
rng = np.random.default_rng(12345)
values = rng.random((100,3))-.5
values[:,1] = 1.6*values[:,1]
values[:,2] = .5*values[:,2]
for ax,axis in zip(axs,['y','x','z']):
axis1,axis2={'x':(1,2),'y':(0,2),'z':(0,1)}[axis]
ax.add_patch(plt.Circle([0,0], radius=.2, color='pink',zorder=-20))
ax.scatter(values[:,axis1],values[:,axis2])
ax.set_xlim([np.amin(values),np.amax(values)])
ax.set_ylim([np.amin(values),np.amax(values)])
ax.set_aspect('equal', adjustable='datalim')
axs[0].set_xlabel('x')
axs[2].set_ylabel('y')
axs[1].set_xlabel('y')
axs[0].set_ylabel('z')
fig.subplots_adjust(.08,.06,.99,.99,0,0)
plt.show()
The output gives:
I made it work using gridspec (I changed scatter for plot to visually make sure no data gets left out). It requires some tweaking of the figsize to really minimize the white space within the axes. Thank you to #jylls for the intermediate solution.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
%matplotlib inline
rng = np.random.default_rng(12345)
values = rng.random((100,3))-.5
values[:,1] = 1.6*values[:,1]
values[:,2] = .5*values[:,2]
fig = plt.figure(figsize=(10,8),facecolor='white')
ranges = np.ptp(values,axis=0)
gs = GridSpec(2, 2, None,.08,.06,.99,.99,0,0, width_ratios=[ranges[0], ranges[1]], height_ratios=[ranges[1], ranges[2]])
axs = [fig.add_subplot(gs[2])]
axs.append(fig.add_subplot(gs[3]))#,sharey=axs[0]))
axs.append(fig.add_subplot(gs[0]))#,sharex=axs[0]))
for ax,axis in zip(axs,['y','x','z']):
axis1,axis2={'x':(1,2),'y':(0,2),'z':(0,1)}[axis]
ax.add_patch(plt.Circle([0,0], radius=.2, color='pink',zorder=-20))
ax.plot(values[:,axis1],values[:,axis2])
ax.set_aspect('equal', adjustable='datalim')
axs[0].set_xlabel('x')
axs[2].set_ylabel('y')
axs[1].set_xlabel('y')
axs[0].set_ylabel('z')
plt.show()

Plotting a curve in the margin of a figure

I have the plot of a function f, which depends on time in a discontinuous way. More precisely, it has a particular behavior for t1<=t<t2 and another everywhere else, like in the example below
import matplotlib.pyplot as plt
import numpy as np
from pylab import *
l1=1.
l2=5.
t1=20.
t2=50.
tf=120.
def f1(t):
if t<t1:
L = l1
elif t1<=t<t2:
L = l2
else:
L=l1
g=L*t
return g
a=np.linspace(0.,100,1000)
values1=map(f1,a)
fig1=plt.figure(1)
plt.plot(a,values1,color='red')
plt.show()
The plot of the pulse is the following
def f2(t):
if t<t1:
L = l1
elif t1<=t<t2:
L = l2
else:
L=l1
return L
values2=map(f2,a)
fig2=plt.figure(2)
plt.plot(a,values2,color='blue')
plt.show()
I want to make a figure with the red curve as the main plot and a little inset in the top margin of the figure showing the blue curve, without any x axis or y axis, just to make the viewer understand when the change in the parameter L happens.
I think that subplots will do what you want. If you make the top subplot smaller, and take the ticks/labels off it looks like its in the margins. Here's a code snippet that sets up the plot.
f = plt.figure()
# Make 2 subplots arranged vertically with different ratios
(ax, ax2) = f.subplots(2,1, gridspec_kw={'height_ratios':[1,4]})
#remove the labels on your top subplot
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax.plot(a, f2(a))
ax2.plot(a, f1(a), 'r:') #red curve main plt
plt.show()
I used this code to plot a few sinusoids and it came out as follows:
Is this what you're looking for?
Maybe you could use inset_axes from mpl_toolkits.axes_grid1.inset_locator
See for example: https://matplotlib.org/gallery/axes_grid1/inset_locator_demo.html
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
fig, axs = plt.subplots(1, 1)
# Create inset of width 1.3 inches and height 0.9 inches
# at the default upper right location
axins = inset_axes(axs, width='20%', height='20%', loc=2)
And then plot your data in axins:
axins.plot(data)
You can also switch off the ticks and labes using:
axins.axes.get_yaxis().set_visible(False)
axins.axes.get_xaxis().set_visible(False)

Fix location of stippling for subplots

I have been trying to stipple contour plots to show locations where values are statistically significant. However, when I do this in subplots where the significance is the same, the stippling looks different based on the random location of the filled stipples. I have reproduced the problem below. Is there a way to fix the location of the stipples so that they look the same when plotted? Or is there a better way to stipple plots?
These 2 subplots are plotting the exact same data, but the stipples look different.
import numpy as np
from matplotlib import pyplot as plt
#Create some random data
x = np.arange(0,100,1)
x,y = np.meshgrid(x,x)
stipp = 10*np.random.rand(len(x),len(x))
fig =plt.figure(figsize=(12,8))
ax1 = plt.subplot(121)
ax2 = plt.subplot(122)
#Plot stippling
ax1.contourf(x,y,stipp,[0,4],colors='none',hatches='.')
ax2.contourf(x,y,stipp,[0,4],colors='none',hatches='.')
plt.show()
So in case anyone wants to know, the best way to stipple multiple subplots with similar statistical significance is to use a scatter plot as recommended above instead of contouring. Just make sure to sample the data sparingly so you don't have a high density of dots next to each other.
import numpy as np
from matplotlib import pyplot as plt
#Create some random data
x = np.arange(0,100,1)
x,y = np.meshgrid(x,x)
stipp = 10*np.random.rand(len(x),len(x))
fig =plt.figure(figsize=(12,8))
ax1 = plt.subplot(121)
ax2 = plt.subplot(122)
#Plot stippling
ax1.scatter(x[(stipp<=4) & (stipp>=0)][::5],y[(stipp<=4) & (stipp>=0)][::5])
ax2.scatter(x[(stipp<=4) & (stipp>=0)][::5],y[(stipp<=4) & (stipp>=0)][::5])
plt.show()

Python, Matplotlib: Suptitle with arbitrary number of vertical figures

I have a website that produces (depending on available data stations running) an arbitrary number of plots (as an image), that are vertically stacked over one another. An example is the following:
The problem is that depending on the number of vertical plots, the suptitle (top title) goes to a different position. Check the following examples of 5 and 10 plots:
5 plots:
And here's 10 plots:
So for every number of plots, I get a different result. Using fig.tight_layout() didn't help.
What I need is to have the bottom of my text at a certain distance from the top of the plots. Is there a general answer to this problem?
I created some minimal working code that has the number of plots parametrized. Please check it out if you would like to reproduce this problem.
import datetime
import random
import matplotlib
matplotlib.use('Agg') # Force matplotlib not to use any Xwindows backend.
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.image as mpimg
import matplotlib.gridspec as gridspec
import numpy as np
random.seed(datetime.datetime.now())
#initial parameters
numOfPlots = 2
dataLen = 100
randomRange = 10*dataLen
dpiVal = 180
#create data
xData = list(range(dataLen) for x in range(numOfPlots))
yData = list(random.sample(range(randomRange), dataLen) for x in range(numOfPlots))
#matplotlib initialize plot
gs = gridspec.GridSpec(numOfPlots,1)
plt.cla()
plt.clf()
fig = plt.figure()
ax = None
for i in list(range(numOfPlots)):
if i == 0:
ax = fig.add_subplot(gs[i])
else:
ax = fig.add_subplot(gs[i],sharex=ax)
ax.plot(xData[i], yData[i])
labelSize = 10
ax.set_ylabel("Hi there",size=8)
ax.get_yaxis().set_label_coords(-0.07,0.5)
plt.yticks(size=8)
plt.ticklabel_format(style='sci', axis='y', scilimits=(0,0),useOffset=True)
plt.subplots_adjust(hspace = 0.3)
if i == numOfPlots-1:
plt.xticks(rotation=0,size=7)
max_xticks = 10
xloc = plt.MaxNLocator(max_xticks)
ax.xaxis.set_major_locator(xloc)
ax=plt.gca()
else:
plt.tick_params(
axis='x', # changes apply to the x-axis
labelbottom='off') # labels along the bottom edge are off
ax_right = ax.twinx()
ax_right.yaxis.set_ticks_position('right')
ax_right.set_ylabel("Nice to see you!",size=labelSize)
ax_right.get_yaxis().set_ticks([])
#the following sets the size and the aspect ratio of the plot
fig.set_size_inches(10, 1.8*numOfPlots)
fig.suptitle("Hi there, this is the first line\nAnd this is the second!!!")
fig.savefig("img_"+str(numOfPlots)+".png",bbox_inches='tight',dpi=dpiVal)
I suggest trying something manual: adding text annotation with position in units of the figure relative coordinates.
Consider these two dummy examples:
hf,ax = plt.subplots(nrows=3)
hf.text(0.5,0.92,
"Hi there, this is the first line\nAnd this is the second!!!",
horizontalalignment='center')
hf,ax = plt.subplots(nrows=7)
hf.text(0.5,0.92,
"Hi there, this is the first line\nAnd this is the second!!!",
horizontalalignment='center')
The result has the "suptitle" located in the exact same position:

Categories