Visualize terrain ground elevation and water depth in the same plot - python-3.x

I would like to get some tips on how to properly visualize/plot two 2-dimensional arrays of the same shape,
say ground_arr and water_arr. ground_arr represents the elevation of some surface, and water_arr represents the height/depth of water on top of that surface. The total elevation is then ofc ground_arr + water_arr.
For now im using plt.imshow(water_arr, cmap=...) to only see the water and plt.imshow(water_arr+ ground_arr) to see the total elevation but i would like to merge both of them in the same plot, to get some map alike plot.
Any tips?

Supposing you have 2D arrays of height values for the terrain and for the water level. And that the water level is set to zero at the places without water.
Just set the water level to Nan where you want the water image to be transparent.
import numpy as np
import matplotlib.pyplot as plt
# Generate test data, terrain is some sine on the distance to the center
terrain_x, terrain_y = np.meshgrid(np.linspace(-15, 15, 1000), np.linspace(-15, 15, 1000))
r = np.sqrt(terrain_x * terrain_x + terrain_y * terrain_y)
terrain_z = 5 + 5 * np.sin(r)
# test data for water has some height where r is between 3 and 4 pi, zero everywhere else
water_z = np.where(3 * np.pi < r, 3 - terrain_z, 0)
water_z = np.where(4 * np.pi > r, water_z, 0)
extent = [-15, 15, -15, 15]
fig, (ax1, ax2, ax3) = plt.subplots(ncols=3)
ax1.imshow(terrain_z, cmap="YlOrBr", extent=extent)
ax1.set_title('Terrain')
ax2.imshow(water_z, cmap="Blues", extent=extent)
ax2.set_title('Water')
ax3.imshow(terrain_z, cmap="YlOrBr", extent=extent)
water_z = np.where(water_z > 0, water_z, np.nan)
ax3.imshow(water_z, cmap="Blues", extent=extent)
ax3.set_title('Combined')
plt.show()

Related

How to draw vertical average lines for overlapping histograms in a loop

I'm trying to draw with matplotlib two average vertical line for every overlapping histograms using a loop. I have managed to draw the first one, but I don't know how to draw the second one. I'm using two variables from a dataset to draw the histograms. One variable (feat) is categorical (0 - 1), and the other one (objective) is numerical. The code is the following:
for chas in df[feat].unique():
plt.hist(df.loc[df[feat] == chas, objective], bins = 15, alpha = 0.5, density = True, label = chas)
plt.axvline(df[objective].mean(), linestyle = 'dashed', linewidth = 2)
plt.title(objective)
plt.legend(loc = 'upper right')
I also have to add to the legend the mean and standard deviation values for each histogram.
How can I do it? Thank you in advance.
I recommend you using axes to plot your figure. Pls see code below and the artist tutorial here.
import numpy as np
import matplotlib.pyplot as plt
# Fixing random state for reproducibility
np.random.seed(19680801)
mu1, sigma1 = 100, 8
mu2, sigma2 = 150, 15
x1 = mu1 + sigma1 * np.random.randn(10000)
x2 = mu2 + sigma2 * np.random.randn(10000)
fig, ax = plt.subplots(1, 1, figsize=(7.2, 7.2))
# the histogram of the data
lbs = ['a', 'b']
colors = ['r', 'g']
for i, x in enumerate([x1, x2]):
n, bins, patches = ax.hist(x, 50, density=True, facecolor=colors[i], alpha=0.75, label=lbs[i])
ax.axvline(bins.mean())
ax.legend()

Add points to a circle

I constructed a large uniform circle (dots = 8000000) in python 3. In the next step, I would like to add additional dots (in myList) outside the circle but at the corresponding position.
import matplotlib.pyplot as plt
import numpy as np
circleSize = 8000000
myList = [155744, 213230, 215537, 262274, 262613, 6143898, 244883, 509516, 1997259, 2336382]
fig = plt.figure(figsize=(4, 4))
n_dots = circleSize # set number of points in circle
uniformSpacing = np.linspace(0, 2*np.pi, n_dots) # create uniform spacing between points
center_x, center_y = (50, 20) # set the center of the circle
x_coord, y_coord = [], [] # for coordinates of points to plot
radius = 10.0 # set the radius of circle
for items in uniformSpacing :
x = center_x + radius*np.cos(items)
y = center_y + radius*np.sin(items)
x_coord.append(x)
y_coord.append(y)
plt.scatter(x_coord, y_coord, c = 'black', s=1) # plot points
plt.show()
How can I add the points to my plot?
Thank you!
If you're coming from a MATLAB background, pyplot has hold on by default, so you can do multiple plot() or scatter() calls without it erasing what was on the plot before.
Also, since you're already using numpy, you should utilize its vectorization capabilities and calculate x_coord and y_coord using SIMD instructions rather than looping and appending to a Python list (which is painfully slow).
fig = plt.figure(figsize=(4, 4))
n_dots = circleSize # set number of points in circle
uniformSpacing = np.linspace(0, 2*np.pi, n_dots) # create uniform spacing between points
center_x, center_y = (50, 20) # set the center of the circle
radius = 10.0 # set the radius of circle
x_coord = center_x + radius * np.cos(uniformSpacing)
y_coord = center_y + radius * np.sin(uniformSpacing)
plt.scatter(x_coord, y_coord, marker='o', color='k');
new_dots_angles = np.linspace(0, 2 * np.pi, 5)
new_radius = 15.0
new_xcoord = center_x + new_radius * np.cos(new_dots_angles)
new_ycoord = center_y + new_radius * np.sin(new_dots_angles)
plt.scatter(new_xcoord, new_ycoord, marker='*', color='r')

Order of object in 3d plot - spiral

I plotted a spiral and a line that should go through the spiral. I am not able to set that the line is behind the front part of the spiral and in front of the back part of the spiral. I tried to use zorder but the line is either whole in front of the spiral or whole behind the spiral. Thank you
Code:
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure()
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
ax.plot(x, y, z, label='parametric curve')
ax.plot([-1,-1], # x
[2,2], # y
[-2, 2], c='red')
plt.show()
For instance, here. The red line is in front of the spiral. If I set zorder it could be behind the spiral. How to set the line goes properly throught the spiral?
Note that matplotlib isn't fully 3D. In order to get enough speed for complex plots, 3D is simulated drawing everything back to front, with each element drawn in its entirety on a specific depth. If you need full 3D, packages such as mayavi are worth investigating.
In order to get the red line inside the spiral, using matplotlib, the following approach can be used:
draw the spiral
draw the red line
draw the spiral again, but only the part that would be in front of the line
Note that such an approach only works if you don't rotate the view too much and you don't use transparency.
Now, to draw only a part of a curve, the standard way uses numpy's masked arrays. But these don't seem to be respected by the 3D plot. The alternative is to set unwanted points to NaN.
To better demonstrates the approach, the code below draws the red line much wider and uses green for the part of the spiral in front of the line. For the real thing, the spiral and the partial spiral would use the same colors.
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
ax.plot(x, y, z, label='parametric curve') # the full spiral
ax.plot([-1,-1], # x
[2,2], # y
[-2, 2], c='red', lw=10)
ym = np.copy(y)
ym[y > 0] = np.NaN
ax.plot(x, ym, z, color='lime') # partial spiral
plt.show()

Proper reuse of Axes in GeoDataFrame.plot()

I want to draw a simple choropleth map of NYC with binned # of yellow cab rides. My gpd.DataFrame looks like this:
bin cnt shape
0 15 1 POLYGON ((-74.25559 40.62194, -74.24448 40.621...
1 16 1 POLYGON ((-74.25559 40.63033, -74.24448 40.630...
2 25 1 POLYGON ((-74.25559 40.70582, -74.24448 40.705...
3 27 1 POLYGON ((-74.25559 40.72260, -74.24448 40.722...
4 32 12 POLYGON ((-74.25559 40.76454, -74.24448 40.764...
where bin is a number of region, cnt is target variable of my plot and shape column is just a series of shapely rectangles composing one covering the whole New York.
Drawing NYC from shapefile:
usa = gpd.read_file('shapefiles/gadm36_USA_2.shp')[['NAME_1', 'NAME_2', 'geometry']]
nyc = usa[usa.NAME_1 == 'New York']
ax = plt.axes([0, 0, 2, 2], projection=ccrs.PlateCarree())
ax.set_extent([-74.25559, -73.70001, 40.49612, 40.91553], ccrs.Geodetic())
ax.add_geometries(nyc.geometry.values,
ccrs.PlateCarree(),
facecolor='#1A237E');
Drawing choropleth alone works fine:
gdf.plot(column='cnt',
cmap='inferno',
scheme='natural_breaks', k=10,
legend=True)
But if I put ax parameter:
gdf.plot(ax=ax, ...)
the output is
<Figure size 432x288 with 0 Axes>
EDIT:
Got it working with following code:
from matplotlib.colors import ListedColormap
cmap = plt.get_cmap('summer')
my_cmap = cmap(np.arange(cmap.N))
my_cmap[:,-1] = np.full((cmap.N, ), 0.75)
my_cmap = ListedColormap(my_cmap)
gax = gdf.plot(column='cnt',
cmap=my_cmap,
scheme='natural_breaks', k=10,
figsize=(16,10),
legend=True,
legend_kwds=dict(loc='best'))
gax.set_title('# of yellow cab rides in NYC', fontdict={'fontsize': 20}, loc='center');
nyc.plot(ax=gax,
color='#141414',
zorder=0)
gax.set_xlim(-74.25559, -73.70001)
gax.set_ylim(40.49612, 40.91553)
When only doing this with .plot calls from geopandas this seems to work fine. Had to make up some data as I don't have yours. Let me know if this helps somehow. Code example should work as is in IPython.
%matplotlib inline
import geopandas as gpd
import numpy as np
from shapely.geometry import Polygon
from random import random
crs = {'init': 'epsg:4326'}
num_squares = 10
# load natural earth shapes
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
# create random choropleth
minx, miny, maxx, maxy = world.geometry.total_bounds
x_coords = np.linspace(minx, maxx, num_squares+1)
y_coords = np.linspace(miny, maxy, num_squares+1)
polygons = [Polygon([[x_coords[i], y_coords[j]],
[x_coords[i+1], y_coords[j]],
[x_coords[i+1], y_coords[j+1]],
[x_coords[i], y_coords[j+1]]]) for i in
range(num_squares) for j in range(num_squares)]
vals = [random() for i in range(num_squares) for j in range(num_squares)]
choro_gdf = gpd.GeoDataFrame({'cnt' : vals, 'geometry' : polygons})
choro_gdf.crs = crs
# now plot both together
ax = choro_gdf.plot(column='cnt',
cmap='inferno',
scheme='natural_breaks', k=10,
#legend=True
)
world.plot(ax=ax)
This should give you something like the following
--Edit, if you're worried about setting the correct limits (as you're doing with the boroughs), please just paste the following to the end of the code (for example)
ax.set_xlim(0, 50)
ax.set_ylim(0, 25)
This should then give you:

Drawing very small shapes (size in µm) with python

I want to create "L" shapes black and white structure on a 20x20 mm figure. Each L shape width and length are defined as uw, ul, lw and ll (see code). A sper my understanding matplotlib works with points per inch (PPI) of 72 and with linewidth of 1, the shape will be 1/72 inch wide. I cannot understand how I can make these figures big enough to be visible when I use plt.show() and save them in the size I want (i.e. 20x20 mm page and each L with their exact shape size with high DPI so that I can view it when I open the saved figure). My code is:
import matplotlib.pyplot as plt
import numpy as np
uw = 20e-6 #upper width in meters
ul = 100e-6 #upper length in meters
lw = 20e-6 #lower width in meters
ll = 100e-6 #lower length in meters
w_space = 50e-6 #width spacing for subplots
h_space = 50e-6 #height spacing for subplots
N = 40
coord = [[0,0], [ll,0], [ll,lw], [uw,lw], [uw,ul], [0,ul]]
coord.append(coord[0]) #repeat the first point to create a 'closed loop'
xs, ys = zip(*coord) #create lists of x and y values
fig = plt.figure(num=None, figsize=(0.1, 0.1), dpi=100, facecolor='w', edgecolor='k') #figsize cannot be chosen below 0.1
for i in range(N):
ax = fig.add_subplot(5,10,i+1)
ax.fill(xs,ys,'k',linewidth=1)
plt.axis('off')
plt.subplots_adjust(wspace = w_space, hspace = h_space)
plt.savefig('screenshots/L_shape.png' ,bbox_inches = 'tight', pad_inches = 0, dpi=10000)
plt.show()

Resources