I am trying to compare wind roses in python, but it is difficult because I cannot figure out how to make the same scale across all of the plots. Someone else asked the same question here Custom percentage scale used by windrose.py but it was not answered .
Example code:
from windrose import WindroseAxes
import numpy as np
import matplotlib.pyplot as plt
wind_dir = np.array([30,45,90,43,180])
wind_sd = np.arange(1,wind_dir.shape[0]+1)
bins_range = np.arange(1,6,1) # this sets the legend scale
fig,ax = plt.subplots()
ax = WindroseAxes.from_ax()
bin_range below sets scale of bars, but I need to change the y-axis frequency scale so it can be compared to other wind roses with different data.
ax.bar(wind_dir,wind_sd,normed=True,bins=bins_range)
this set_ylim does seem to work, but the yaxis ticks do not change
ax.set_ylim(0,50)
this set_ticks line below does not do anything and I do not know why
ax.yaxis.set_ticks(np.arange(0,50,10))
ax.set_legend()
plt.show()
from windrose import WindroseAxes
import numpy as np
import matplotlib.pyplot as plt
wind_dir = np.array([30,45,90,43,180])
wind_sd = np.arange(1,wind_dir.shape[0]+1)
bins_range = np.arange(1,6,1) # this sets the legend scale
ax = WindroseAxes.from_ax()
ax.bar(wind_dir,wind_sd,normed=True,bins=bins_range)
ax.set_yticks(np.arange(10, 60, step=10))
ax.set_yticklabels(np.arange(10, 60, step=10))
plt.show()
Related
I do have a question with matplotlib in python. I create different figures, where every figure should have the same height to print them in a publication/poster next to each other.
If the y-axis has a label on the very top, this shrinks the height of the box with the plot. So I use MaxNLocator to remove the upper and lower y-tick. In some plots, I want to have the 1.0 as a number on the y-axis, because I have normalized data. So I need a solution, which expands in these cases the y-axis and ensures 1.0 is a y-Tick, but does not corrupt the size of the figure using tight_layout().
Here is a minimal example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
x = np.linspace(0,1,num=11)
y = np.linspace(1,.42,num=11)
fig,axs = plt.subplots(1,1)
axs.plot(x,y)
locator=MaxNLocator(prune='both',nbins=5)
axs.yaxis.set_major_locator(locator)
plt.tight_layout()
fig.show()
Here is a link to a example-pdf, which shows the problems with height of upper boxline.
I tried to work with adjust_subplots() but this is of no use for me, because I vary the size of the figures and want to have same the font size all the time, which changes the margins.
Question is:
How can I use MaxNLocator and specify a number which has to be in the y-axis?
Hopefully someone of you has some advice.
Greetings,
Laenan
Assuming that you know in advance how many plots there will be in 1 row on a page one way to solve this would be to put all those plots into one figure - matplotlib will make sure they are alinged on axes:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
x = np.linspace(0, 1, num=11)
y = np.linspace(1, .42, num=11)
fig, (ax1, ax2) = plt.subplots(1,2, figsize=(8,3), gridspec_kw={'wspace':.2})
ax1.plot(x,y)
ax2.plot(x,y)
locator=MaxNLocator(prune='both', nbins=5)
ax1.yaxis.set_major_locator(locator)
# You don't need to use tight_layout and using it might give an error
# plt.tight_layout()
fig.show()
I´d like to create an autocorrelation plot of financial market returns and use statsmodel's plot_acf() function for that. However, I am trying to alter the color of all the plot elements but my approach only modifies the color of the markers. Though, neither the bars nor the confidence interval receives the color="red" argument. I am using Python (3.8.3) and Statsmodels (0.12.1).
The following displays a simple code snippet of my current approach to the autocorrelation plot:
# import required package
import pandas as pd
from statsmodels.graphics.tsaplots import plot_acf
# initialize acplot
fig, ax = plt.subplots(nrows=1, ncols=1, facecolor="#F0F0F0")
# autocorrelation subplots
plot_acf(MSCIFI_ret["S&P500"], lags=10, alpha=0.05, zero=False, title=None, ax=ax, color="red")
ax.legend(["S&P500"], loc="upper right", fontsize="x-small", framealpha=1, edgecolor="black", shadow=None)
ax.grid(which="major", color="grey", linestyle="--", linewidth=0.5)
ax.set_xticks(np.arange(1, 11, step=1))
# save acplot
fig.savefig(fname=(plotpath + "test.png"))
plt.clf()
plt.close()
And here comes the corresponding autocorrelation plot itself:
Does anyone know how to deal with that problem? Any ideas would be much appreciated.
I have the suspicion that they (accidentally?) hardcoded the color for the confidence interval, overruling any changes the user makes (for instance, the edgecolor of this area can be modified). I did not see in the source code a way to change the color of the CI polygon. rcParams["patch.facecolor"] = "red" should change the color, alas, it does not. But we can retrospectively change the color of the generated polygon:
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm
from matplotlib.collections import PolyCollection
#sample data from their website
dta = sm.datasets.sunspots.load_pandas().data
dta.index = pd.Index(sm.tsa.datetools.dates_from_range('1700', '2008'))
del dta["YEAR"]
curr_fig, curr_ax = plt.subplots(figsize=(10, 8))
my_color="red"
#change the color of the vlines
sm.graphics.tsa.plot_acf(dta.values.squeeze(), lags=40, ax=curr_ax, color=my_color, vlines_kwargs={"colors": my_color})
#get polygon patch collections and change their color
for item in curr_ax.collections:
if type(item)==PolyCollection:
item.set_facecolor(my_color)
plt.show()
Update
Given the muddled approach of keywords, kwarg dictionaries, and retrospective changes, I think the code might be more readable when changing all colors after statsmodels has plotted the graph:
...
from matplotlib.collections import PolyCollection, LineCollection
...
curr_fig, curr_ax = plt.subplots(figsize=(10, 8))
sm.graphics.tsa.plot_acf(dta.values.squeeze(), lags=40, ax=curr_ax)
my_color="red"
for item in curr_ax.collections:
#change the color of the CI
if type(item)==PolyCollection:
item.set_facecolor(my_color)
#change the color of the vertical lines
if type(item)==LineCollection:
item.set_color(my_color)
#change the color of the markers/horizontal line
for item in curr_ax.lines:
item.set_color(my_color)
plt.show()
Follow-up on Mr. T's answer.
# a PR at statsmodels would have been more productive...
# Authors: Mr. T (Jan 2021) and PatrickT (Aug 2022)
def plot_acf_colors(ax, markercolor="red", linecolor="black", facecolor="silver", barcolor="darkorange", linewidth=1):
"""utility function to get some control over colors with plot_acf()"""
from statsmodels.graphics.tsaplots import plot_pacf
from statsmodels.graphics.tsaplots import plot_acf
from matplotlib.collections import PolyCollection, LineCollection
for item in ax.collections:
# change the color of the confidence interval
if type(item) == PolyCollection:
item.set_facecolor(facecolor)
# change the color of the vertical lines
if type(item) == LineCollection:
item.set_color(barcolor)
# change the color of the markers
[line.get_label() for line in ax.lines]
for item in ax.lines:
item.set_color(markercolor)
# change the color of the horizontal lines
ax.lines[0].set_color(linecolor)
ax.lines[0].set_linewidth(linewidth)
#ax.lines.remove(ax.lines[0])
return ax
# basic packages
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.graphics.tsaplots import plot_pacf
from statsmodels.graphics.tsaplots import plot_acf
# sample data
import statsmodels.api as sm
dta = sm.datasets.sunspots.load_pandas().data
dta.index = pd.Index(sm.tsa.datetools.dates_from_range('1700', '2008'))
del dta["YEAR"]
# custom plot
f, ax = plt.subplots(figsize=(10, 8))
plot_acf(dta.values.squeeze(), lags=40, ax=ax)
ax = plot_acf_colors(ax)
plt.savefig("stackoverflow-plot-acf-colors.png")
plt.close()
The color of the horizontal line at 0 can now be controlled independently of the markers. The horizontal line's thickness has been tweaked. More could be done along the same lines of course, like controlling the thickness of the vertical bars or the the size of the markers, but I had to stop somewhere.
I am trying to create a graphic where I overlay multiple contour plots on a single image. So I want to have colorbars for each of the plots, as well as a legend indicating what each contour represents. However Matplotlib will not allow me to create a separate legend for my contour plots. Simple example:
import matplotlib as mpl
import matplotlib.pyplot as plt
import cartopy
import cartopy.crs as ccrs
import numpy as np
def create_contour(i,j):
colors = ["red","green","blue"]
hatches = ['-','+','x','//','*']
fig = plt.figure()
ax = plt.axes(projection=ccrs.PlateCarree())
ax.set_extent((-15.0,15.0,-15.0,15.0))
delta = 0.25
x = np.arange(-3.0,3.0,delta)
y = np.arange(-2.0,2.0,delta)
X, Y = np.meshgrid(x, y)
data = np.full(np.shape(X), 1.0)
plot = ax.contourf(X,Y,data, levels = [float(i),float(i+1)], hatch=[hatches[j]], colors = colors[i], label="label")
plt.legend(handles=[plot], labels=["label"])
plt.savefig("figure_"+str(i)+".png")
create_contour(1,3)
When I run this, I get the following message:
UserWarning: Legend does not support
(matplotlib.contour.QuadContourSet object at 0x7fa69df7cac8)
instances. A proxy artist may be used instead. See:
http://matplotlib.org/users/legend_guide.html#creating-artists-specifically-for-adding-to-the-legend-aka-proxy-artists
"aka-proxy-artists".format(orig_handle)
But as far as I can tell, I am following those directions as closely as possible, the only difference being that they do not use contourf in the example.
Any help would be greatly appreciated.
The comments to your question look like they have solved the question (by making custom patches and passing those through to the legend). There is also an example that I added many years ago to the matplotlib documentation to do something similar (about the same time I added contour hatching to matplotlib): https://matplotlib.org/examples/pylab_examples/contourf_hatching.html#pylab-examples-contourf-hatching
It is such a reasonable request that there is even a method on the contour set to give you legend proxies out of the box: ContourSet.legend_elements.
So your example might look something like:
%matplotlib inline
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import numpy as np
fig = plt.figure(figsize=(10, 10))
ax = plt.axes(projection=ccrs.PlateCarree())
ax.coastlines('10m')
y = np.linspace(40.0, 60.0, 30)
x = np.linspace(-10.0, 10.0, 40)
X, Y = np.meshgrid(x, y)
data = 2*np.cos(2*X**2/Y) - np.sin(Y**X)
cs = ax.contourf(X, Y, data, 3,
hatches=['//','+','x','o'],
alpha=0.5)
artists, labels = cs.legend_elements()
plt.legend(handles=artists, labels=labels)
plt.show()
I do have a question with matplotlib in python. I create different figures, where every figure should have the same height to print them in a publication/poster next to each other.
If the y-axis has a label on the very top, this shrinks the height of the box with the plot. So I use MaxNLocator to remove the upper and lower y-tick. In some plots, I want to have the 1.0 as a number on the y-axis, because I have normalized data. So I need a solution, which expands in these cases the y-axis and ensures 1.0 is a y-Tick, but does not corrupt the size of the figure using tight_layout().
Here is a minimal example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
x = np.linspace(0,1,num=11)
y = np.linspace(1,.42,num=11)
fig,axs = plt.subplots(1,1)
axs.plot(x,y)
locator=MaxNLocator(prune='both',nbins=5)
axs.yaxis.set_major_locator(locator)
plt.tight_layout()
fig.show()
Here is a link to a example-pdf, which shows the problems with height of upper boxline.
I tried to work with adjust_subplots() but this is of no use for me, because I vary the size of the figures and want to have same the font size all the time, which changes the margins.
Question is:
How can I use MaxNLocator and specify a number which has to be in the y-axis?
Hopefully someone of you has some advice.
Greetings,
Laenan
Assuming that you know in advance how many plots there will be in 1 row on a page one way to solve this would be to put all those plots into one figure - matplotlib will make sure they are alinged on axes:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
x = np.linspace(0, 1, num=11)
y = np.linspace(1, .42, num=11)
fig, (ax1, ax2) = plt.subplots(1,2, figsize=(8,3), gridspec_kw={'wspace':.2})
ax1.plot(x,y)
ax2.plot(x,y)
locator=MaxNLocator(prune='both', nbins=5)
ax1.yaxis.set_major_locator(locator)
# You don't need to use tight_layout and using it might give an error
# plt.tight_layout()
fig.show()
Is there an easy way to draw a zigzag or wavy line in matplotlib?
I'm aware of the different line styles (http://matplotlib.org/examples/lines_bars_and_markers/line_styles_reference.html), and I'm of course aware that instead of plotting
plt.figure(); plt.plot(n.linspace(0.7,1.42,100),[0.7]*100)
I could plot
plt.figure(); plt.plot(n.linspace(0.7,1.42,100),[0.69,0.71]*50)
for a zigzag-line, but I was wondering whether there was a more straightforward way?
Yes there is, but it comes with a little bit of fallout. The easiest way is to use the xkcd mode in matplotlib.
import numpy as np
import matplotlib.pyplot as plt
plt.xkcd()
plt.figure()
plt.plot(np.linspace(0.7,1.42,100),[0.7]*100)
plt.show()
Which gives you the following:
If you take a look at the code used to achieve this you will find that the xkcd function makes some changes to the rcParams dictionary. Most notably the entry rcParams['path.sketch'] = (scale, length, randomness) which is a path effect that is able to simulate a hand drawn look. The default parameters used by xkcd style are:
# explanation from the docstring of the xkcd function
scale = 1 # amplitude of the wiggle
length = 100 # length of the wiggle along the line
randomness = 2 # scale factor for shrinking and expanding the length
You can change the entries in the rcParams dictionary if you import it from the matplotlib package. In the following example I increased the randomness value from 2 to 100:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rcParams
rcParams['path.sketch'] = (1, 100, 100)
plt.plot(np.linspace(0.7,1.42,100),[0.7]*100)
plt.show()
Which will result in the following plot:
As you can see, more jiggling and the font used for the ticks is still 'normal'. However, the style is also used to draw the axes and so far I have not found a way around that.
Two workarounds could be:
Work without drawn borders/ spines.
Plot spines and line independently (hard and annoying to automize).
Dig through the documentation of matplotlib and path styles and find out if there is a way to set path styles only for a subset of drawn lines.
Option 1 can be achieved like this:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rcParams
rcParams['path.sketch'] = (10, 10, 100)
fig = plt.plot(np.linspace(0.7,1.42,100),[0.7]*100)
for pos, spine in fig[0].axes.spines.items():
spine.set_visible(False)
plt.show()
Which, in my opinion look quite ok. borders around plots are highly overrated anyways.
Edit: Less Chaos
To get an evenly waved line, set the randomness parameter to 1 and pick small values for amplitude and length:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rcParams
rcParams['path.sketch'] = (3, 10, 1)
fig = plt.plot(np.linspace(0.7,1.42,100),[0.7]*100)
for pos, spine in fig[0].axes.spines.items():
spine.set_visible(False)
plt.show()
Bonus image: More Chaos
rcParams['path.sketch'] = (100, 1, 100)
You can apply the change in rcParams['path.sketch'] dictionary only to selected curves using with.
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
# prepare some fancy data
x = np.linspace(0,5,200)
y_0 = 10*x**0.2-x**1.5
y_1 = 20*np.sin(x)
y_2 = x**2
# prepare figure and axis
fig, ax = plt.subplots(nrows=1, ncols = 1, figsize = (5,3), dpi = 128)
# plot with some normal style
ax.plot(x, y_0, color = 'gray', ls='-.', lw = 2, label = 'normal style')
# now plot the wavy-like style!!!!
with mpl.rc_context({'path.sketch': (5, 15, 1)}):
ax.plot(x, y_1, color = 'blue', label = 'wavy style!')
# again plot with some different normal style
ax.plot(x, y_2, color = 'orange', ls = '-', lw = 3, label = 'again normal style')
ax.legend(loc='best') # turn on legend with automatic best location
plt.show()