set_xlim() does not work with text labels - matplotlib

I am trying to zoom in on geopandas map with labels using set_xlim() in with matplotlib. I basically adapted this SO question to add labels to a map.
However, set_xlim() does not seem to work and did not zoom in on the given extent. (By the way, I've also tried to use text() instead of annotate(), to no avail.)
What I did was the following:
I used the same US county data as in the question linked above, extracted the files, and then executed the following in Jupyter notebook:
import geopandas as gpd
import matplotlib.pyplot as plt
%matplotlib inline
shpfile='shp/cb_2015_us_county_20m.shp'
gdf=gpd.read_file(shpfile)
gdf.plot()
, which gives a map of all US counties as expected:
Adding labels as with one of the answers also works:
ax = gdf.plot()
gdf.apply(lambda x: ax.annotate(s=x.NAME, xy=x.geometry.centroid.coords[0], ha='center'),axis=1);
However, when trying to zoom in to a particular geographic extent with set_xlim() and set_ylim() as follows:
ax = gdf.plot()
gdf.apply(lambda x: ax.annotate(s=x.NAME, xy=x.geometry.centroid.coords[0], ha='center'),axis=1);
ax.set_xlim(-84.2, -83.4)
ax.set_ylim(42, 42.55)
, the two functions do not seem to work. Instead of zooming in, they just trimmed everything outside of the given extent.
If the labeling code is dropped out (gdf.apply(lambda x: ax.annotate(s=x.NAME, xy=x.geometry.centroid.coords[0], ha='center'),axis=1);, the set_xlim() works as expected:
My question is:
What is the correct way to zoom in to an area when labels are present in a plot?

You need some coordinate transformation.
import cartopy.crs as ccrs
# relevant code follows
# set numbers in degrees of longitude
ax.set_xlim(-84.2, -83.4, ccrs.PlateCarree())
# set numbers in degrees of latitude
ax.set_ylim(42, 42.55, ccrs.PlateCarree())
plt.show()
with the option ccrs.PlateCarree(), the input values are transformed to proper data coordinates.

When I try it, I can't draw on matplotlib with the axes restricted. So it's possible to extract the data.
import geopandas as gpd
import matplotlib.pyplot as plt
%matplotlib inline
fig,ax = plt.subplots(1,1, figsize=(4,4), dpi=144)
shpfile = './cb_2015_us_county_20m/cb_2015_us_county_20m.shp'
gdf = gpd.read_file(shpfile)
# gdf = gdf.loc[gdf['STATEFP'] == '27']
gdf['coords'] = gdf['geometry'].apply(lambda x: x.representative_point().coords[:])
gdf['coords'] = [coords[0] for coords in gdf['coords']]
gdf = (gdf[(gdf['coords'].str[0] >= -84.2) & (gdf['coords'].str[0] <= -83.4)
& (gdf['coords'].str[1] >= 42) & (gdf['coords'].str[1] <= 42.55)])
gdf.plot(ax=ax)
gdf.apply(lambda x: ax.annotate(text=x.NAME, xy=x.geometry.centroid.coords[0], ha='center'),axis=1)

Related

Equivalent of Hist()'s Layout hyperparameter in Sns.Pairplot?

Am trying to find hist()'s figsize and layout parameter for sns.pairplot().
I have a pairplot that gives me nice scatterplots between the X's and y. However, it is oriented horizontally and there is no equivalent layout parameter to make them vertical to my knowledge. 4 plots per row would be great.
This is my current sns.pairplot():
sns.pairplot(X_train,
x_vars = X_train.select_dtypes(exclude=['object']).columns,
y_vars = ["SalePrice"])
This is what I would like it to look like: Source
num_mask = train_df.dtypes != object
num_cols = train_df.loc[:, num_mask[num_mask == True].keys()]
num_cols.hist(figsize = (30,15), layout = (4,10))
plt.show()
What you want to achieve isn't currently supported by sns.pairplot, but you can use one of the other figure-level functions (sns.displot, sns.catplot, ...). sns.lmplot creates a grid of scatter plots. For this to work, the dataframe needs to be in "long form".
Here is a simple example. sns.lmplot has parameters to leave out the regression line (fit_reg=False), to set the height of the individual subplots (height=...), to set its aspect ratio (aspect=..., where the subplot width will be height times aspect ratio), and many more. If all y ranges are similar, you can use the default sharey=True.
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
# create some test data with different y-ranges
np.random.seed(20230209)
X_train = pd.DataFrame({"".join(np.random.choice([*'uvwxyz'], np.random.randint(3, 8))):
np.random.randn(100).cumsum() + np.random.randint(100, 1000) for _ in range(10)})
X_train['SalePrice'] = np.random.randint(10000, 100000, 100)
# convert the dataframe to long form
# 'SalePrice' will get excluded automatically via `melt`
compare_columns = X_train.select_dtypes(exclude=['object']).columns
long_df = X_train.melt(id_vars='SalePrice', value_vars=compare_columns)
# create a grid of scatter plots
g = sns.lmplot(data=long_df, x='SalePrice', y='value', col='variable', col_wrap=4, sharey=False)
g.set(ylabel='')
plt.show()
Here is another example, with histograms of the mpg dataset:
import matplotlib.pyplot as plt
import seaborn as sns
mpg = sns.load_dataset('mpg')
compare_columns = mpg.select_dtypes(exclude=['object']).columns
mpg_long = mpg.melt(value_vars=compare_columns)
g = sns.displot(data=mpg_long, kde=True, x='value', common_bins=False, col='variable', col_wrap=4, color='crimson',
facet_kws={'sharex': False, 'sharey': False})
g.set(xlabel='')
plt.show()

Add text flush left below plot in python

I'd like to add text beneath a plot, which includes the source of the used data.
It should be positioned at the edge of the image, so beneath the longest ytick and if possible at a fixed vertical distance to the x-axis.
My approach:
import matplotlib.pyplot as plt
country = ['Portugal','Spain','Austria','Italy','France','Federal Republic of Germany']
value = [6,8,10,12,14,25]
plt.figure(figsize=(4,4))
plt.barh(country,value)
plt.xlabel('x-axis')
plt.text(-18,-2.5,'Source: blablablablablablablablablablablablablablablablabla',ha='left')
Plot of the code
I used plt.text(). My problem with the command is, that I have to manually try x and y values (in the code: -18,-2.5) for different plots.
Is there a better way?
Thanks in advance.
Firstly, I got the box info of yticklabels, and then got the leftmost x location for all the yticklabels. Finally, the blended transform method was used to add text with some location adjustments.
import matplotlib.pyplot as plt
from matplotlib.transforms import IdentityTransform
import matplotlib.transforms as transforms
country = ['Portugal','Spain','Austria','Italy','France','Federal Republic of Germany']
value = [6,8,10,12,14,25]
plt.figure(figsize=(4,4))
plt.barh(country,value)
plt.xlabel('x-axis')
ax = plt.gca()
fig =plt.gcf()
fig.tight_layout()
fig.canvas.draw()
labs = ax.get_yticklabels()
xlocs = []
for ilab in labs:
xlocs.append(ilab.get_window_extent().x0)
print(xlocs)
x0 = min(xlocs)
trans = transforms.blended_transform_factory(IdentityTransform(), ax.transAxes)
plt.text(x0-2.5,-0.2,'Source: blablablablablablablablablablablablablablablablabla',ha='left',transform=trans)
plt.savefig("flush.png",bbox_inches="tight")

Display several polygons at once with matplotlib by getting Google Vision API data in the right format

My goal is to display several polygons at once (this is data I'm getting from Google Vision API).
I have a list of coordinates in this format:
lst_coord = [['(742,335),(840,334),(840,351),(742,352)'], ['(304,1416),(502,1415),(502,1448),(304,1449)']
Knowing that those are strings:
(742,548),(814,549),(814,563),(742,562)
<class 'str'>
import matplotlib.pyplot as plt
def plotpoly(coord,x,y):
coord.append(coord[0])
x, y = zip(*coord)
plt.plot(x,y)
for coord in lst_coord:
plotpoly(coord,x,y)
plt.show()
I'm getting this error:
AttributeError: 'str' object has no attribute 'append'
I've tried quite a few different things. But I can't get it to work...
Bonus: My final goal is to display those polygons on a picture and I'm also struggling with this...
You can use Polygon patches, they will close automatically:
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
from matplotlib.collections import PatchCollection
lst_coord = [['(742,335),(840,334),(840,351),(742,352)'], ['(304,1416),(502,1415),(502,1448),(304,1449)']]
patches = []
for coord in lst_coord:
patches.append(Polygon(eval(coord[0])))
fig, ax = plt.subplots()
ax.add_collection(PatchCollection(patches, fc='none', ec='red'))
ax.set_xlim(0,1000)
ax.set_ylim(0,1500)
plt.show()

Cartopy non-zero central longitude distorted with contourf

I am trying to plot the surface temperature from a NetCDF file using Cartopy and contourf. The domain of my plot is 30S to 60N and 90.044495E to 89.95552E (so all the way around the Earth centered on 90W). Here is a section of my code:
import numpy as np
import wrf as wrf
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
cart_proj = wrf.get_cartopy(skintemp)
lats, lons = wrf.latlon_coords(skintemp)
ax = plt.axes(projection=cart_proj)
ax.coastlines('50m', linewidth=0.8)
clevels = np.linspace(230,300,8)
cmap = plt.cm.YlOrRd
contours_fill = plt.contourf(wrf.to_np(lons), wrf.to_np(lats), skintemp, cmap=cmap, levels = clevels, transform=ccrs.PlateCarree(),extend="both")
cbar = plt.colorbar(contours_fill, shrink = .65, orientation='horizontal', pad=.05)
plt.show()
skintemp, lats and lons are all 2D arrays with dimensions (454, 1483), ordered (lat,lon), and cart_proj = wrf.projection.MercatorWithLatTS.
When I show the plot, it's distorted and incorrect:
I have determined that the issue has to do with the non-zero central longitude. The problem appears to be when the longitude changes from 179.90082 to -179.85632. lons.values[0,370]=179.90082, so I changed contourf to the following:
contours_fill = plt.contourf(wrf.to_np(lons[:,0:371]), wrf.to_np(lats[:,0:371]), skintemp[:,0:371], cmap=cmap, levels = clevels, transform=ccrs.PlateCarree(),extend="both")
which produces the following correct figure:
And when I change contourf to:
contours_fill = plt.contourf(wrf.to_np(lons[:,371:-1]), wrf.to_np(lats[:,371:-1]), skintemp[:,371:-1], cmap=cmap, levels = clevels, transform=ccrs.PlateCarree(),extend="both")
I get the other part of the map:
I cannot seem to get both parts of the map to display correctly together. I tried using contourf twice in the same plot, one for each section of the map, but only the last contourf line plots. Any help would be much appreciated!

Matplotlib histogram with errorbars

I have created a histogram with matplotlib using the pyplot.hist() function. I would like to add a Poison error square root of bin height (sqrt(binheight)) to the bars. How can I do this?
The return tuple of .hist() includes return[2] -> a list of 1 Patch objects. I could only find out that it is possible to add errors to bars created via pyplot.bar().
Indeed you need to use bar. You can use to output of hist and plot it as a bar:
import numpy as np
import pylab as plt
data = np.array(np.random.rand(1000))
y,binEdges = np.histogram(data,bins=10)
bincenters = 0.5*(binEdges[1:]+binEdges[:-1])
menStd = np.sqrt(y)
width = 0.05
plt.bar(bincenters, y, width=width, color='r', yerr=menStd)
plt.show()
Alternative Solution
You can also use a combination of pyplot.errorbar() and drawstyle keyword argument. The code below creates a plot of the histogram using a stepped line plot. There is a marker in the center of each bin and each bin has the requisite Poisson errorbar.
import numpy
import pyplot
x = numpy.random.rand(1000)
y, bin_edges = numpy.histogram(x, bins=10)
bin_centers = 0.5*(bin_edges[1:] + bin_edges[:-1])
pyplot.errorbar(
bin_centers,
y,
yerr = y**0.5,
marker = '.',
drawstyle = 'steps-mid-'
)
pyplot.show()
My personal opinion
When plotting the results of multiple histograms on the the same figure, line plots are easier to distinguish. In addition, they look nicer when plotting with a yscale='log'.