HoloViews Layout Scatter and ErrorBars Different Axis Ranges - python-3.x

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.

Related

Is there a way to apply 3d-like appearance (like bevel) to 2d matplotlib plots?

I've been working for a while with the matplotlib package in Python, and I know that you can do 2D graphs (usually involving two "dimensions", x and y) or 3D graphs (with functions like plot3D). However, I am unable to find documentation about giving a '3D aesthetic' to a 2D plot.
That is, giving the plot a bit of volume, some shadows, etc.
To give an example, let's say I wanted to create a donut chart in matplotlib. A first draft could be something like this:
import matplotlib.pyplot as plt
#Given an array of values 'values' and,
#optionally, an array of colors 'colors'
#and an array of labels 'labels':
ax = plt.subplot()
ax.pie(
x = values,
labels = labels,
colors = colors
)
center_circle = plt.Circle((0,0), radius = 0.5, fc = "white")
ax.add_artist(center_circle)
plt.show()
However, a quick graph with Excel can give a much more appealing result:
Looking at the documentation of plt.pie, I was not able to find anything significant, apart from the parameter shadow, which when set to True, gives an underwhelming result:
Also, I would like to add effect such as the use of bevel (like the 3d-look of the borders of each wedge of the pie) and more style things. How could I improve the look of my graph with matplotlib? Is it even possible to accomplish it with this library?
One solution might be using a different library. I am not familiar with seaborn, but I know it is also a powerful visualisation library. The same with plotly. Does any one of these libraries allow for these kind of customisations?
There are a whole bunch of options on the matplotlib website for pie charts here: https://matplotlib.org/stable/gallery/pie_and_polar_charts/index.html
Matplotlib does not have a built-in option to add a bevel to a 2D pie chart or any other types of charts directly.
But, you could do this (raised shaddow) for a 3d effect:
import matplotlib.pyplot as plt
# Pie chart, where the slices will be ordered and plotted counter-clockwise:
labels = 'Frogs', 'Hogs', 'Dogs', 'Logs'
sizes = [15, 30, 45, 10]
explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs')
fig1, ax1 = plt.subplots()
ax1.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%',
shadow=True, startangle=90)
ax1.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle.
plt.show()
which give this:

How to mimic the Draw('same') from ROOT with matplotlib

I have a use case from ROOT that I have not been able to reproduce with matplotlib. Here is a minimal example
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
dist1x = np.random.normal(5, 0.05, 10_000)
dist1y = np.random.normal(5, 0.05, 10_000)
dist2x = np.random.normal(15, 0.05, 10_000)
dist2y = np.random.normal(15, 0.05, 10_000)
ax.hist2d(dist1x, dist1y, bins=100, cmap='viridis')
ax.hist2d(dist2x, dist2y, bins=100, cmap='viridis')
plt.show()
and the output is
With ROOT one can do:
TCanvas *c1 = new TCanvas("c1","c1");
TH1D *h1 = new TH1D("h1","h1",500,-5,5);
h1->FillRandom("gaus");
TH1D *h2 = new TH1D("h2","h2",500,-5,5);
h2->FillRandom("gaus");
h1->Draw();
h2->Draw("SAME");
and the two histograms will share the canvas, axes, etc. Why plotting the two histograms in the same figure only shows the last one? How can I reproduce the ROOT behavior?
I think the intended behavior is to draw the sum of both histograms. You can do this by concatenating the arrays before plotting:
ax.hist2d(np.concatenate([dist1x, dist2x]),
np.concatenate([dist1y, dist2y]),
bins=100, cmap='viridis')
(I've modified the number a bit, to make sure the two blobs overlap.)
The default behavior in ROOT for SAME with TH2F is probably not desirable.
The second histogram is drawn over the other, overwriting the fill color of the bins. The information from the first histogram is discarded in every cell if there is at least one event from the second histogram.
To reproduce this behavior, I'd suggest to use numpy.histogram2d. Set the bins of the first histogram to zero if there are entries in the second one, and then plot the sum of both.
bins = np.linspace(0, 20, 100), np.linspace(0, 20, 100)
hist1, _, _ = np.histogram2d(dist1x, dist1y, bins=bins)
hist2, _, _ = np.histogram2d(dist2x, dist2y, bins=bins)
hist1[hist2 > 0] = 0
sum_hist = hist1 + hist2
plt.pcolormesh(*bins, sum_hist)
If the two histograms don't have any populated bin in common, the two behaviors are identical.

How to fill areas between curves with different scales in a plot?

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).

Increasing plot size with multiple plots?

I am trying to plot a histogram with my data.
Using python on Jupyter notebook
viz = cdf[['GyrNative', 'GyMutant', 'Hbond_native', 'HMutant', 'RMSDNative','RMSDMutant', 'RMSFNative', 'RMSFMutant', 'SASANative', 'SASAMutant']]
plt.figure(figsize = (15,10))
viz.hist(grid=True, rwidth = 0.9, color ='red')
plt.tight_layout(pad=0.4, w_pad=0.5, h_pad=0.1)
plt.show()
The plot generated are really tiny... How may I increase the size of each plot at once?
Following from the comments, if you just want to make the whole thing bigger, you should just add figsize to this and rearrange your plt. calls:
plt.tight_layout(pad=0.9, w_pad=0.5, h_pad=0.1)
viz.hist(grid=True, rwidth = 0.9, color ='red', figsize=(15,10))
plt.show()

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()

Resources