I am trying to create a plot using matplotlib but I get an error, and after hours of searching, I do not see an alternative or something that works. Here's the code that's giving me trouble:
import matplotlib.transforms as transforms
self.transDataToAxes = self.transScale + (self.transLimits +
transforms.Affine2D().skew_deg(rot, 0))
Which gives me the error: AttributeError: 'Affine2D' object has no attribute 'skew_deg'. This error happens with both python 2.7 and python 3.
If anyone has any suggestions on what I can try, it would be greatly appreciated.
Edit: Here is the entire script which I am trying to run, it should also be noted that I've tried this on Windows, Linux, and Mac with no success:
import matplotlib
spc_file = open('1OUN.txt', 'r').read()
import sharppy
import sharppy.sharptab.profile as profile
import sharppy.sharptab.interp as interp
import sharppy.sharptab.winds as winds
import sharppy.sharptab.utils as utils
import sharppy.sharptab.params as params
import sharppy.sharptab.thermo as thermo
import numpy as np
from StringIO import StringIO
def parseSPC(spc_file):
## read in the file
data = np.array([l.strip() for l in spc_file.split('\n')])
## necessary index points
title_idx = np.where( data == '%TITLE%')[0][0]
start_idx = np.where( data == '%RAW%' )[0] + 1
finish_idx = np.where( data == '%END%')[0]
## create the plot title
data_header = data[title_idx + 1].split()
location = data_header[0]
time = data_header[1][:11]
## put it all together for StringIO
full_data = '\n'.join(data[start_idx : finish_idx][:])
sound_data = StringIO( full_data )
## read the data into arrays
p, h, T, Td, wdir, wspd = np.genfromtxt( sound_data, delimiter=',', comments="%", unpack=True )
return p, h, T, Td, wdir, wspd
pres, hght, tmpc, dwpc, wdir, wspd = parseSPC(spc_file)
prof = profile.create_profile(profile='default', pres=pres, hght=hght, tmpc=tmpc, \
dwpc=dwpc, wspd=wspd, wdir=wdir, missing=-9999, strictQC=True)
import matplotlib.pyplot as plt
plt.plot(prof.tmpc, prof.hght, 'r-')
plt.plot(prof.dwpc, prof.hght, 'g-')
#plt.barbs(40*np.ones(len(prof.hght)), prof.hght, prof.u, prof.v)
plt.xlabel("Temperature [C]")
plt.ylabel("Height [m above MSL]")
plt.grid()
plt.show()
msl_hght = prof.hght[prof.sfc] # Grab the surface height value
agl_hght = interp.to_agl(prof, msl_hght) # Converts to AGL
msl_hght = interp.to_msl(prof, agl_hght) # Converts to MSL
plt.plot(thermo.ktoc(prof.thetae), prof.hght, 'r-', label='Theta-E')
plt.plot(prof.wetbulb, prof.hght, 'c-', label='Wetbulb')
plt.xlabel("Temperature [C]")
plt.ylabel("Height [m above MSL]")
plt.legend()
plt.grid()
plt.show()
sfcpcl = params.parcelx( prof, flag=1 ) # Surface Parcel
#fcstpcl = params.parcelx( prof, flag=2 ) # Forecast Parcel
#mupcl = params.parcelx( prof, flag=3 ) # Most-Unstable Parcel
#mlpcl = params.parcelx( prof, flag=4 ) # 100 mb Mean Layer Parcel
# This serves as an intensive exercise of matplotlib's transforms
# and custom projection API. This example produces a so-called
# SkewT-logP diagram, which is a common plot in meteorology for
# displaying vertical profiles of temperature. As far as matplotlib is
# concerned, the complexity comes from having X and Y axes that are
# not orthogonal. This is handled by including a skew component to the
# basic Axes transforms. Additional complexity comes in handling the
# fact that the upper and lower X-axes have different data ranges, which
# necessitates a bunch of custom classes for ticks,spines, and the axis
# to handle this.
from matplotlib.axes import Axes
import matplotlib.transforms as transforms
import matplotlib.axis as maxis
import matplotlib.spines as mspines
import matplotlib.path as mpath
from matplotlib.projections import register_projection
# The sole purpose of this class is to look at the upper, lower, or total
# interval as appropriate and see what parts of the tick to draw, if any.
class SkewXTick(maxis.XTick):
def draw(self, renderer):
if not self.get_visible(): return
renderer.open_group(self.__name__)
lower_interval = self.axes.xaxis.lower_interval
upper_interval = self.axes.xaxis.upper_interval
if self.gridOn and transforms.interval_contains(
self.axes.xaxis.get_view_interval(), self.get_loc()):
self.gridline.draw(renderer)
if transforms.interval_contains(lower_interval, self.get_loc()):
if self.tick1On:
self.tick1line.draw(renderer)
if self.label1On:
self.label1.draw(renderer)
if transforms.interval_contains(upper_interval, self.get_loc()):
if self.tick2On:
self.tick2line.draw(renderer)
if self.label2On:
self.label2.draw(renderer)
renderer.close_group(self.__name__)
# This class exists to provide two separate sets of intervals to the tick,
# as well as create instances of the custom tick
class SkewXAxis(maxis.XAxis):
def __init__(self, *args, **kwargs):
maxis.XAxis.__init__(self, *args, **kwargs)
self.upper_interval = 0.0, 1.0
def _get_tick(self, major):
return SkewXTick(self.axes, 0, '', major=major)
#property
def lower_interval(self):
return self.axes.viewLim.intervalx
def get_view_interval(self):
return self.upper_interval[0], self.axes.viewLim.intervalx[1]
# This class exists to calculate the separate data range of the
# upper X-axis and draw the spine there. It also provides this range
# to the X-axis artist for ticking and gridlines
class SkewSpine(mspines.Spine):
def _adjust_location(self):
trans = self.axes.transDataToAxes.inverted()
if self.spine_type == 'top':
yloc = 1.0
else:
yloc = 0.0
left = trans.transform_point((0.0, yloc))[0]
right = trans.transform_point((1.0, yloc))[0]
pts = self._path.vertices
pts[0, 0] = left
pts[1, 0] = right
self.axis.upper_interval = (left, right)
# This class handles registration of the skew-xaxes as a projection as well
# as setting up the appropriate transformations. It also overrides standard
# spines and axes instances as appropriate.
class SkewXAxes(Axes):
# The projection must specify a name. This will be used be the
# user to select the projection, i.e. ``subplot(111,
# projection='skewx')``.
name = 'skewx'
def _init_axis(self):
#Taken from Axes and modified to use our modified X-axis
self.xaxis = SkewXAxis(self)
self.spines['top'].register_axis(self.xaxis)
self.spines['bottom'].register_axis(self.xaxis)
self.yaxis = maxis.YAxis(self)
self.spines['left'].register_axis(self.yaxis)
self.spines['right'].register_axis(self.yaxis)
def _gen_axes_spines(self):
spines = {'top':SkewSpine.linear_spine(self, 'top'),
'bottom':mspines.Spine.linear_spine(self, 'bottom'),
'left':mspines.Spine.linear_spine(self, 'left'),
'right':mspines.Spine.linear_spine(self, 'right')}
return spines
def _set_lim_and_transforms(self):
"""
This is called once when the plot is created to set up all the
transforms for the data, text and grids.
"""
rot = 30
#Get the standard transform setup from the Axes base class
Axes._set_lim_and_transforms(self)
# Need to put the skew in the middle, after the scale and limits,
# but before the transAxes. This way, the skew is done in Axes
# coordinates thus performing the transform around the proper origin
# We keep the pre-transAxes transform around for other users, like the
# spines for finding bounds
self.transDataToAxes = self.transScale + (self.transLimits +
transforms.Affine2D().skew_deg(rot, 0))
# Create the full transform from Data to Pixels
self.transData = self.transDataToAxes + self.transAxes
# Blended transforms like this need to have the skewing applied using
# both axes, in axes coords like before.
self._xaxis_transform = (transforms.blended_transform_factory(
self.transScale + self.transLimits,
transforms.IdentityTransform()) +
transforms.Affine2D().skew_deg(rot, 0)) + self.transAxes
# Now register the projection with matplotlib so the user can select
# it.
register_projection(SkewXAxes)
pcl = sfcpcl
# Create a new figure. The dimensions here give a good aspect ratio
fig = plt.figure(figsize=(6.5875, 6.2125))
ax = fig.add_subplot(111, projection='skewx')
ax.grid(True)
pmax = 1000
pmin = 10
dp = -10
presvals = np.arange(int(pmax), int(pmin)+dp, dp)
# plot the moist-adiabats
for t in np.arange(-10,45,5):
tw = []
for p in presvals:
tw.append(thermo.wetlift(1000., t, p))
ax.semilogy(tw, presvals, 'k-', alpha=.2)
def thetas(theta, presvals):
return ((theta + thermo.ZEROCNK) / (np.power((1000. / presvals),thermo.ROCP))) - thermo.ZEROCNK
# plot the dry adiabats
for t in np.arange(-50,110,10):
ax.semilogy(thetas(t, presvals), presvals, 'r-', alpha=.2)
plt.title(' OAX 140616/1900 (Observed)', fontsize=14, loc='left')
# Plot the data using normal plotting functions, in this case using
# log scaling in Y, as dicatated by the typical meteorological plot
ax.semilogy(prof.tmpc, prof.pres, 'r', lw=2)
ax.semilogy(prof.dwpc, prof.pres, 'g', lw=2)
ax.semilogy(pcl.ttrace, pcl.ptrace, 'k-.', lw=2)
# An example of a slanted line at constant X
l = ax.axvline(0, color='b', linestyle='--')
l = ax.axvline(-20, color='b', linestyle='--')
# Disables the log-formatting that comes with semilogy
ax.yaxis.set_major_formatter(plt.ScalarFormatter())
ax.set_yticks(np.linspace(100,1000,10))
ax.set_ylim(1050,100)
ax.xaxis.set_major_locator(plt.MultipleLocator(10))
ax.set_xlim(-50,50)
plt.show()
And the text file can be found here, before renaming it: OUN_Sounding
Related
I have following code defining a function:
import pandas as pd
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns;sns.set()
from Utils import cmp_iso_forest_od_params
## agg backend is used to create plot as a .png file
mpl.use('agg')
def print_boxplots(data: pd.DataFrame,
graph_filename: str,
col_examined: str,
col_related: str,
sort_func,
title: str,
x_title: str,
y_title: str,
min_val=None,
max_val=None
):
g = data.groupby([col_related])
# graph parameters
scale = 2
show_fliers = True
mean_color='b'
mean_marker='o'
labels = []
data_to_plot_arr = []
#switch = True
for group, group_df in g:
data_to_plot_arr.append(group_df[col_examined])
labels.append(group)
# dynamically set parameters of the graphs so that they are uniform across all graphs, but are minimalised
figsize = ((len(g)) * scale, 25 * scale) # originally (60, 30)
if max_val is None:
max_val = data[col_examined].max()
if min_val is None:
min_val = data[col_examined].min()
tick = (max_val - min_val) / 40
y_labels = np.concatenate([ np.arange(0, min_val-tick, -tick)[::-1], np.arange(0, max_val+6*tick, tick)])
# Create a figure instance
_fig = plt.figure( figsize=figsize)
# Create an axes instance
_ax = _fig.add_subplot(111)
_ax.set_xlabel(col_related, fontsize=20*scale)
# this sorts times and labels for display in the boxplot by the parameters of the boxplots
data_to_plot_arr, labels = zip(*sorted(zip(data_to_plot_arr,labels), key=sort_func ))
# Create the boxplot
bp = _ax.boxplot(data_to_plot_arr, positions=[x for x in range(len(labels))], showfliers=show_fliers)
# following function is described here: https://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.plot
_ax.plot([x for x in range(len(labels))], list(map(lambda x: x.mean(), list(data_to_plot_arr))), marker=mean_marker, color=mean_color)
_ax.set_title(title,
fontsize=25 * scale)
_ax.set_xlabel(x_title, fontsize=25 * scale)
_ax.set_ylabel(y_title, rotation=90, fontsize=25 * scale)
_ax.set_xticklabels(labels, rotation=90)
_ax.set_yticks(y_labels)
_ax.tick_params(axis='x', labelsize=22*scale)
_ax.tick_params(axis='y', labelsize=22*scale)
# custom legend elements gymnastics (it is really awful, but I coudl not find better solution)
colors = [mean_color]
sizes = [6*scale]
texts = ["Mean"]
patches = [plt.plot([], [], marker=mean_marker, ms=sizes[i], ls="", mec=None, color=colors[i],
label="{:s}".format(texts[i]))[0] for i in range(len(texts))]
legend = plt.legend(handles=patches,
bbox_to_anchor=[0.5, -0.08],
loc='center',
title="Boxplots show first and third quartile,\n with variability represented with whiskers",
ncol=2,
prop={'size': 16 * scale})
legend.get_title().set_fontsize(16 * scale)
_ax.grid(True)
# Save the figure
_fig.savefig(graph_filename+'.png', bbox_inches='tight')
What it does is it saves a series of boxplots with plotted means to a file.
What I need is to adjust the thickness of lines and size of all markers in the graph in relation to the scale parameter so I can control the definition of the final picture.
I was not able to find any useful parameters in the documentation so I ended up here.
Current example picture looks like this:
In a recent, very broad question it was asked how to plot several symbols, like "circles, squares, rectangles, stars, thermometers, and boxplots" with matplotlib. From that list, all but thermometers are obvious as either shown in the documentation or in many existing stackoverflow answers. Since the OP did not seem interested in thermomenters at all, I'd rather ask a new question specifically about thermometers here.
How to plot thermometers in matplotlib?
In principle you can plot any symbol you like, making it either a marker or a Path. There does not seem to be any unicode symbol for thermometers though. Font awesome has a thermometer symbol and plotting FontAwesome symbols in matplotlib is possible. Yet there are only 5 differnt fillings
Also, the color of such font symbol is uniform, yet ideally one would have the inner part of a thermometer (the "mercury pillar") in a different color (probably mostly red for associative reasons) or in different colors as to encode temperature in color as well.
So is it possible to have a temperature symbol where the mercury pillar encodes temperature (or in fact any other quantity) in terms of color and filling level? And if so, how?
(I gave an answer below, alternatives to or improvements of that method are welcome as further answers here.)
An option to plot a thermometer consisting of two parts is to create two Paths, the outer hull and the inner mercury pillar. For this one can create the Paths from scratch and allow the inner path to be variable depending on a (normalized) input parameter.
Then plotting both paths as individual scatter plots is possible. In the following, we create a class that has a scatter method, which works similar to a usual scatter, except that it would also take the additional arguments temp for the temperature and tempnorm for the normalization of the temperature as input.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.path as mpath
class TemperaturePlot():
#staticmethod
def get_hull():
verts1 = np.array([[0,-128],[70,-128],[128,-70],[128,0],
[128,32.5],[115.8,61.5],[96,84.6],[96,288],
[96,341],[53,384],[0,384]])
verts2 = verts1[:-1,:] * np.array([-1,1])
codes1 = [1,4,4,4,4,4,4,2,4,4,4]
verts3 = np.array([[0,-80],[44,-80],[80,-44],[80,0],
[80,34.3],[60.7,52],[48,66.5],[48,288],
[48,314],[26.5,336],[0,336]])
verts4 = verts3[:-1,:] * np.array([-1,1])
verts = np.concatenate((verts1, verts2[::-1], verts4, verts3[::-1]))
codes = codes1 + codes1[::-1][:-1]
return mpath.Path(verts/256., codes+codes)
#staticmethod
def get_mercury(s=1):
a = 0; b = 64; c = 35
d = 320 - b
e = (1-s)*d
verts1 = np.array([[a,-b],[c,-b],[b,-c],[b,a],[b,c],[c,b],[a,b]])
verts2 = verts1[:-1,:] * np.array([-1,1])
verts3 = np.array([[0,0],[32,0],[32,288-e],[32,305-e],
[17.5,320-e],[0,320-e]])
verts4 = verts3[:-1,:] * np.array([-1,1])
codes = [1] + [4]*12 + [1,2,2,4,4,4,4,4,4,2,2]
verts = np.concatenate((verts1, verts2[::-1], verts3, verts4[::-1]))
return mpath.Path(verts/256., codes)
def scatter(self, x,y, temp=1, tempnorm=None, ax=None, **kwargs):
self.ax = ax or plt.gca()
temp = np.atleast_1d(temp)
ec = kwargs.pop("edgecolor", "black")
kwargs.update(linewidth=0)
self.inner = self.ax.scatter(x,y, **kwargs)
kwargs.update(c=None, facecolor=ec, edgecolor=None, color=None)
self.outer = self.ax.scatter(x,y, **kwargs)
self.outer.set_paths([self.get_hull()])
if not tempnorm:
mi, ma = np.nanmin(temp), np.nanmax(temp)
if mi == ma:
mi=0
tempnorm = plt.Normalize(mi,ma)
ipaths = [self.get_mercury(tempnorm(t)) for t in temp]
self.inner.set_paths(ipaths)
Usage of this class could look like this,
plt.rcParams["figure.figsize"] = (5.5,3)
plt.rcParams["figure.dpi"] = 72*3
fig, ax = plt.subplots()
p = TemperaturePlot()
p.scatter([.25,.5,.75], [.3,.4,.5], s=[800,1200,1600], temp=[28,39,35], color="C3",
ax=ax, transform=ax.transAxes)
plt.show()
where we plot 3 Thermometers with different temperatures depicted by the fill of the "mercury" pillar. Since no normalization is given it will normalize the temperatures of [28,39,35] between their minimum and maximum.
Or we can use color (c) and temp to show the temparature as in
np.random.seed(42)
fig, ax = plt.subplots()
n = 42
x = np.linspace(0,100,n)
y = np.cumsum(np.random.randn(n))+5
ax.plot(x,y, color="darkgrey", lw=2.5)
p = TemperaturePlot()
p.scatter(x[::4],y[::4]+3, s=300, temp=y[::4], c=y[::4], edgecolor="k", cmap="RdYlBu_r")
ax.set_ylim(-6,18)
plt.show()
I want to plot the average daily temperature from the NOAA Earth System Research Laboratory's Physical Sciences Division onto a map created with matplotlib's Basemap.
The dataset can be download as a netCDF-file from here.
My problem is, however, that Basemap seems not to store the center (or boundary box) coordinates of the map as the subsequent overplot only fills part of the map, see the following figure:
The code to generate the figure is as follows:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import netCDF4
# to check whether a file exists (before downloading it)
import os.path
import sys
fig1, ax1 = plt.subplots(1,1, figsize=(8,6) )
temperature_fname = 'air.sig995.2016.nc'
url = 'https://www.esrl.noaa.gov/psd/thredds/fileServer/Datasets/ncep.reanalysis.dailyavgs/surface/{0}'.format( temperature_fname)
if not os.path.isfile( temperature_fname ):
print( "ERROR: you need to download the file {0}".format(url) )
sys.exit(1)
# read netCDF4 dataset
tmprt_dSet = netCDF4.Dataset( temperature_fname )
# extract (copy) the relevant data
tmprt_vals = tmprt_dSet.variables['air'][:] - 273.15
tmprt_lat = tmprt_dSet.variables['lat'][:]
tmprt_lon = tmprt_dSet.variables['lon'][:]
# close dataset
tmprt_dSet.close()
# use the Miller projection
map1 = Basemap( projection='mill', resolution='l',
lon_0=0., lat_0=0.
)
# draw coastline, map-boundary
map1.drawcoastlines()
map1.drawmapboundary( fill_color='white' )
# draw grid
map1.drawparallels( np.arange(-90.,90.,30.), labels=[1,0,0,0] )
map1.drawmeridians( np.arange(-180.,180.,60.),labels=[0,0,0,1] )
# overplot temperature
## make the longitude and latitude grid projected onto map
tmprt_x, tmprt_y = map1(*np.meshgrid(tmprt_lon,tmprt_lat))
## make the contour plot
CS1 = map1.contourf( tmprt_x, tmprt_y, tmprt_vals[0,:,:],
cmap=plt.cm.jet
)
cbar1 = map1.colorbar( CS1, location='right' )
cbar1.set_label( r'$T$ in $^\circ$C')
plt.show()
Note: if I set lon_0=180 everything looks fine (it is just not the center position I would like to have)
I have the feeling that the solution is pretty obvious and I would appreciate any hint pointing me into that direction.
As commented, the data is aranged from 0 to 360 instead of -180 to 180. So you would need to
map the range between 180 and 360 degrees to -180 to 0.
move the second half of the data in front of the first half, such that it is sorted ascendingly.
Adding the following piece of code in between your data extraction and the plotting function would do that.
# map lon values to -180..180 range
f = lambda x: ((x+180) % 360) - 180
tmprt_lon = f(tmprt_lon)
# rearange data
ind = np.argsort(tmprt_lon)
tmprt_lon = tmprt_lon[ind]
tmprt_vals = tmprt_vals[:, :, ind]
Complete code:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import netCDF4
# read netCDF4 dataset
tmprt_dSet = netCDF4.Dataset('data/air.sig995.2018.nc')
# extract (copy) the relevant data
tmprt_vals = tmprt_dSet.variables['air'][:] - 273.15
tmprt_lat = tmprt_dSet.variables['lat'][:]
tmprt_lon = tmprt_dSet.variables['lon'][:]
# close dataset
tmprt_dSet.close()
### Section added ################
# map lon values to -180..180 range
f = lambda x: ((x+180) % 360) - 180
tmprt_lon = f(tmprt_lon)
# rearange data
ind = np.argsort(tmprt_lon)
tmprt_lon = tmprt_lon[ind]
tmprt_vals = tmprt_vals[:, :, ind]
##################################
fig1, ax1 = plt.subplots(1,1, figsize=(8,6) )
# use the Miller projection
map1 = Basemap( projection='mill', resolution='l',
lon_0=0., lat_0=0. )
# draw coastline, map-boundary
map1.drawcoastlines()
map1.drawmapboundary( fill_color='white' )
# draw grid
map1.drawparallels( np.arange(-90.,90.,30.), labels=[1,0,0,0] )
map1.drawmeridians( np.arange(-180.,180.,60.),labels=[0,0,0,1] )
# overplot temperature
## make the longitude and latitude grid projected onto map
tmprt_x, tmprt_y = map1(*np.meshgrid(tmprt_lon,tmprt_lat))
## make the contour plot
CS1 = map1.contourf( tmprt_x, tmprt_y, tmprt_vals[0,:,:],
cmap=plt.cm.jet
)
cbar1 = map1.colorbar( CS1, location='right' )
cbar1.set_label( r'$T$ in $^\circ$C')
plt.show()
This is challenging. I split the data array into 2 parts. The first part spans from 0° to 180°E longitude. The second part lying on the west side of 0° need longitude shift of 360°. Colormap must be normalized and applied to get common reference colors. Here is the working code and the resulting plot:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import netCDF4
import matplotlib as mpl
#import os.path
#import sys
fig1, ax1 = plt.subplots(1,1, figsize=(10,6) )
temperature_fname = r'.\air.sig995.2018.nc'
# read netCDF4 dataset
tmprt_dSet = netCDF4.Dataset( temperature_fname )
# extract (copy) the relevant data
shift_val = - 273.15
tmprt_vals = tmprt_dSet.variables['air'][:] + shift_val
tmprt_lat = tmprt_dSet.variables['lat'][:]
tmprt_lon = tmprt_dSet.variables['lon'][:]
# prep norm of the color map
color_shf = 40 # to get better lower range of colormap
normalize = mpl.colors.Normalize(tmprt_vals.data.min()+color_shf, \
tmprt_vals.data.max())
# close dataset
#tmprt_dSet.close()
# use the Miller projection
map1 = Basemap( projection='mill', resolution='i', \
lon_0=0., lat_0=0.)
# draw coastline, map-boundary
map1.drawcoastlines()
map1.drawmapboundary( fill_color='white' )
# draw grid
map1.drawparallels( np.arange(-90.,90.,30.), labels=[1,0,0,0] )
map1.drawmeridians( np.arange(-180.,180.,60.), labels=[0,0,0,1] )
# overplot temperature
# split data into 2 parts at column 73 (longitude: +180)
# part1 (take location as is)
beg_col = 0
end_col = 73
grdx, grdy = np.meshgrid(tmprt_lon[beg_col:end_col], tmprt_lat[:])
tmprt_x, tmprt_y = map1(grdx, grdy)
CS1 = map1.contourf( tmprt_x, tmprt_y, tmprt_vals[0,:, beg_col:end_col],
cmap=plt.cm.jet, norm=normalize)
# part2 (longitude is shifted -360 degrees, but -359.5 looks better)
beg_col4 = 73
end_col4 = 144
grdx, grdy = np.meshgrid(tmprt_lon[beg_col4:end_col4]-359.5, tmprt_lat[:])
tmprt_x, tmprt_y = map1(grdx, grdy)
CS4 = map1.contourf( tmprt_x, tmprt_y, tmprt_vals[0,:, beg_col4:end_col4],
cmap=plt.cm.jet, norm=normalize)
# color bars CS1, CS4 are the same (normalized), plot one only
cbar1 = map1.colorbar( CS1, location='right' )
cbar1.set_label( r'$T$ in $^\circ$C')
plt.show()
The resulting plot:
Both answers posted so far are a solution to my question (thank you, ImportanceOfBeingErnest and swatchai).
I thought, however, that there must be a simpler way to do this (and by simple I mean some Basemap utility). So I looked again into the documentation [1] and found something which I overlooked so far: mpl_toolkits.basemap.shiftgrid. The following two lines need to be added to the code:
from mpl_toolkits.basemap import shiftgrid
tmprt_vals, tmprt_lon = shiftgrid(180., tmprt_vals, tmprt_lon, start=False)
Note that the second line has to be added before the meshgrid call.
[1] https://matplotlib.org/basemap/api/basemap_api.html
I'm trying to do a quite simple scatter plot with error bars and semilogy scale. What is a little bit different from tutorials I have found is that the color of the scatterplot should trace a different quantity. On one hand, I was able to do a scatterplot with the errorbars with my data, but just with one color. On the other hand, I realized a scatterplot with the right colors, but without the errorbars.
I'm not able to combine the two different things.
Here an example using fake data:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import division
import numpy as np
import matplotlib.pyplot as plt
n=100
Lx_gas = 1e40*np.random.random(n) + 1e37
Tx_gas = np.random.random(n) + 0.5
Lx_plus_error = Lx_gas
Tx_plus_error = Tx_gas/2.
Tx_minus_error = Tx_gas/4.
#actually positive numbers, this is the quantity that should be traced by the
#color, in this example I use random numbers
Lambda = np.random.random(n)
#this is actually different from zero, but I want to be sure that this simple
#code works with the log axis
Lx_minus_error = np.zeros_like(Lx_gas)
#normalize the color, to be between 0 and 1
colors = np.asarray(Lambda)
colors -= colors.min()
colors *= (1./colors.max())
#build the error arrays
Lx_error = [Lx_minus_error, Lx_plus_error]
Tx_error = [Tx_minus_error, Tx_plus_error]
##--------------
##important part of the script
##this works, but all the dots are of the same color
#plt.errorbar(Tx_gas, Lx_gas, xerr = Tx_error,yerr = Lx_error,fmt='o')
##this is what is should be in terms of colors, but it is without the error bars
#plt.scatter(Tx_gas, Lx_gas, marker='s', c=colors)
##what I tried (and failed)
plt.errorbar(Tx_gas, Lx_gas, xerr = Tx_error,yerr = Lx_error,\
color=colors, fmt='o')
ax = plt.gca()
ax.set_yscale('log')
plt.show()
I even tried to plot the scatterplot after the errorbar, but for some reason everything plotted on the same window is put in background with respect to the errorplot.
Any ideas?
Thanks!
You can set the color to the LineCollection object returned by the errorbar as described here.
from __future__ import division
import numpy as np
import matplotlib.pyplot as plt
n=100
Lx_gas = 1e40*np.random.random(n) + 1e37
Tx_gas = np.random.random(n) + 0.5
Lx_plus_error = Lx_gas
Tx_plus_error = Tx_gas/2.
Tx_minus_error = Tx_gas/4.
#actually positive numbers, this is the quantity that should be traced by the
#color, in this example I use random numbers
Lambda = np.random.random(n)
#this is actually different from zero, but I want to be sure that this simple
#code works with the log axis
Lx_minus_error = np.zeros_like(Lx_gas)
#normalize the color, to be between 0 and 1
colors = np.asarray(Lambda)
colors -= colors.min()
colors *= (1./colors.max())
#build the error arrays
Lx_error = [Lx_minus_error, Lx_plus_error]
Tx_error = [Tx_minus_error, Tx_plus_error]
sct = plt.scatter(Tx_gas, Lx_gas, marker='s', c=colors)
cb = plt.colorbar(sct)
_, __ , errorlinecollection = plt.errorbar(Tx_gas, Lx_gas, xerr = Tx_error,yerr = Lx_error, marker = '', ls = '', zorder = 0)
error_color = sct.to_rgba(colors)
errorlinecollection[0].set_color(error_color)
errorlinecollection[1].set_color(error_color)
ax = plt.gca()
ax.set_yscale('log')
plt.show()
I'm trying to find a peak of an fft of a signal to be used for a further analysis of the signal. I'm using a SpanSelect of data and doing an fft, represented as a frequency spectrum. I really wanted to have the plot be interactive and the user click a point to be further analyzed, but I don't see a way to do that so would like a way to find local frequency peaks. The frequency spectrum may look like this:
So I would want a way to return the frequency that has a peak at 38 hz for example. Is there a way to do this?
use argrelextrema for finding local maxima:
import numpy as np
from scipy.signal import argrelextrema
from matplotlib.pyplot import *
np.random.seed()
x = np.random.random(50)
m = argrelextrema(x, np.greater) #array of indexes of the locals maxima
y = [x[m] for i in m]
plot(x)
plot(m, y, 'rs')
show()
You can do something like that using matplotlib widgets, for example check out the lasso method of selecting points.
You can then use the selected point in any form of analysis you need.
EDIT: Combined lasso and SpanSelect widget from matplotlib examples
#!/usr/bin/env python
from __future__ import print_function
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector, LassoSelector
from matplotlib.path import Path
import matplotlib.pyplot as plt
try:
raw_input
except NameError:
# Python 3
raw_input = input
class SelectFromCollection(object):
"""Select indices from a matplotlib collection using `LassoSelector`.
Selected indices are saved in the `ind` attribute. This tool highlights
selected points by fading them out (i.e., reducing their alpha values).
If your collection has alpha < 1, this tool will permanently alter them.
Note that this tool selects collection objects based on their *origins*
(i.e., `offsets`).
Parameters
----------
ax : :class:`~matplotlib.axes.Axes`
Axes to interact with.
collection : :class:`matplotlib.collections.Collection` subclass
Collection you want to select from.
alpha_other : 0 <= float <= 1
To highlight a selection, this tool sets all selected points to an
alpha value of 1 and non-selected points to `alpha_other`.
"""
def __init__(self, ax, collection, alpha_other=0.3):
self.canvas = ax.figure.canvas
self.collection = collection
self.alpha_other = alpha_other
self.xys = collection.get_offsets()
self.Npts = len(self.xys)
# Ensure that we have separate colors for each object
self.fc = collection.get_facecolors()
if len(self.fc) == 0:
raise ValueError('Collection must have a facecolor')
elif len(self.fc) == 1:
self.fc = np.tile(self.fc, self.Npts).reshape(self.Npts, -1)
self.lasso = LassoSelector(ax, onselect=self.onselect)
self.ind = []
def onselect(self, verts):
path = Path(verts)
self.ind = np.nonzero([path.contains_point(xy) for xy in self.xys])[0]
self.fc[:, -1] = self.alpha_other
self.fc[self.ind, -1] = 1
self.collection.set_facecolors(self.fc)
self.canvas.draw_idle()
def disconnect(self):
self.lasso.disconnect_events()
self.fc[:, -1] = 1
self.collection.set_facecolors(self.fc)
self.canvas.draw_idle()
def onselect(xmin, xmax):
indmin, indmax = np.searchsorted(x, (xmin, xmax))
indmax = min(len(x)-1, indmax)
thisx = x[indmin:indmax]
thisy = y[indmin:indmax]
line2.set_data(thisx, thisy)
ax2.set_xlim(thisx[0], thisx[-1])
ax2.set_ylim(thisy.min(), thisy.max())
fig.canvas.draw()
if __name__ == '__main__':
plt.ion()
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(211, axisbg='#FFFFCC')
x = np.arange(0.0, 5.0, 0.01)
y = np.sin(2*np.pi*x) + 0.5*np.random.randn(len(x))
ax.plot(x, y, '-')
ax.set_ylim(-2,2)
ax.set_title('Press left mouse button and drag to test')
ax2 = fig.add_subplot(212, axisbg='#FFFFCC')
line2, = ax2.plot(x, y, '-')
pts = ax2.scatter(x, y)
# set useblit True on gtkagg for enhanced performance
span = SpanSelector(ax, onselect, 'horizontal', useblit=True,
rectprops=dict(alpha=0.5, facecolor='red') )
selector = SelectFromCollection(ax2, pts)
plt.draw()
raw_input('Press any key to accept selected points')
print("Selected points:")
print(selector.xys[selector.ind])
selector.disconnect()
# Block end of script so you can check that the lasso is disconnected.
raw_input('Press any key to quit')