multicolor line on matplotlib - python

Is it possible to have something similar to edgecolor and facecolor for plt.plot()? I need to plot a curve with a different colored boundary around it for e.g. a segment of the line would look like ||| with the outer lines in a different color and the inner line in a different color. Can this be done using a single plot command rather than plotting three plots?

You can do this using the matplotlib.patheffects module. You can set the path_effect of a line by using the path_effects kwarg.
Specifically in this case, we can use the Stroke class for the outline, and the Normal class for the inner part of the line (this just uses the linewidth and color specified by plt.plot). See the example below.
import matplotlib.pyplot as plt
import matplotlib.patheffects as path_effects
import numpy as np
fig, ax = plt.subplots(1)
ax.plot(np.random.rand(5), linewidth=4, color='r', path_effects=[
path_effects.Stroke(linewidth=8, foreground='black'),
path_effects.Normal()
])
plt.show()

Related

How to change color in statsmodel's plot_acf function?

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.

Cartopy coastlines hidden by inset_axes use of Axes.pie

I am producing a map of the world with pie charts in individual model grid boxes. I make the map and coastlines using cartopy. The pie charts I produce using inset_axes. Unfortunately the pie charts hide the coastlines and I'd like to see them clearly.
Minimum working example:
import cartopy.crs as ccrs
import numpy as np
import cartopy.feature as feature
import matplotlib.pyplot as plt
def plot_pie_inset(dataframe_pie,ilat_pie,ilon_pie,axis_main,width_local,alpha_local):
ax_sub= inset_axes(axis_main, width=width_local, height=width_local, loc=3, bbox_to_anchor=(ilat_pie, ilon_pie),bbox_transform=axis_main.figure.transFigure, borderpad=0.0)
wedges,texts= ax_sub.pie(dataframe_pie,colors=colors_dual)
for w in wedges:
w.set_linewidth(0.02)
w.set_alpha(alpha_local)
w.set_zorder(1)
plt.axis('equal')
colors_dual=['RosyBrown','LightBlue']
lat_list= np.arange(0.2,0.7,0.05)
fig= plt.figure()
ax_main= plt.subplot(1,1,1,projection=ccrs.PlateCarree())
ax_main.coastlines(zorder=3)
for ilat in np.arange(len(lat_list)):
plot_pie_inset([75,25],lat_list[ilat],0.72,ax_main,0.2,0.9)
plt.show()
I can see the coastlines by making the pie charts partially transparent by reducing the alpha value. However, this makes the colors somewhat muted. My aim is to have the coastlines as the topmost layer.
I have attempted to use 'zorder' to force the coastlines to the top layer. However, 'zorder' cannot be passed to inset_axes, nor to ax.pie so I've made the patches of color in pie charts translucent. This fails because the ax_main.coastlines does not have its own 'zorder'. The coastline zorder seems to be tied to that of ax_main. There is no benefit in increasing the zorder of ax_main.
Any suggestions greatly welcomed.
The problem is that each axes either lies on top or below another axes. So changing the zorder of artists within axes, does not help here. In principle, one could set the zorder of the axes themselves, putting the inset axes behind the main axes.
ax_sub.set_zorder(axis_main.get_zorder()-1)
Cartopy's GeoAxes uses its own background patch. This would then need to be set to invisble.
ax_main.background_patch.set_visible(False)
Complete example:
import cartopy.crs as ccrs
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
def plot_pie_inset(dataframe_pie,ilat_pie,ilon_pie,axis_main,width_local,alpha_local):
ax_sub= inset_axes(axis_main, width=width_local, height=width_local, loc=3,
bbox_to_anchor=(ilat_pie, ilon_pie),
bbox_transform=axis_main.transAxes,
borderpad=0.0)
wedges,texts= ax_sub.pie(dataframe_pie,colors=colors_dual)
for w in wedges:
w.set_linewidth(0.02)
w.set_alpha(alpha_local)
w.set_zorder(1)
plt.axis('equal')
# Put insets behind main axes
ax_sub.set_zorder(axis_main.get_zorder()-1)
colors_dual=['RosyBrown','LightBlue']
lat_list= np.arange(0.2,0.7,0.05)
fig= plt.figure()
ax_main= plt.subplot(1,1,1,projection=ccrs.PlateCarree())
ax_main.coastlines()
# set background patch invisible, such that axes becomes transparent
# since the GeoAxes from cartopy uses a different patch as background
# the following does not work
# ax_main.patch.set_visible(False)
# so we need to set the GeoAxes' background_patch invisible
ax_main.background_patch.set_visible(False)
for ilat in np.arange(len(lat_list)):
plot_pie_inset([75,25],lat_list[ilat],0.72,ax_main,0.2,0.9)
plt.show()
An alternative solution suggest by a colleague neglects to use the inset_axes but achieves a similar result. The main difference is that the coordinate system in this solution is in the original latitude/longitude coordinates rather than figure coordinates.
def plot_pie_direct(dataframe_pie,ilat_pie,ilon_pie,axis_main,width_local,alpha_local):
wedges,texts= ax_main.pie(dataframe_pie,colors=colors_aer_atm,radius=width_local)
for w in wedges:
w.set_linewidth(0.02) ## Reduce linewidth to near-zero
w.set_center((ilat_pie,ilon_pie))
w.set_zorder(0)
fig= plt.figure()
ax_main= plt.axes(projection=ccrs.PlateCarree())
ax_main.coastlines(zorder=3)
ax_main.set_global()
lim_x= ax_main.get_xlim()
lim_y= ax_main.get_ylim()
for ilat in np.arange(len(lat_list_trim)):
plot_pie_direct(frac_aer_atm_reshape_trim[:,ilat,ilon],x_val_pies[ilon],y_val_pies[ilat],ax_main,lat_list_diff_trim,0.9)
ax_main.coastlines(zorder=3)
ax_main.set_xlim(lim_x)
ax_main.set_ylim(lim_y)
plt.show()

In Pyplot, How can we change the line width of a plot once it is already plotted?

Consider the following python module 'plot_figure.py' defining PlotFigure(). Note that it is a pseudo-code.
import matplotlib.pyplot as plt
def PlotFigure(x)
# Some other routines..
plt.plot(x)
# Some other routines..
I would like to call plot_figure.PlotFigure, but after plotting a figure, I would like to change the line widths of this figure. Even though PlotFigure() may include other routines, the lines in the figure are plotted using plt.plot() as shown in the above pseudo-code.
The following is the code that calls plot_figure.PlotFigure()
#!/usr/bin/python
import matplotlib.pyplot as plt
import plot_figure
x_data = [ # some data ]
plot_figure.PlotFigure(x_data)
#***I would like to change the line width of the figure here***
plt.show()
I know that I can get the figure handle using fig = plt.gcf(), but plt.setp(fig, linewidth=2) doesn't work.
Could anyone give some suggestion on this?
First let me note that the generic way to set the linewidth (or any other plot parameter) would be to give it as an argument to the plot command.
import matplotlib.pyplot as plt
def PlotFigure(x, **kwargs):
# Some other routines..
plt.plot(x, linewidth=kwargs.get("linewidth", 1.5) )
# Some other routines..
and the call
plot_figure.PlotFigure(x_data, linewidth=3.)
If this really is not an option, you'd need to get the lines from the figure.
The easiest case would be if there was only one axes.
for line in plt.gca().lines:
line.set_linewidth(3.)

python matplotlib colormap gets inherited by subsequent plots as well

I have two plots using matlpotlib in python. In the first one, I have a colormap plt.summer(). Unfortunately my second plot also inherits this colormap. How can I stop the first one from propagating its specs to other plots? How can I turn it off once I pass to another plot?
Try
matplotlib.cm.set_cmap(cm.jet)
Or
import matplotlib.cm as cm
import matplotlib.pyplot as plt
...
cmap = plt.get_cmap('BlueRed2')
plt.scatter(x, y, c=t, cmap=cmap)
...

How to change the plot line color from blue to black?

I am stuck when I have generated a set of data and tried to color the plot line in python.
For example I would like to change the line color from blue to black here.
This is what I have and returns is the set of data that I got from pandas.
ax=plt.gca()
ax.set_axis_bgcolor('#cccccc')
returns.plot()
The usual way to set the line color in matplotlib is to specify it in the plot command. This can either be done by a string after the data, e.g. "r-" for a red line, or by explicitely stating the color argument.
import matplotlib.pyplot as plt
plt.plot([1,2,3], [2,3,1], "r-") # red line
plt.plot([1,2,3], [5,5,3], color="blue") # blue line
plt.show()
See also the plot command's documentation.
In case you already have a line with a certain color, you can change that with the lines2D.set_color() method.
line, = plt.plot([1,2,3], [4,5,3], color="blue")
line.set_color("black")
Setting the color of a line in a pandas plot is also best done at the point of creating the plot:
import matplotlib.pyplot as plt
import pandas as pd
df = pd.DataFrame({ "x" : [1,2,3,5], "y" : [3,5,2,6]})
df.plot("x", "y", color="r") #plot red line
plt.show()
If you want to change this color later on, you can do so by
plt.gca().get_lines()[0].set_color("black")
This will get you the first (possibly the only) line of the current active axes.
In case you have more axes in the plot, you could loop through them
for ax in plt.gcf().axes:
ax.get_lines()[0].set_color("black")
and if you have more lines you can loop over them as well.
If you have more than one plot line and you want to set a different color for each line,
use the attribute below in your plot() where each index represents a line color for a column.
color=['color1','color2']
If you get the object after creation (for instance after "seasonal_decompose"), you can always access and edit the properties of the plot; for instance, changing the color of the first subplot from blue to black:
plt.axes[0].get_lines()[0].set_color('black')

Categories

Resources