Using the python seaborn package I was trying to plot the nested bar graphs with three different y-axes as shown in the below figure:
And the code that I have used is :
import matplotlib.pyplot as plt
from matplotlib import rc
import numpy as np
import seaborn as sns
#plt.style.use(['science'])
rc('font', **{'family': 'serif', 'serif': ['Computer Modern']})
rc('text', usetex=True)
HV = [388, 438]
YS = [1070, 1200]
UTS = [1150, 1400]
Z = [15, 12.5]
x = [1, 2]
fig, ax1 = plt.subplots(figsize=(5, 5.5))
colors=sns.color_palette("rocket",4)
ax1 = sns.barplot(x[0],YS[0],color="blue")
ax1 = sns.barplot(x[0],color="blue")
ax1 = sns.barplot(x[1],YS[1],color="blue")
ax1 = sns.barplot(x[1],UTS[1],color="blue")
ax2 = ax1.twinx()
ax2 = sns.barplot(x[0], HV[0],color="green")
ax2 = sns.barplot(x[1], HV[1],color="green")
ax3 = ax1.twinx()
ax3 = sns.barplot(x[0],Z[0],color="red")
ax3 = sns.barplot(x[1],Z[1],color="red")
#ax3.spines['right'].set_position(('outward',60))
ax3.spines['right'].set_position(('axes',1.15))
ax1.set_ylabel("First",color="blue")
ax2.set_ylabel("Second",color="green")
ax3.set_ylabel("Third",color="red")
ax1.tick_params(axis='y',colors="blue")
ax2.tick_params(axis='y',colors="green")
ax3.tick_params(axis='y',colors="red")
ax2.spines['right'].set_color("green")
ax3.spines['right'].set_color("red")
ax3.spines['left'].set_color("blue")
plt.show()
And I'm getting the following error:
Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/seaborn/utils.py", line 531, in categorical_order
order = values.cat.categories
AttributeError: 'int' object has no attribute 'cat'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/seaborn/utils.py", line 534, in categorical_order
order = values.unique()
AttributeError: 'int' object has no attribute 'unique'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/sspenkulinti/these/thesis_E185_fatigue/test_matrix/E185_properties_AB_HT.py", line 21, in <module>
ax1 = sns.barplot(x[0],YS[0],color="blue")
File "/usr/lib/python3/dist-packages/seaborn/categorical.py", line 3147, in barplot
plotter = _BarPlotter(x, y, hue, data, order, hue_order,
File "/usr/lib/python3/dist-packages/seaborn/categorical.py", line 1614, in __init__
self.establish_variables(x, y, hue, data, orient,
File "/usr/lib/python3/dist-packages/seaborn/categorical.py", line 200, in establish_variables
group_names = categorical_order(groups, order)
File "/usr/lib/python3/dist-packages/seaborn/utils.py", line 536, in categorical_order
order = pd.unique(values)
File "/usr/lib/python3/dist-packages/pandas/core/algorithms.py", line 395, in unique
values = _ensure_arraylike(values)
File "/usr/lib/python3/dist-packages/pandas/core/algorithms.py", line 204, in _ensure_arraylike
inferred = lib.infer_dtype(values, skipna=False)
File "pandas/_libs/lib.pyx", line 1251, in pandas._libs.lib.infer_dtype
TypeError: 'int' object is not iterable
The error is because you can't call sns.barplot with a single number as first parameter. The x-values need to be a list.
To get want you want using seaborn, the data needs to be presented as if it comes from a dataframe. hue_order is needed to preserve enough space for each of the bars, even when nothing is plotted there.
import matplotlib.pyplot as plt
from matplotlib import rc
import numpy as np
import seaborn as sns
HV = [388, 438]
YS = [1070, 1200]
UTS = [1150, 1400]
Z = [15, 12.5]
x = ["As built", "200ºC-850ºC"]
names = ['YS', 'UTS', 'HV', 'Z']
fig, ax1 = plt.subplots(figsize=(9, 5.5))
colors = sns.color_palette("tab10", len(names))
sns.barplot(x=x + x, y=YS + UTS, hue=[names[0]] * len(x) + [names[1]] * len(x),
hue_order=names, palette=colors, alpha=0.7, ax=ax1)
# ax1 will already contain the full legend, the third handle needs to
# be updated to show the hatching
ax1.legend_.legendHandles[2].set_hatch('///')
ax2 = ax1.twinx()
sns.barplot(x=x, y=HV, hue=[names[2]] * len(x), hue_order=names, palette=colors, hatch='//', alpha=0.7, ax=ax2)
ax2.legend_.remove() # seaborn automatically creates a new legend
ax3 = ax1.twinx()
sns.barplot(x=x, y=Z, hue=[names[3]] * len(x), hue_order=names, palette=colors, alpha=0.7, ax=ax3)
ax3.legend_.remove()
ax3.spines['right'].set_position(('axes', 1.15))
ax1.set_ylabel("First", color=colors[0])
ax2.set_ylabel("Second", color=colors[2])
ax3.set_ylabel("Third", color=colors[3])
ax1.tick_params(axis='y', colors=colors[0])
ax2.tick_params(axis='y', colors=colors[2])
ax3.tick_params(axis='y', colors=colors[3])
ax2.spines['right'].set_color(colors[2])
ax3.spines['right'].set_color(colors[3])
plt.tight_layout()
plt.show()
Related
I am making a function in python which allows me to create two parallel graphs and they share their 2 axes:
def PlotManager(data1,data2,fig):
f, (ax1, ax2) = fig.subplots(2, 1, sharey=True,sharex=True)
#Plot1 sopra
x_axis = data1.index
#Plot and shade the area between the upperband and the lower band grey
ax1.fill_between(x_axis,data1['Upper'],data1['Lower'], color = 'grey', alpha= 0.5)
#Plot the closing price and the moving average
ax1.plot(x_axis,data1['Close'],color = 'gold',lw = 3,label = 'Close Price', alpha= 0.5)
ax1.plot(x_axis,data1['SMA'],color = 'blue',lw = 3,label = 'Simple Moving Average', alpha= 0.5)
ax1.scatter(x_axis,data1['Buy'],color="green", lw=3,label="Buy",marker = "^", alpha=1)
ax1.scatter(x_axis,data1['Sell'],color="red", lw=3,label="Sell",marker = "v", alpha = 1)
#Set the title and show the image
ax1.set_title("Bollinger Band for Amazon")
plt.xticks(rotation = 45)
#Plot 2 Sotto
ax2.set_title('RSI_Plot')
ax2.plot(x_axis,data2['RSI'])
ax2.axhline(0,linestyle='--',alpha=0.5, color="grey")
ax2.axhline(10,linestyle='--',alpha=0.5, color="orange")
ax2.axhline(20,linestyle='--',alpha=0.5, color="green")
ax2.axhline(30,linestyle='--',alpha=0.5, color="red")
ax2.axhline(70,linestyle='--',alpha=0.5, color="red")
ax2.axhline(80,linestyle='--',alpha=0.5, color="green")
ax2.axhline(90,linestyle='--',alpha=0.5, color="orange")
ax2.axhline(100,linestyle='--',alpha=0.5, color="grey")
But gives me the cannot unpack non-iterable AxesSubplot object error:
[Command: python -u C:\Users\Nicolò\Documents\Git\ProgettoTradingBot\ProgettoTradeBot\GUIprova2.py]
C:\Users\Nicolò\Documents\Git\ProgettoTradingBot\ProgettoTradeBot\BollingerBandsFinal.py:63: MatplotlibDeprecationWarning: Adding an axes using the same arguments as a previous axes currently reuses the earlier instance. In a future version, a new instance will always be created and returned. Meanwhile, this warning can be suppressed, and the future behavior ensured, by passing a unique label to each axes instance.
ax = f.add_subplot(111)
C:\Users\Nicolò\Documents\Git\ProgettoTradingBot\ProgettoTradeBot\BollingerBandsFinal.py:63: MatplotlibDeprecationWarning: Adding an axes using the same arguments as a previous axes currently reuses the earlier instance. In a future version, a new instance will always be created and returned. Meanwhile, this warning can be suppressed, and the future behavior ensured, by passing a unique label to each axes instance.
ax = f.add_subplot(111)
Traceback (most recent call last):
File "C:\Users\Nicolò\AppData\Local\Programs\Python\Python38\lib\site-packages\matplotlib\cbook\__init__.py", line 196, in process
func(*args, **kwargs)
File "C:\Users\Nicolò\AppData\Local\Programs\Python\Python38\lib\site-packages\matplotlib\animation.py", line 951, in _start
self._init_draw()
File "C:\Users\Nicolò\AppData\Local\Programs\Python\Python38\lib\site-packages\matplotlib\animation.py", line 1743, in _init_draw
self._draw_frame(next(self.new_frame_seq()))
File "C:\Users\Nicolò\AppData\Local\Programs\Python\Python38\lib\site-packages\matplotlib\animation.py", line 1766, in _draw_frame
self._drawn_artists = self._func(framedata, *self._args)
File "C:\Users\Nicolò\Documents\Git\ProgettoTradingBot\ProgettoTradeBot\GUIprova2.py", line 48, in animate
PlotManager(BollingerBands(df,f),RSI(df,f2),f)
File "C:\Users\Nicolò\Documents\Git\ProgettoTradingBot\ProgettoTradeBot\mostraGrafici.py", line 7, in PlotManager
f, (ax1, ax2) = fig.subplots(2, 1, sharey=True,sharex=True)
TypeError: cannot unpack non-iterable AxesSubplot object
How can i handle to this error?
The value of plt.subplots(2, 1, ...) is a tuple figure, array(subplot0, subplot1) so that you can unpack correctly to a figure and two subplots.
On the contrary the value of fig.subplots(2, 1, ...) is subplot0, subplot1 (because you ALREADY have the figure…) and when you try to unpack it's equivalent to
f = subplot0
ax0, ax1 = subplot1
and this leads to TypeError: cannot unpack non-iterable AxesSubplot object
Because you are not using the object labeled as f in the following, you should write
ax1, ax2 = fig.subplots(2, 1, sharey=True,sharex=True)
I want to draw a seaborn.heatmap and annotate only some rows/columns.
Example where all cells have annotation:
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
n1 = 5
n2 = 10
M = np.random.random((n1, n2))
fig, ax = plt.subplots()
sns.heatmap(ax = ax, data = M, annot = True)
plt.show()
Following these examples (paragraph Adding Value Annotations), it is possible to pass to seaborn.heatmap an array with annotations for each cell as annot parameter:
annot : bool or rectangular dataset, optional
If True, write the data value in each cell. If an array-like with the same shape as data,
then use this to annotate the heatmap instead of the data.
Note that DataFrames will match on position, not index.
If I try to generate an array of str and pass it as annot parameter to seaborn.heatmap I get the following error:
Traceback (most recent call last):
File "C:/.../myfile.py", line 16, in <module>
sns.heatmap(ax = ax, data = M, annot = A)
File "C:\venv\lib\site-packages\seaborn\_decorators.py", line 46, in inner_f
return f(**kwargs)
File "C:\venv\lib\site-packages\seaborn\matrix.py", line 558, in heatmap
plotter.plot(ax, cbar_ax, kwargs)
File "C:\venv\lib\site-packages\seaborn\matrix.py", line 353, in plot
self._annotate_heatmap(ax, mesh)
File "C:\venv\lib\site-packages\seaborn\matrix.py", line 262, in _annotate_heatmap
annotation = ("{:" + self.fmt + "}").format(val)
ValueError: Unknown format code 'g' for object of type 'numpy.str_'
Code which generates the ValueError (in this case I try to remove annotations of the 4th columns as an example):
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
n1 = 5
n2 = 10
M = np.random.random((n1, n2))
A = np.array([[f'{M[i, j]:.2f}' for j in range(n2)] for i in range(n1)])
A[:, 3] = ''
fig, ax = plt.subplots(figsize = (6, 3))
sns.heatmap(ax = ax, data = M, annot = A)
plt.show()
What is the cause of this error?
How can I generate a seaborn.heatmap and annotate only selected rows/columns?
It is a formatting issue. Here the fmt = '' is required if you are using non-numeric labels (defaults to: fmt='.2g') which consider only for numeric values and throw an error for labels with text format.
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
n1 = 5
n2 = 10
M = np.random.random((n1, n2))
A = np.array([[f'{M[i, j]:.2f}' for j in range(n2)] for i in range(n1)])
A[:, 3] = ''
fig, ax = plt.subplots(figsize = (6, 3))
sns.heatmap(ax = ax, data = M, annot = A, fmt='')
plt.show()
When attempting to plot wind barbs using matplotlib on a cartopy map, I get a QhullError. I've never seen this error before and my code hasn't changed since I last used it. I've also made sure the packages are up to date and the grib2 file being used is valid by printing the xarray variables. Below is the code:
file = xr.open_dataset('/Users/victoralvarez/prog2/grib/&var_UGRD=on&var_VGRD=on.grb',
engine='cfgrib')
# Mask the barbs where winds < 50.
masknum = int(input('BARB THRESHOLD: '))
# Extract the lon/lat.
x = file.variables['longitude'].values
y = file.variables['latitude'].values
# Extract the desired data.
u_wind = file.variables['u'].values * units('m/s')
v_wind = file.variables['v'].values * units('m/s')
# Calculate the wind speed.
wndspd = mpcalc.wind_speed(u_wind, v_wind).to('kt')
wnds_f = wndspd.astype(float)
mask = np.ma.masked_less_equal(wnds_f, masknum).mask
u_wind[mask] = np.nan
v_wind[mask] = np.nan
fig = plt.figure(1, figsize=(15,15))
ax = plt.axes(projection=ccrs.LambertConformal(central_longitude=-100,
central_latitude=35,
standard_parallels=(30, 60)))
ax.set_extent([-121, -75, 25, 50], ccrs.PlateCarree())
ax.add_feature(cfeature.OCEAN.with_scale('50m'), facecolor='#626262',
edgecolor='black',
zorder=0,
linewidth=.5)
ax.add_feature(cfeature.LAND.with_scale('50m'), edgecolor='black',
facecolor='#626262',
zorder=1)
ax.add_feature(cfeature.STATES.with_scale('50m'), linewidth=.5,
edgecolor='black',
zorder=5)
b1 = ax.barbs(x, y, u_wind.to('kt').m, v_wind.to('kt').m,
color='black', length=4.5, regrid_shape=20, pivot='middle',
linewidth=1.5, zorder=103, transform=ccrs.PlateCarree())
b2 = ax.barbs(x, y, u_wind.to('kt').m, v_wind.to('kt').m,
color='white', length=4.5, regrid_shape=10, pivot='middle',
linewidth=0.5, zorder=104, transform=ccrs.PlateCarree())
plt.savefig('img.png', dpi=300, bbox_inches='tight')
When running the script through terminal, the below errors show:
Traceback (most recent call last):
File "winds_barb.py", line 63, in <module>
linewidth=0.5, zorder=104, transform=ccrs.PlateCarree())
File "/anaconda3/lib/python3.7/site-packages/cartopy/mpl/geoaxes.py", line 1826, in barbs
target_extent=target_extent)
File "/anaconda3/lib/python3.7/site-packages/cartopy/vector_transform.py", line 146, in vector_scalar_to_grid
return _interpolate_to_grid(nx, ny, x, y, u, v, *scalars, **kwargs)
File "/anaconda3/lib/python3.7/site-packages/cartopy/vector_transform.py", line 68, in _interpolate_to_grid
method='linear'),)
File "/anaconda3/lib/python3.7/site-packages/scipy/interpolate/ndgriddata.py", line 222, in griddata
rescale=rescale)
File "interpnd.pyx", line 248, in scipy.interpolate.interpnd.LinearNDInterpolator.__init__
File "qhull.pyx", line 1828, in scipy.spatial.qhull.Delaunay.__init__
File "qhull.pyx", line 354, in scipy.spatial.qhull._Qhull.__init__
scipy.spatial.qhull.QhullError: QH6019 qhull input error: can not scale last coordinate. Input is cocircular
or cospherical. Use option 'Qz' to add a point at infinity.
While executing: | qhull d Qz Qt Qbb Q12 Qc
Options selected for Qhull 2015.2.r 2016/01/18:
run-id 1133843321 delaunay Qz-infinity-point Qtriangulate Qbbound-last
Q12-no-wide-dup Qcoplanar-keep _pre-merge _zero-centrum Qinterior-keep
Pgood
This error is most likely caused by your input latitude coordinate containing the pole. The following code will reproduce the error you are seeing:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import numpy as np
lons = np.array([-110, -100, -90])
lats = np.array([-90, 30, 40, 50])
u = np.ones([len(lats), len(lons)]) * 10
v = np.ones_like(u) * 10
p = ccrs.LambertConformal(
central_longitude=-100,
central_latitude=35,
standard_parallels=(30, 60),
)
ax = plt.axes(projection=p)
ax.coastlines()
ax.set_extent([-121, -75, 25, 50], crs=ccrs.PlateCarree())
ax.barbs(lons, lats, u, v, regrid_shape=3, transform=ccrs.PlateCarree())
plt.show()
But when the pole is removed there is no error:
lats = np.array([30, 40, 50])
Since your example is not runnable I cannot suggest the exact fix for your case. However, you can likely exclude such points from your input data prior to plotting to avoid this problem and still use your desired projection.
It appears the solution (sorta) to this was to simply use a different projection than the LambertConformal projection I was using originally. Not exactly sure what was wrong so this is only a circumvention to the original problem.
After defining ax1=fig1.add_subplot(111) and plotting 8 data series with associated label values, I used the following line of code to add a legend.
ax1.legend(loc='center left', bbox_to_anchor=(1.0, 0.5))
I have used this method many times before without a problem, but on this occasion it produces an error saying IndexError: tuple index out of range
Traceback (most recent call last):
File "interface_tension_adhesion_plotter.py", line 45, in <module>
ax1.legend(loc='center left', bbox_to_anchor=(1.0, 0.5))
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/matplotlib/axes/_axes.py", line 564, in legend
self.legend_ = mlegend.Legend(self, handles, labels, **kwargs)
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/matplotlib/legend.py", line 386, in __init__
self._init_legend_box(handles, labels, markerfirst)
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/matplotlib/legend.py", line 655, in _init_legend_box
fontsize, handlebox))
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/matplotlib/legend_handler.py", line 119, in legend_artist
fontsize, handlebox.get_transform())
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/matplotlib/legend_handler.py", line 476, in create_artists
self.update_prop(coll, barlinecols[0], legend)
IndexError: tuple index out of range
I have no idea why this is happening and would really appreciate suggestions.
1. If data is intact and arrays are not empty, this code works perfectly.
fig = plt.gcf()
ax=fig.add_subplot(111)
for i in range(8):
x = np.arange(10)
y = i + random.rand(10)
yerr = .1*y
l = .1*i
ax.errorbar(x,y,yerr=yerr,label="adhsion={:02.1f}".format(l))
ax.legend(loc='center left', bbox_to_anchor=(1.0, 0.5))
2. I had same error when I applied a filter to my data and got empty arrays. This could be reproduced as follows:
fig = plt.gcf()
ax=fig.add_subplot(111)
for i in range(8):
x = np.arange(10)
y = i + random.rand(10)
yerr = .1*y
l = .1*i
if i == 7:
ind = np.isnan(y)
y = y[ind]
x = x[ind]
yerr = yerr[ind]
ax.errorbar(x,y,yerr=yerr,label="adhsion={:02.1f}".format(l))
ax.legend(loc='center left', bbox_to_anchor=(1.0, 0.5))
This code gives identical Traceback as in the question. Empty array for errors results in wrong handles for errorbars.
Workaround mentioned by #crevell:
handles, labels = ax.get_legend_handles_labels()
handles = [h[0] for h in handles]
ax.legend(handles, labels,loc='center left', bbox_to_anchor=(1.0, 0.5))
It works, but legend appears without errorbar lines.
So one should check the data supplied to the matplotlib errorbar function.
So I using this code to create a donut chart with python (inspired in this Donut plot recipe):
def make_pie(sizes, text,colors,labels):
import matplotlib.pyplot as plt
import numpy as np
col = [[i/255. for i in c] for c in colors]
fig, ax = plt.subplots()
ax.axis('equal')
width = 0.35
kwargs = dict(colors=col, startangle=180)
outside, _ = ax.pie(sizes, radius=1, pctdistance=1-width/2,labels=labels,**kwargs)
plt.setp( outside, width=width, edgecolor='white')
kwargs = dict(size=20, fontweight='bold', va='center')
ax.text(0, 0, text, ha='center', **kwargs)
plt.show()
c1 = (226,33,7)
c2 = (60,121,189)
make_pie([257,90], "Gender (AR)",[c1,c2],['M','F'])
which results in:
My problem is that now I want the respective percentages. For that I was simply adding the argument:
autopct='%1.1f%%'
like this:
kwargs = dict(colors=col, startangle=180,autopct='%1.1f%%')
but this results in the following error:
Traceback (most recent call last):
File "draw.py", line 30, in <module>
make_pie([257,90], "Gender (AR)",[c1,c2],['M','F'])
File "draw.py", line 13, in make_pie
outside, _ = ax.pie(sizes, radius=1, pctdistance=1-width/2,labels=labels,**kwargs)
ValueError: too many values to unpack
So, what am I doing wrong?
From the docstring:
If *autopct* is not *None*, return the tuple (*patches*,
*texts*, *autotexts*), where *patches* and *texts* are as
above, and *autotexts* is a list of
:class:`~matplotlib.text.Text` instances for the numeric
labels.
So if you want to unpack the result of pie() using autopct you need 3 values:
kwargs = dict(colors=col, startangle=180, autopct='%1.1f%%')
outside, _, _ = ax.pie(sizes, radius=1, pctdistance=1-width/2,
labels=labels,**kwargs)
Or maybe it will be more robust without unpacking so it works with or without autopct:
outside = ax.pie(sizes, radius=1, pctdistance=1-width/2,
labels=labels,**kwargs)[0]