Pyplot errorbar cannot be passed an array of colours - python

I am trying to change the appearance of a diagram created using plt.errorbar, but I can't get it to change in the ways I would like.
To demonstrate the problem, I have made up some example data:
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import matplotlib.axes as axes
import numpy as np
Temps=np.array([18000,15000,14000,12000,11750,11500,10000,5750,6000])
Powers=np.array([1.2,1.0,0.5,.35,0.4,0.2,.15,5.3,4.9])
Errors=100*np.array([2,2,2,2,2,2,2,3,3])
I have a function that turns the temperature values into colours:
def makecolour(t):
a=(t-min(Temps))/(max(Temps)-min(Temps))
return [[1-A,0,A] for A in a]
I have also changed some of the other properties of the diagram.
plt.axes(facecolor='black')
plt.yscale('log')
plt.xscale('log')
plt.xlim(2e4,5e3)
plt.errorbar(Temps,Powers,xerr=Errors,ecolor=makecolour(Temps),fmt='.')
I can't get the data points to change colour, only the error bars. When I try to change the colour of the actual points:
plt.errorbar(Temps,Powers,xerr=Errors,ecolor=makecolour(Temps),fmt='.',color=makecolour(Temps))
"Breaks because it fails to interpret the array of colours."
It doesn't work and I'm don't know how to fix it. The closest I have come to a solution is hiding the data points entirely:
plt.errorbar(Temps,Powers,xerr=Errors,ecolor=makecolour(Temps),fmt='.',markersize=0)
"Not showing where the data point is isn't acceptable."`
But this not good enough.
I have also been struggling with the way the axis ticks are displayed when using plt.xscale('log'). Ideally, I want to display the tick labels as a plain integer as opposed to scientific notation, but neither of the solutions I have tried worked. I have tried:
ticker.LogFormatter(base=1)
axes.ticklabel_format(style='plain')
I have searched around on here for previous answers, but I have not found any disussions of similar problems with plt.errorbar. Any help would be much appreciated.

Here is a partial answer. Just first plot without markers and on the same plot without the errorlines.
About the tickers, this post proposes:
ax=plt.gca()
ax.xaxis.set_minor_formatter(ticker.ScalarFormatter())
ax.xaxis.set_major_formatter(ticker.ScalarFormatter())
Demo code:
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import matplotlib.axes as axes
import numpy as np
Temps=np.array([18000,15000,14000,12000,11750,11500,10000,5750,6000])
Powers=np.array([1.2,1.0,0.5,.35,0.4,0.2,.15,5.3,4.9])
Errors=100*np.array([2,2,2,2,2,2,2,3,3])
def makecolour(t):
a=(t-min(Temps))/(max(Temps)-min(Temps))
return [[1-A,0,A] for A in a]
plt.axes(facecolor='black')
plt.yscale('log')
plt.xscale('log')
plt.xlim(2e4,5e3)
ax=plt.gca()
ax.xaxis.set_minor_formatter(ticker.ScalarFormatter())
ax.xaxis.set_major_formatter(ticker.ScalarFormatter())
plt.errorbar(Temps,Powers,xerr=Errors,ecolor=makecolour(Temps),fmt='.',markersize=0)
plt.errorbar(Temps,Powers,xerr=None,fmt='.')
plt.show()

Related

How can I rotate axis tickmark labels if I set axis properties before making my plot?

I'm experimenting with seaborn and have a question about specifying axes properties. In my code below, I've taken two approaches to creating a heatmap of a matrix and placing the results on two sets of axes in a figure.
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
A=np.random.randn(4,4)
labels=['a','b','c','d']
fig, ax = plt.subplots(2)
sns.heatmap(ax =ax[0], data = A)
ax[0].set_xticks(range(len(labels)))
ax[0].set_xticklabels(labels,fontsize=10,rotation=45)
ax[0].set_yticks(range(len(labels)))
ax[0].set_yticklabels(labels,fontsize=10,rotation=45)
ax[1].set_xticks(range(len(labels)))
ax[1].set_xticklabels(labels,fontsize=10,rotation=45)
ax[1].set_yticks(range(len(labels)))
ax[1].set_yticklabels(labels,fontsize=10,rotation=45)
sns.heatmap(ax =ax[1], data = A,xticklabels=labels, yticklabels=labels)
plt.show()
The resulting figure looks like this:
Normally, I would always take the first approach of creating the heatmap and then specifying axis properties. However, when creating an animation (to be embedded on a tkinter canvas), which is what I'm ultimately interested in doing, I found such an ordering in my update function leads to "flickering" of axis labels. The second approach will eliminate this effect, and it also centers the tickmarks within squares along the axes.
However, the second approach does not rotate the y-axis tickmark labels as desired. Is there a simple fix to this?
I'm not sure this is what you're looking for. It looks like you create your figure after you change the yticklabels. so the figure is overwriting your yticklabels.
Below would fix your issue.
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
A=np.random.randn(4,4)
labels=['a','b','c','d']
fig, ax = plt.subplots(2)
sns.heatmap(ax =ax[0], data = A)
ax[0].set_xticks(range(len(labels)))
ax[0].set_xticklabels(labels,fontsize=10,rotation=45)
ax[0].set_yticks(range(len(labels)))
ax[0].set_yticklabels(labels,fontsize=10,rotation=45)
ax[1].set_xticks(range(len(labels)))
ax[1].set_xticklabels(labels,fontsize=10,rotation=45)
ax[1].set_yticks(range(len(labels)))
sns.heatmap(ax =ax[1], data = A,xticklabels=labels, yticklabels=labels)
ax[1].set_yticklabels(labels,fontsize=10,rotation=45)
plt.show()

Displaying minor grid lines for wide x axis ranges (log)

I noticed a 'strange' behaviour when running the following code:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import (MultipleLocator, AutoMinorLocator)
freqs = np.logspace(2,4)
freqs_ext = np.logspace(2, 10)
fig, ax = plt.subplots(1,2)
ax [0].plot(freqs , freqs**2)
#ax[0].xaxis.set_minor_locator(AutoMinorLocator(5))
ax[0].grid(which='both')
#ax[0].minorticks_on()
ax[0].set_xscale( 'log')
ax[1].plot(freqs_ext,freqs_ext**2)
#ax[l].xaxis.set_minor_locator(AutoMinorLocator(5))
ax[1].grid(which='both')
#ax[1].minorticks on()
ax[1].set_xscale('log')
The output is the following:
I have tried more variants than I care to report, (some are commented out in the code above), but I cannot get matplotlib to draw minor gridlines for the plot on the right side, as it does for the one on the left.
I think I have understood that the "problem" lies in where the ticks are located for the second plot, which has a much larger span. They are every two decades and I believe this might be the source of the minor grid lines not displaying.
I have played with xaxis.set_xticks and obtained ticks every decade, but still cannot get this to correctly produce the gridlines.
It is probably something stupid but I can't see it.
NOTE : I know that matplotlib doesn't turn the minor ticks on by default, and in this case this action is "triggered" by changing the scale to log (that's why axis.grid(which='both') actually only acts on the x axis)
OK, I have found this answer:
Matplotlib: strange double-decade axis ticks in log plot
which actually shows how the issue is a design choice for matplotlib starting with v2. Answer was given in 2017 so, not the newest issue :)
The following code correctly plots the minor grids as wanted:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import LogLocator
freqs = np.logspace(2,4)
freqs_ext = np.logspace(2, 10)
fig, ax = plt.subplots(1,2)
ax[0].plot(freqs , freqs**2)
ax[0].grid(which='both')
ax[0].set_xscale( 'log')
ax[1].plot(freqs_ext,freqs_ext**2)
ax[1].set_xscale('log')
ax[1].xaxis.set_major_locator(LogLocator(numticks=15))
ax[1].xaxis.set_minor_locator(LogLocator(numticks=15,subs=np.arange(2,10)))
ax[1].grid(which='both')

Cartopy plotting unwanted lines on map

I'm very (very very) new at this game of mapping/carto in Python, and am finding a drought of resources available for CartoPy, so I thought someone here may be able to help.
I have thousands of lat/long coordinates to plot, distributed across 4 parallel lines.
My code so far is:
import pandas as pd
import numpy as np
import os
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy import config
import cartopy.feature as cf
latcol = [2]
loncol = [3]
df_lat = pd.read_csv(r'G:\Documents\Thesis\Outputs\concat_Xenos_combineds.csv', usecols = latcol)
df_lon = pd.read_csv(r'G:\Documents\Thesis\Outputs\concat_Xenos_combineds.csv', usecols = loncol)
map = plt.figure(figsize=(15,15))
ax = plt.axes(projection=ccrs.EuroPP())
ax.coastlines(resolution='10m')
ax.add_feature(cf.LAND)
ax.add_feature(cf.OCEAN)
ax.add_feature(cf.COASTLINE)
ax.add_feature(cf.BORDERS, linestyle=':')
ax.add_feature(cf.LAKES, alpha=0.5)
ax.add_feature(cf.RIVERS)
ax.plot(df_lon, df_lat, markersize=2, marker='o', color='red', transform=ccrs.PlateCarree())
ax.stock_img()
As I said, the coordinates are distributed accross 4 parallel lines. but the plot produced is messy as anything with lines going all over the place:
How do I correct this pls?
Also, I have a third column with a numerical value representing the value_count of a specific feature type at each coordinate point, and would eventually like to incorperate this into the map some way (whether as different sized or coloured markers or different colours). whats the best way to achieve this? Is it worth trying to do a heatmap-style plot instead/
Any clarification needed don't hesitate to ask!
Thanks,
R
To get rid of red lines between the markers, pass linestyle='none' to plot().
Regarding your second question, you should probably use ax.scatter() and use
the value_count variable for color.

How to update 3D arrow animation in matplotlib

I am trying to reproduce the left plot of this animation in python using matplotlib.
I am able to generate the vector arrows using the 3D quiver function, but as I read here, it does not seem possible to set the lengths of the arrows. So, my plot does not look quite right:
So, the question is: how do I generate a number of 3D arrows with different lengths? Importantly, can I generate them in such a way so that I can easily modify for each frame of the animation?
Here's my code so far, with the not-so-promising 3D quiver approach:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d
ax1 = plt.subplot(111,projection='3d')
t = np.linspace(0,10,40)
y = np.sin(t)
z = np.sin(t)
line, = ax1.plot(t,y,z,color='r',lw=2)
ax1.quiver(t,y,z, t*0,y,z)
plt.show()
As Azad suggests, an inelegant, but effective, solution is to simply edit the mpl_toolkits/mplot3d/axes3d.py to remove the normalization. Since I didn't want to mess with my actual matplotlib installation, I simply copied the axes3d.py file to the same directory as my other script and modified the line
norm = math.sqrt(u ** 2 + v ** 2 + w ** 2)
to
norm = 1
(Be sure to change the correct line. There is another use of "norm" a few lines higher.) Also, to get axes3d.py to function correctly when it's outside of the mpl directory, I changed
from . import art3d
from . import proj3d
from . import axis3d
to
from mpl_toolkits.mplot3d import art3d
from mpl_toolkits.mplot3d import proj3d
from mpl_toolkits.mplot3d import axis3d
And here is the nice animation that I was able to generate (not sure what's going wrong with the colors, it looks fine before I uploaded to SO).
And the code to generate the animation:
import numpy as np
import matplotlib.pyplot as plt
import axes3d_hacked
ax1 = plt.subplot(111,projection='3d')
plt.ion()
plt.show()
t = np.linspace(0,10,40)
for index,delay in enumerate(np.linspace(0,1,20)):
y = np.sin(t+delay)
z = np.sin(t+delay)
if delay > 0:
line.remove()
ax1.collections.remove(linecol)
line, = ax1.plot(t,y,z,color='r',lw=2)
linecol = ax1.quiver(t,y,z, t*0,y,z)
plt.savefig('images/Frame%03i.gif'%index)
plt.draw()
plt.ioff()
plt.show()
Now, if I could only get those arrows to look prettier, with nice filled heads. But that's a separate question...
EDIT: In the future, matplotlib will not automatically normalize the arrow lengths in the 3D quiver per this pull request.
Another solution is to call ax.quiever on each arrow, individually - with each call having an own length attribute. This is not very efficient but it will get you going.
And there's no need to change MPL-code

Save figure with clip box from another figure

Normally if you plot two different figures using the default settings in pyplot, they will be exactly the same size, and if saved can be neatly aligned in PowerPoint or the like. I'd like to generate one figure, however, which has a legend outside of the figure. The script I'm using is shown below.
import numpy as np
import matplotlib.pyplot as plt
x=np.linspace(0,1,201)
y1=x**2
y2=np.sin(x)
fig1=plt.figure(1)
plt.plot(x,y1,label='y1')
handles1,labels1=plt.gca().get_legend_handles_labels()
lgd1=plt.gca().legend(handles1,labels1,bbox_to_anchor=(1.27,1),borderaxespad=0.)
fig2=plt.figure(2)
plt.plot(x,y2)
fig1.savefig('fig1',bbox_extra_artists=(lgd1,),bbox_inches='tight')
fig2.savefig('fig2')
plt.show()
The problem is that in PowerPoint, I can no longer align the two figures left and have their axes aligned. Due to the use of the 'extra artists' and 'bbox_inches=tight' arguments for the first figure, the width of its margins becomes different from the second figure.
Is there any way to 'transfer' the clip box from the first figure to the second figure, such that they can be aligned by 'align left' in PowerPoint?
I think an easier way to achieve what you want is to just construct one figure with two subplots, and let matplotlib align everything for you.
Do you think doing something like this is a good idea?
import matplotlib.pyplot as plt
import numpy as np
x=np.linspace(0,1,201)
y1=x**2
y2=np.sin(x)
fig = plt.figure()
a = fig.add_subplot(211)
a.plot(x,y1, label='y1')
lgd1 = a.legend(bbox_to_anchor = (1.27,1), borderaxespad=0.)
a = fig.add_subplot(212)
a.plot(x,y2)
fig.savefig('fig',bbox_extra_artists=(lgd1,),bbox_inches='tight')

Categories