How to fill areas between curves with different scales in a plot? - python-3.x

I have a dataframe with three features: DEPTH, PERMEABILITY and POROSITY. And I would like to plot DEPTH at y axis and PERMEABILITY and POROSITY together at x axis, although these last two features have different scales.
df = pd.DataFrame({'DEPTH(m)': [100, 150, 200, 250, 300, 350, 400, 450, 500, 550],
'PERMEABILITY(mD)': [1000, 800, 900, 600, 200, 250, 400, 300, 100, 200],
'POROSITY(%)': [0.30, 0.25, 0.15, 0.19, 0.15, 0.10, 0.15, 0.19, 0.10, 0.15]})
I already managed to plot them together, but now I need to fill with two different colors the areas between the curves. For example, when PERMEABILITY curve is on the right side of POROSITY, the area between them should be green. If PERMEABILITY is on the left side, the area between curves should be yellow.
f, ax1 = plt.subplots()
ax1.set_xlabel('PERMEABILITY(mD)')
ax1.set_ylabel('DEPTH(m)')
ax1.set_ylim(df['DEPTH(m)'].max(), df['DEPTH(m)'].min())
ax1.plot(df['PERMEABILITY(mD)'], df['DEPTH(m)'], color='red')
ax1.tick_params(axis='x', labelcolor='red')
ax2 = ax1.twiny()
ax2.set_xlabel('POROSITY(%)')
ax2.plot(df['POROSITY(%)'], df['DEPTH(m)'], color='blue')
ax2.tick_params(axis='x', labelcolor='blue')
So the right output should be like this: (Sorry for the Paint image below)
Anyone could help me with this?

You can use the fill_betweenx() function, however you need to convert one of your axis to the scale of the other one, because you use twiny. Below, I converted your POROSITY data to fit to the axis of PERMEABILITY.
Then you can use two conditional fill_betweenx, where the two curves are larger than each other, to assign different colors to those patches. Also, since your data is discrete, you need to set interpolate=True in your fill_betweenx functions.
f, ax1 = plt.subplots()
ax1.set_xlabel('PERMEABILITY(mD)')
ax1.set_ylabel('DEPTH(m)')
ax1.set_ylim(df['DEPTH(m)'].max(), df['DEPTH(m)'].min())
ax1.plot(df['PERMEABILITY(mD)'], df['DEPTH(m)'], color='red')
ax1.tick_params(axis='x', labelcolor='red')
ax2 = ax1.twiny()
ax2.set_xlabel('POROSITY(%)')
ax2.plot(df['POROSITY(%)'], df['DEPTH(m)'], color='blue')
ax2.tick_params(axis='x', labelcolor='blue')
# convert POROSITY axis to PERMEABILITY
# value-min / range -> normalized POROSITY (normp)
# normp*newrange + newmin -> stretched POROSITY to PERMEABILITY
z=df['POROSITY(%)']
x=df['PERMEABILITY(mD)']
nz=((z-np.min(z))/(np.max(z)-np.min(z)))*(np.max(x)-np.min(x))+np.min(x)
# fill between in green where PERMEABILITY is larger
ax1.fill_betweenx(df['DEPTH(m)'],x,nz,where=x>=nz,interpolate=True,color='g')
# fill between in yellow where POROSITY is larger
ax1.fill_betweenx(df['DEPTH(m)'],x,nz,where=x<=nz,interpolate=True,color='y')
plt.show()
The result is as below (I might have used different colors, but I assume that's not a concern).

Related

How to add numbers to top of histogram bars?

I want to add the frequency number above each bar and want to keep the bars separated. I can't figure out how to keep the bars separated.
My code is:
df_dm2["resolution"].plot(kind='hist', color='blue', edgecolor='black', linewidth=1.2 )
plt.ylabel('Frequency')
plt.xlabel('Resolution (m)')
plt.title('Region DM2 Frame Resolution Frequency')
#plt.text( .92, 3.5, r'$\mu=1.092531 ,\ \sigma=0.091098$')
#plt.xticks(np.arange(0, 2, 0.2))
plt.yticks(np.arange(0, 200,25))
plt.savefig('histogram_dm2.png',dpi=72, bbox_inches='tight')
plt.show()
My output thus far is as follows: enter image description here
Use the rwidth option in plot:
df_dm2["resolution"].plot(kind='hist',
color='blue',
edgecolor='black',
linewidth=1.2,
rwidth=0.75)

using % range in plot labels

I want to change the range in terms of %. In the attached figure along x-axis, I want to label it as -1%, -0.05%, 0, 0.05% and 1% along x-axis. Is there any way to do that directly in python using range function?
ax.set_xlim(-0.012, 0.012, 0.2)
You can modify the tick labels as shown below
fig, ax = plt.subplots()
x = np.linspace(-0.01, 0.01, 10)
ax.plot(x, -x/10, '-bo')
ax.set_xlim(-0.012, 0.012, 0.2)
labels = ['{:.2f}%'.format(item*100) for item in ax.get_xticks()]
ax.set_xticklabels(labels)

How to fill the area near the y axis in a plot?

I need to plot two features of a dataframe where df['DEPTH'] should be inverted and at y-axis and df['SPECIES'] should be at x-axis. Imagining that the plot would be a variant line, I would like to fill with color the area near the y-axis (left side of the line). So I wrote some code:
df = pd.DataFrame({'DEPTH': [100, 150, 200, 250, 300, 350, 400, 450, 500, 550],
'SPECIES':[12, 8, 9, 6, 10, 7, 4, 3, 1, 2]})
plt.plot(df['SPECIES'], df['DEPTH'])
plt.fill_between(df['SPECIES'], df['DEPTH'])
plt.ylabel('DEPTH')
plt.xlabel('SPECIES')
plt.ylim(np.max(df['DEPTH']), np.min(df['DEPTH']))
I tried 'plt.fill_between', but then the left part of the plot doesn't get all filled.
Anyone knows how can the filled part (blue color) reach the y-axis?
Instead of fill_between, you can use fill_betweenx. It will start filling from 0 by default, thus you need to set your x limit to be 0 too.
plt.plot(df['SPECIES'], df['DEPTH'])
# changing fill_between to fill_betweenx -- the order also changes
plt.fill_betweenx(df['DEPTH'], df['SPECIES'])
plt.ylabel('DEPTH')
plt.xlabel('SPECIES')
plt.ylim(np.max(df['DEPTH']), np.min(df['DEPTH']))
# setting the lower limit to 0 for the filled area to reach y axis.
plt.xlim(0,np.max(df['SPECIES']))
plt.show()
The result is below.

Set matplotlib legend markersize to a constant

I'm making a diagram using matplotlib, and it has plt.Circles and plt.axvlines to represent different shapes. I need a legend to describe these shapes, but the problem is the legend marker (the image part), changes size depending on the input, which looks awful. How do I set the size to a constant?
fig = plt.figure(figsize=(6.4, 6), dpi=200, frameon=False)
ax = fig.gca()
# 3 Circles, they produce different sized legend markers
ax.add_patch(plt.Circle((0,0), radius=1, alpha=0.9, zorder=0, label="Circle"))
ax.add_patch(plt.Circle((-1,0), radius=0.05, color="y", label="Point on Circle"))
ax.add_patch(plt.Circle((1, 0), radius=0.05, color="k", label="Opposite Point on Circle"))
# A vertical line which produces a huge legend marker
ax.axvline(0, ymin=0.5-0.313, ymax=0.5+0.313, linewidth=12, zorder=1, c="g", label="Vertical Line")
ax.legend(loc=2)
ax.set_xlim(-2,1.2) # The figsize and limits are meant to preserve the circle's shape
ax.set_ylim(-1.5, 1.5)
fig.show()
I've seen solutions including legend.legendHandles[0]._size or various assortments of that, and it doesn't seem to change the size regardless of the value I set
The legend markers for the circles are different in size because the first circle has no edgecolor, while the two other ones have an edgecolor set via color. You may instead set the facecolor of the circle. Alternatively, you can set the linewidth of all 3 circles equal.
The legend marker for the line is so huge because it simply copies the attribute from the line in the plot. If you want to use a different linewidth, you can update it via the respective legend handler.
import matplotlib.pyplot as plt
from matplotlib.legend_handler import HandlerLine2D
def update_prop(handle, orig):
handle.update_from(orig)
handle.set_linewidth(2)
fig, ax = plt.subplots(figsize=(6.4, 6), dpi=200, frameon=False)
# 3 Circles, set the facecolor instead of edge- and face-color
ax.add_patch(plt.Circle((0,0), radius=1, alpha=0.9, zorder=0, label="Circle"))
ax.add_patch(plt.Circle((-1,0), radius=0.05, facecolor="y", label="Point on Circle"))
ax.add_patch(plt.Circle((1, 0), radius=0.05, facecolor="k", label="Opposite Point on Circle"))
# Line, update the linewidth via
ax.axvline(0, ymin=0.5-0.313, ymax=0.5+0.313, linewidth=12, zorder=1, c="g", label="Vertical Line")
ax.legend(loc=2, handler_map={plt.Line2D:HandlerLine2D(update_func=update_prop)})
ax.set_xlim(-2,1.2)
ax.set_ylim(-1.5, 1.5)
plt.show()

HoloViews Layout Scatter and ErrorBars Different Axis Ranges

I have a layout of two plots that have very different y-axis scales. Is there a way to automatically set the y-axis range for each plot? By default, both plots are initially displayed with the same y-axis range of the second plot. See example below.
A_points = [
("2000", 1.5, 0.3, 0.5),
("2015", 2.3, 0.7, 0.3)
]
B_points = [
("2000", 13000, 800, 200),
("2015", 12000, 400, 600)
]
%%opts Scatter (size=10)
(hv.Scatter(A_points, kdims=["Year"], vdims=["A"]) * hv.ErrorBars(A_points, vdims=["y", "yerrneg", "yerrpos"])) + \
(hv.Scatter(B_points, kdims=["Year"], vdims=["B"]) * hv.ErrorBars(B_points, vdims=["y", "yerrneg", "yerrpos"]))
Sure; just specify +axiswise normalization for each of the Element types in your plot:
The default is to normalize all plots that share the same axes together, but turning on independent normalization per axis will disable that behavior.

Resources