How to solve " 'PathCollection' object has no attribute 'yaxis' " error? - python

I'm a MSc Student and I used to make graphs and plots with commercial packages like OriginPro, Excel and Matlab. Although these softwares provide a great user experience, there are some major disadvantages as they are specific OS dependent and, in general, very expensive.
Hence, I started to learn Python using matplotlib library with VS Code, however I'm having some problems with some library functions and statements that seems to be standard from matplotlib and numPy, but it doesnt work.
For example, I'm making some templates for scatter plots and I can't control minor ticks because it doesn't recognize the statements xaxix and yaxix:
Sample of the code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator, AutoMinorLocator
.
.
.
fig = plt.figure(figsize=(x_pixels/my_dpi, y_pixels/my_dpi), dpi=my_dpi)
ax = plt.scatter(x*format_x, y*format_y, s = size, alpha = transparency, color = color, label = legend_text)
.
.
.
# Major Ticks
plt.tick_params(axis = 'both', which = 'major', length = majorT_length, direction = majorT_direction, color = majorT_color, labelsize = label_size, top = 'on', right = 'on')
# Minor Ticks
plt.minorticks_on()
plt.tick_params(axis='both', which='minor', length = minorT_length, direction = minorT_direction, color = minorT_color, top = 'on', right = 'on')
ax.yaxis.set_minor_locator(AutoMinorLocator(2))
ax.xaxis.set_minor_locator(AutoMinorLocator(2))
# Figure Layout
plt.tight_layout()
plt.savefig(output_file, dpi=my_dpi, bbox_inches=borders)
plt.show()
and the Terminal show this error:
File "c:/Users/luagu/Desktop/Python Matplotlib Training/Scatter_Template.py", line 128, in <module>
ax.yaxis.set_minor_locator(AutoMinorLocator(2))
AttributeError: 'PathCollection' object has no attribute 'yaxis'
What I'm doing wrong?
Thanks in advance!

You wrote ax = plt.scatter but your ax here is an artist returned by the scatter method, not an Axes object. What you want to do is:
plt.scatter(...)
...
ax = plt.gca()
ax.yaxis.set_minor_locator(AutoMinorLocator(2))
ax.xaxis.set_minor_locator(AutoMinorLocator(2))

Related

Why can't I change the title of the x-axis and add a curve to a graph in a module containing a plotting function?

I am looking to study the evolution of the population using the pyworld3 module.
For this I entered the parameters and details I wanted. I get the result I wanted with my code.
Here is my code:
import pyworld3
from pyworld3 import World3
import numpy as np
import matplotlib.pyplot as plt
from pyworld3.utils import plot_world_variables
world3 = World3(year_min=1951, year_max=2100, dt=1)
world3.init_world3_constants(p1i=92e7,
p2i=70e7, p3i=19e7, p4i=6e7,
dcfsn=3,
fcest=4000, hsid=20, ieat=3,
len=42, # life expectancy normal.
lpd=20, mtfn=12, pet=4000, rlt=30, sad=20,
zpgt=4000, ici=2.1e11, sci=1.44e11, iet=4000,
iopcd=400,lfpf=0.75, lufdt=2, icor1=3, icor2=3,
scor1=1,
scor2=1, alic1=14, alic2=14, alsc1=20, alsc2=20,
fioac1=0.43, fioac2=0.43,
ali=0.9e9, pali=2.3e9, lfh=0.7, palt=3.2e9,
pl=0.1, alai1=2, alai2=2, io70=7.9e11, lyf1=1,
lyf2=1, sd=0.07, uili=8.2e6, alln=6000, uildt=10,
lferti=600, ilf=600, fspd=2, sfpc=230,
ppoli=2.5e7, ppol70=1.36e8, ahl70=1.5, amti=1,
imti=10, imef=0.1, fipm=0.001, frpm=0.02,
ppgf1=1, ppgf2=1, ppgf21=1, pptd1=20, pptd2=20,
nri=1e12, nruf1=1, nruf2=1)
world3.init_world3_variables()
world3.set_world3_table_functions(json_file=None)
world3.set_world3_delay_functions(method= 'odeint')
world3.run_world3()
plot_world_variables(world3.time,
[world3.nrfr, world3.iopc, world3.fpc, world3.pop,
world3.ppolx],
["NRFR", "IOPC", "FPC", "POP", "PPOLX"],
[[0, 1], [0, 1e3], [0, 1e3], [5e9, 12e9], [0, 32]],
# img_background="./img/fig7-7.png",
figsize=(12, 8),
title="Evolution of the world population",
grid=True)
Here is the output I get:
However I would like to change the title of the x-axis and also add a curve on the graph with plt.plot.
I can choose the title I want to give to the graph because there is an argument for that in plot_world_variables but there is no argument to choose the title of the x-axis.
So I tried to make these changes with plt.gcf() and plt.gca().
Here is what I added after my previous code:
# First we get its Axes:
axes: plt.Axes = plt.gcf().gca()
# On it, we can plot:
X = np.linspace(-2, 0, 100)
Y = X2*2-1
axes.plot(X2, Y2, label="another curve")
plt.legend()
# And adjust things:
axes.set_xlabel("Year")
plt.show()
I don't get an error when adding this code. In fact, I get nothing at all. Nothing changes when I run the code. Python gives me exactly the same output as the one I got before.
Where do you think this problem comes from and how can I fix it?
P.S.: I saw that someone had asked the same question as me formerly but even reading his post I still can't figure out my problem.
Sadly, plot_world_variables doesn't return anything. A quick and dirty solution: you can easily copy the source code of that function and apply the necessary edits. I've looked at it and there is nothing fancy going on, easy edit to do :)
EDIT: source code of that function.
from matplotlib.ticker import EngFormatter
from matplotlib.image import imread
from numpy import isnan
import matplotlib.pyplot as plt
def plot_world_variables(time, var_data, var_names, var_lims,
img_background=None,
title=None,
figsize=None,
dist_spines=0.09,
grid=False):
"""
Plots world state from an instance of World3 or any single sector.
"""
prop_cycle = plt.rcParams['axes.prop_cycle']
colors = prop_cycle.by_key()['color']
var_number = len(var_data)
fig, host = plt.subplots(figsize=figsize)
axs = [host, ]
for i in range(var_number-1):
axs.append(host.twinx())
fig.subplots_adjust(left=dist_spines*2)
for i, ax in enumerate(axs[1:]):
ax.spines["left"].set_position(("axes", -(i + 1)*dist_spines))
ax.spines["left"].set_visible(True)
ax.yaxis.set_label_position('left')
ax.yaxis.set_ticks_position('left')
if img_background is not None:
im = imread(img_background)
axs[0].imshow(im, aspect="auto",
extent=[time[0], time[-1],
var_lims[0][0], var_lims[0][1]], cmap="gray")
ps = []
for ax, label, ydata, color in zip(axs, var_names, var_data, colors):
ps.append(ax.plot(time, ydata, label=label, color=color)[0])
axs[0].grid(grid)
axs[0].set_xlim(time[0], time[-1])
for ax, lim in zip(axs, var_lims):
ax.set_ylim(lim[0], lim[1])
for ax_ in axs:
formatter_ = EngFormatter(places=0, sep="\N{THIN SPACE}")
ax_.tick_params(axis='y', rotation=90)
ax_.yaxis.set_major_locator(plt.MaxNLocator(5))
ax_.yaxis.set_major_formatter(formatter_)
tkw = dict(size=4, width=1.5)
axs[0].set_xlabel("time [years] asd")
axs[0].tick_params(axis='x', **tkw)
for i, (ax, p) in enumerate(zip(axs, ps)):
ax.set_ylabel(p.get_label(), rotation="horizontal")
ax.yaxis.label.set_color(p.get_color())
ax.tick_params(axis='y', colors=p.get_color(), **tkw)
ax.yaxis.set_label_coords(-i*dist_spines, 1.01)
if title is not None:
fig.suptitle(title, x=0.95, ha="right", fontsize=10)
plt.tight_layout()
Now you can copy it and modify it to your liking.

Python: Highlighting, marking or indicating point in (scatter) plot

UPDATE
Trying some more, I managed to run this code without error:
from matplotlib.pyplot import figure
dict = pd.DataFrame({"Return": mkw_returns, "Standard Deviation": mkw_stds})
dict.head()
#plt.annotate("Sharpe Ratio", xytext=(0.5,0.5), xy=(0.03,0.03) , arrowprops=dict(facecolor='blue', shrink=0.01, width=220)) # arrowprops={width = 3, "facecolor":
#dict.plot(x="Standard Deviation", y = "Return", kind="scatter", figsize=(10,6))
#plt.xlabel("Standard Deviations")
#plt.ylabel("log_Return YoY")
figure(num=None, figsize=(15, 10), dpi=100, facecolor='w', edgecolor='k')
plt.plot( 'Standard Deviation', 'Return', data=dict, linestyle='none', marker='o')
plt.xlabel("Standard Deviations")
plt.ylabel("log_Return YoY")
# Annotate with text + Arrow
plt.annotate(
# Label and coordinate
'This is a Test', xy=(0.01, 1), xytext=(0.01, 1), color= "r", arrowprops={"facecolor": 'black', "shrink": 0.05}
)
Which now works YaY, can anybody shed some light onto this issue? Im not so sure why it suddenly started working. Thank you :)
Also, how would I simply mark a point, instead of using the arrow?
Problem: Cannot figure out how to mark/select/highlight a specific point in my scatter graph
(Python 3 Beginner)
So my goal is to highlight one or more points in a scatter graph with some text by it or supplied by a legend.
https://imgur.com/a/VWeO1EH
(not enough reputation to post images, sorry)
dict = pd.DataFrame({"Return": mkw_returns, "Standard Deviation": mkw_stds})
dict.head()
#plt.annotate("Sharpe Ratio", xytext=(0.5,0.5), xy=(0.03,0.03) , arrowprops=dict(facecolor='blue', shrink=0.01, width=220)) # arrowprops={width = 3, "facecolor":
dict.plot(x="Standard Deviation", y = "Return", kind="scatter", figsize=(10,6))
plt.xlabel("Standard Deviations")
plt.ylabel("log_Return YoY")
The supressed "plt.annotate" would give an error as specified below.
Specifically i would like to select the sharpe ratio, but for now Im happy if I manage to select any point in the scatter graph.
Im truly confused how to work with matplotlib, so any help is welcomed
I tried the following solutions I found online:
I)
This shows a simple way to use annotate in a plot, to mark a specific point by an arrow.
https://www.youtube.com/watch?v=ItHDZEE5wSk
However the pd.dataframe environment does not like annotate and i get the error:
TypeError: 'DataFrame' object is not callable
II)
Since Im running into issues with annotate in a Data Frame environment, I looked at the following solution
Annotate data points while plotting from Pandas DataFrame
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import string
df = pd.DataFrame({'x':np.random.rand(10), 'y':np.random.rand(10)},
index=list(string.ascii_lowercase[:10]))
fig, ax = plt.subplots()
df.plot('x', 'y', kind='scatter', ax=ax, figsize=(10,6))
for k, v in df.iterrows():
ax.annotate(k, v)
However the resulting plot does not show any annotation what so ever when applied to my problem, besides this very long horizontal scroll bar
https://imgur.com/a/O8ykmeg
III)
Further, I stumbled upon this solution, to use a marker instead of an arrow,
Matplotlib annotate with marker instead of arrow
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
x=[1,2,3,4,5,6,7,8,9,10]
y=[1,1,1,2,10,2,1,1,1,1]
line, = ax.plot(x, y)
ymax = max(y)
xpos = y.index(ymax)
xmax = x[xpos]
# Add dot and corresponding text
ax.plot(xmax, ymax, 'ro')
ax.text(xmax, ymax+2, 'local max:' + str(ymax))
ax.set_ylim(0,20)
plt.show()
however the code does absolutely nothing, when applied to my situation like so
dict = pd.DataFrame({"Return": mkw_returns, "Standard Deviation": mkw_stds})
dict.head()
plt.annotate("Sharpe Ratio", xytext=(0.5,0.5), xy=(0.03,0.03) , arrowprops=dict(facecolor='blue', shrink=0.01, width=220)) # arrowprops={width = 3, "facecolor":
dict.plot(x="Standard Deviation", y = "Return", kind="scatter", figsize=(10,6))
plt.xlabel("Standard Deviations")
plt.ylabel("log_Return YoY")
ymax = max(y)
xpos = y.index(ymax)
xmax = x[xpos]
# Add dot and corresponding text
ax.plot(xmax, ymax, 'ro')
ax.text(xmax, ymax+2, 'local max:' + str(ymax))
ax.set_ylim(0,20)
plt.show()
IV)
Lastly, I tried a solution that apparently works flawlessly with an arrow in a pd.dataframe,
https://python-graph-gallery.com/193-annotate-matplotlib-chart/
# Library
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
# Basic chart
df=pd.DataFrame({'x': range(1,101), 'y': np.random.randn(100)*15+range(1,101) })
plt.plot( 'x', 'y', data=df, linestyle='none', marker='o')
# Annotate with text + Arrow
plt.annotate(
# Label and coordinate
'This point is interesting!', xy=(25, 50), xytext=(0, 80),
# Custom arrow
arrowprops=dict(facecolor='black', shrink=0.05)
)
however running this code yields me the same error as above:
TypeError: 'DataFrame' object is not callable
Version:
import sys; print(sys.version)
3.7.1 (default, Dec 10 2018, 22:54:23) [MSC v.1915 64 bit (AMD64)]
Sorry for the WoT, but I thought its best to have everything I tried together in one post.
Any help is appreciated, thank you!
I think one solution is the following, as posted above as the "UPDATE":
UPDATE
Trying some more, I managed to run this code without error:
from matplotlib.pyplot import figure
dict = pd.DataFrame({"Return": mkw_returns, "Standard Deviation": mkw_stds})
dict.head()
#plt.annotate("Sharpe Ratio", xytext=(0.5,0.5), xy=(0.03,0.03) , arrowprops=dict(facecolor='blue', shrink=0.01, width=220)) # arrowprops={width = 3, "facecolor":
#dict.plot(x="Standard Deviation", y = "Return", kind="scatter", figsize=(10,6))
#plt.xlabel("Standard Deviations")
#plt.ylabel("log_Return YoY")
figure(num=None, figsize=(15, 10), dpi=100, facecolor='w', edgecolor='k')
plt.plot( 'Standard Deviation', 'Return', data=dict, linestyle='none', marker='o')
plt.xlabel("Standard Deviations")
plt.ylabel("log_Return YoY")
# Annotate with text + Arrow
plt.annotate(
# Label and coordinate
'This is a Test', xy=(0.01, 1), xytext=(0.01, 1), color= "r", arrowprops={"facecolor": 'black', "shrink": 0.05}
)
One question remains, how can I use a different marker or color and write about it in the legend instead?
Thanks in advance :)

matplotlib finance candlestick2_ohlc: vertical line color and width

In regards to candlestick2_ohlc's vertical line, how do I change its default color of black to something else? I've been looking at the source code, but I couldn't figure out how to change it correctly.
Also, when you are dealing with roughly 400 data points or more, the 'width' parameter needs to be rather larger. But when its large and you zoom in, the candlesticks overlap.
Anyway to work around this?
from matplotlib.finance import candlestick2_ohlc
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import matplotlib.dates as mdates
from pandas import read_csv
import numpy as np
path1 = "./ES 06-15.Last.txt" # typical stock data
t_0 = 30
t_end = 431
N_data = read_csv(path1, sep=';|,', names=['datetime1', 'open1', 'high1',
'low1', 'close1', 'volume1'],
skiprows=t_0, nrows=t_end - t_0 + 3,
converters={'open1': np.float32, 'high1': np.float32,
'low1': np.float32, 'close1': np.float32})
fig = plt.figure(facecolor='k')
ax1 = plt.subplot(1,1,1, axisbg='#191919')
ax1.yaxis.label.set_color('w')
ax1.xaxis.label.set_color("w")
ax1.spines['bottom'].set_color("#5998ff")
ax1.spines['top'].set_color("#5998ff")
ax1.spines['left'].set_color("#5998ff")
ax1.spines['right'].set_color("#5998ff")
ax1.tick_params(axis='y', colors='w')
ax1.tick_params(axis='x', colors='yellow')
plt.ylabel('Price')
x = np.arange(len(N_data))
my_xticks = N_data['datetime1']
plt.xticks(x,my_xticks)
ax1.xaxis.set_major_locator(mticker.MaxNLocator(6))
for label in ax1.xaxis.get_ticklabels():
label.set_rotation(25)
candlestick2_ohlc(ax1, N_data['open1'], N_data['high1'],
N_data['low1'], N_data['close1'], width=2,
colorup='#008000', colordown='#FF0000', alpha=1)
plt.show()
I know this is an oldish question, however here is your answer.
Open the source file located in /site-packages/matplotlib/finance.py and edit the candlestick2_ohlc function around line 1127.
There should be the following code:
rangeCollection = LineCollection(rangeSegments,
colors=((0, 0, 0, 1), ),
linewidths=lw,
antialiaseds=useAA,
)
If you change to.
rangeCollection = LineCollection(rangeSegments,
colors=colors, # << this bit
linewidths=lw,
antialiaseds=useAA,
)
This will make the wick for each candle the same colour as the main body.
Alternatively, if you wish to have the same colour for each candle wick regardless of direction, edit the def section like so.
def candlestick2_ohlc(ax, opens, highs, lows, closes, width=4,
colorup='k', colordown='r', linecolor=None,
alpha=0.75,
):
Adding linecolor=None will provide a default setting.
Then edit again.
if linecolor is None:
linecolor = colors
rangeCollection = LineCollection(rangeSegments,
colors=linecolor,
linewidths=lw,
antialiaseds=useAA,
)
Now when the function is called from the main script without the linecolor
parameter, the default will be... wick same colour as the candle body.
candlestick2_ohlc(ax1, popen, phigh, plow, pclose, width=2,
colorup='g', colordown='r', alpha=1)
or if white is desired.
candlestick2_ohlc(ax1, popen, phigh, plow, pclose, width=2,
colorup='g', colordown='r', linecolor='w', alpha=1)

Creating identical axes with matplotlib twiny

I'm trying to duplicate my y axis so that it appears on both the left and the right side of my graph (same scale on each side). I believe the correct way to do this is through the twiny method, but cannot get my head round it. Here is my current code:
import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
def bar(data_df,
colour_df=None,
method='default',
ret_obj=False):
height = len(data_df.columns)*4
width = len(data_df.index)/4
ind = np.arange(len(data_df.index))
dat = data_df[data_df.columns[0]]
bar_width = 0.85
fig, ax = plt.subplots(figsize=(width,height))
ax1 = ax.bar(ind,dat,bar_width,color='y',log=True)
ax2 = ax1.twiny()
ax.tick_params(bottom='off', top='off', left='on', right='on')
plt.xticks(np.arange(len(data_df.index)) + bar_width,
data_df.index, rotation=67,ha='right')
ylab = 'Region Length (base pairs, log10)'
figname = 'bar' + method + '.png'
if ret_obj==False:
fig.savefig(figname,bbox_inches='tight',dpi=250)
print "Output figure:", figname
plt.close()
if ret_obj==True:
return fig
Which returns the following error when passed a dataframe:
AttributeError: 'BarContainer' object has no attribute 'twiny'
Having looked into it a bit further I believe that using the host/parasite methods would also work, but I'm a bit lost how I could fit it into my current code. Advice would be gratefully appreciated!
You don't have to use twiny in this case. It suffices to draw the labels on all sides:
bars = ax.bar(ind,dat,bar_width,color='y',log=True)
ax.tick_params(axis='both', which='both', labelbottom=True, labeltop=True,
labelleft=True, labelright=True)
I get following result with dummy data:
df = pd.DataFrame({"a": np.logspace(1,10,20)})
bar(df)

Matplotlib - Multicolour axis labels [duplicate]

Is there a way in matplotlib to partially specify the color of a string?
Example:
plt.ylabel("Today is cloudy.")
How can I show "today" as red, "is" as green and "cloudy." as blue?
I only know how to do this non-interactively, and even then only with the 'PS' backend.
To do this, I would use Latex to format the text. Then I would include the 'color' package, and set your colors as you wish.
Here is an example of doing this:
import matplotlib
matplotlib.use('ps')
from matplotlib import rc
rc('text',usetex=True)
rc('text.latex', preamble='\usepackage{color}')
import matplotlib.pyplot as plt
plt.figure()
plt.ylabel(r'\textcolor{red}{Today} '+
r'\textcolor{green}{is} '+
r'\textcolor{blue}{cloudy.}')
plt.savefig('test.ps')
This results in (converted from ps to png using ImageMagick, so I could post it here):
Here's the interactive version. Edit: Fixed bug producing extra spaces in Matplotlib 3.
import matplotlib.pyplot as plt
from matplotlib import transforms
def rainbow_text(x,y,ls,lc,**kw):
"""
Take a list of strings ``ls`` and colors ``lc`` and place them next to each
other, with text ls[i] being shown in color lc[i].
This example shows how to do both vertical and horizontal text, and will
pass all keyword arguments to plt.text, so you can set the font size,
family, etc.
"""
t = plt.gca().transData
fig = plt.gcf()
plt.show()
#horizontal version
for s,c in zip(ls,lc):
text = plt.text(x,y,s+" ",color=c, transform=t, **kw)
text.draw(fig.canvas.get_renderer())
ex = text.get_window_extent()
t = transforms.offset_copy(text._transform, x=ex.width, units='dots')
#vertical version
for s,c in zip(ls,lc):
text = plt.text(x,y,s+" ",color=c, transform=t,
rotation=90,va='bottom',ha='center',**kw)
text.draw(fig.canvas.get_renderer())
ex = text.get_window_extent()
t = transforms.offset_copy(text._transform, y=ex.height, units='dots')
plt.figure()
rainbow_text(0.05,0.05,"all unicorns poop rainbows ! ! !".split(),
['red', 'orange', 'brown', 'green', 'blue', 'purple', 'black'],
size=20)
Extending Yann's answer, LaTeX coloring now also works with PDF export:
import matplotlib
from matplotlib.backends.backend_pgf import FigureCanvasPgf
matplotlib.backend_bases.register_backend('pdf', FigureCanvasPgf)
import matplotlib.pyplot as plt
pgf_with_latex = {
"text.usetex": True, # use LaTeX to write all text
"pgf.rcfonts": False, # Ignore Matplotlibrc
"pgf.preamble": [
r'\usepackage{color}' # xcolor for colours
]
}
matplotlib.rcParams.update(pgf_with_latex)
plt.figure()
plt.ylabel(r'\textcolor{red}{Today} '+
r'\textcolor{green}{is} '+
r'\textcolor{blue}{cloudy.}')
plt.savefig("test.pdf")
Note that this python script sometimes fails with Undefined control sequence errors in the first attempt. Running it again is then successful.
After trying all the methods above, I return back to my stupid but easy method, using plt.text. The only problem is that you need to adjust the spaces between each word. You may need to adjust the positions several times, but I still like this way, because it
saves you from installing tex compilers,
does not require any special backends, and
free you from configuring matplotlib rc and configure back, or it may slows down your other plots, due to usetex=True
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
label_x = -0.15
ax.text(label_x, 0.35, r"Today", color='red', rotation='vertical', transform=ax.transAxes)
ax.text(label_x, 0.5, r"is", color='green', rotation='vertical', transform=ax.transAxes)
ax.text(label_x, 0.55, r"cloudy", color='blue', rotation='vertical', transform=ax.transAxes)

Categories