I used the code below for creating one row two column subplot using plotly package. But the two x-axis overlapped (see the attached screenshot). The first plot's axis is covering both plot. How to solve this issue? I don't see a good example from plotly site. Also how to remove the legend?
import plotly
import plotly.graph_objs as go
def plot(plot_dic, width=1000, **kwargs):
kwargs['output_type'] = 'div'
plot_str = plotly.offline.plot(plot_dic, **kwargs)
print('%%angular <div style=" width: %spx"> %s </div>' % ( width, plot_str))
# Create a trace
trace0 = go.Scatter(
x = np.arange(100)+1,
y = np.round(df[df['']=='value'].iloc[:,1:]*100, 2).values.reshape(100),
mode = 'lines+markers',
)
trace1 = go.Scatter(
x = np.arange(100)+1,
y = np.cumsum(np.round(df[df['']=='value'].iloc[:,1:]*100, 2).values).reshape(100),
mode = 'lines+markers',
)
fig = plotly.tools.make_subplots(rows=1, cols=2, subplot_titles=('Variance', 'Cumulative Variance')
# ,shared_xaxes=True, shared_yaxes=True
)
fig['layout'].update(height=500, width=800, title="Plot title"
,xaxis=dict(range = [1, 400],
dtick = 40,
showticklabels=True,
tickfont=dict(family='Arial, sans-serif', size=14, color='black'),
exponentformat='e',
showexponent='all')
,xaxis1=dict(range = [1, 400],
dtick = 40,
showticklabels=True,
tickfont=dict(family='Arial, sans-serif', size=14, color='black'),
exponentformat='e',
showexponent='all')
)
fig.append_trace(trace0, 1, 1)
fig.append_trace(trace1, 1, 2)
plot(fig, width=600, show_link=False)
alternative way to create subplot but avoid overlap of x-axis is to change the subplot to 2 rows and 1 column instead.
fig = plotly.tools.make_subplots(rows=2, cols=1, subplot_titles=('Variance', 'Cumulative Variance')
# ,shared_xaxes=True, shared_yaxes=True)
Related
I have a grid of subplots and I would like to adjust the white space between only two of them such that the shared x labels are centred without overlapping either graph.
This question has a solution for when these are the only two subplots. However I'm struggling to adjust this to two specific subplots in a grid of many.
This code can be used to illustrate my problem.
In [1]
fig = plt.figure(figsize = (15, 10))
gs = fig.add_gridspec(2,4)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1:3])
ax3 = fig.add_subplot(gs[0, 3])
ax4 = fig.add_subplot(gs[1, 0])
ax5 = fig.add_subplot(gs[1, 1])
ax6 = fig.add_subplot(gs[1, 2])
ax7 = fig.add_subplot(gs[1, 3])
np.random.seed(19680801)
# Example data
people = ('Really Really Long Name', 'Another Long Name', 'Short Name', 'Name', 'N')
y_pos = np.arange(len(people))
performance = 3 + 10 * np.random.rand(len(people))
ax5.barh(y_pos, performance, align='center')
ax5.set_yticks(y_pos, labels=people)
ax5.invert_xaxis()
ax5.set_xlabel('Label')
ax5.set_title('Bar 1')
ax6.barh(y_pos, performance, align='center')
ax6.set_yticks(y_pos, labels=people)
ax6.set_xlabel('Label')
ax6.set_title('Bar 2')
Out [1]
If I apply the solution to the linked question here then every subplot's white space is effected. I know this is because it calls on fig.dpi_scale_trans which effects the whole figure but I'm new to transforms and can't work out what to use in its place
In [2]
fig.tight_layout()
fig.subplots_adjust(wspace=0.7)
plt.setp(axes[0].yaxis.get_majorticklabels(), ha='center')
# Create offset transform by some points in x direction
dx = 60 / 72.
dy = 0 / 72.
offset = mlb.transforms.ScaledTranslation(dx, dy, fig.dpi_scale_trans)
# apply offset transform to all y ticklabels.
for label in ax6.yaxis.get_majorticklabels():
label.set_transform(label.get_transform() + offset)
Out [2]
I figured out how to solve this so posting my own answer in case anybody has a similar problem in the future.
This question and answer from 7 years ago contained the necessary help to solve the problem.
Essentially you must plot and position different GridSpecs in the figure using GridSpec from matplotlib.gridspec rather than calling one with fig.add_gridspec()
Link to GridSpec documentation
Following on from my example above I wanted to create a 2x4 grid. To do that we can plot the following grids in set positions of the figure:
Left: 1x2
Top Centre: 1x1
Bottom Centre: 2x1
Right: 1x2
In [1]:
from matplotlib.gridspec import GridSpec
fig = plt.figure(figsize = (15, 10))
# Example Data
people = ('Really Really Long Name', 'Another Long Name', 'Short Name', 'Name',
'N')
y_pos = np.arange(len(people))
performance = 3 + 10 * np.random.rand(len(people))
# Left portion of grid (2x1).
# 'left' and 'right' tell the grid where it should start and finish
gs1 = GridSpec(2, 1)
gs1.update(left = 0, right = 0.2)
# Plotting empty subplots for illustration purposes
for i in gs1:
ax = plt.subplot(i)
# Mirroring on the right portion of the grid
gs2 = GridSpec(2, 1)
gs2.update(left = 0.8, right = 1)
for i in gs2:
ax = plt.subplot(i)
# Plotting in top center
# Note here we only need to plot a 1x1
gs3 = GridSpec(1, 1)
gs3.update(left = 0.25, right = 0.75, bottom = 0.53) #0.53 aligns with sides
ax3 = plt.subplot(gs3[0])
# Plotting the barh in the bottom center
# wsapce only adjusts this grid not the entire figure
gs4 = GridSpec(1, 2)
gs4.update(left = 0.2, right = 0.8, top = 0.45, wspace = 0.75)
# Left barh
ax5 = plt.subplot(gs4[0])
ax5.barh(y_pos, performance, align='center')
ax5.set_yticks([])
ax5.invert_xaxis()
ax5.set_xlabel('Label')
ax5.set_title('Bar 1')
# Right barh
ax6 = plt.subplot(gs4[1])
ax6.barh(y_pos, performance, align='center')
ax6.set_yticks(y_pos, labels=people)
ax6.set_xlabel('Label')
ax6.set_title('Bar 2')
plt.show()
Out [1]:
I dont have a working code - but a snipet of my code can be as follows. I'm trying to use geopandas with mathplotlib, and trying to plot a map with links and points.
shape_file = os.path.join(os.getcwd(), "Healthboard")
healthboard = gp.read_file(os.path.join(shape_file, "healthboard.shp"))
healthboard = healthboard.to_crs({'init': 'epsg:4326'}) # re-projection
geo_df1 = geo_df1[geo_df1['HealthBoardArea2019Code'] == string1]
geo = geo_df[geo_df['Healthboard '] == string2]
new_shape_file = os.path.join(os.getcwd(), "Council_Shapefile")
council_to_healtboard = pd.read_csv("council_to_healthboard.csv")
council_to_healthboard = council_to_healtboard.rename(columns = {'CA': 'Council_area_code'})
council = gp.read_file(os.path.join(new_shape_file, "Council_shapefile.shp"))
council = council.to_crs({'init': 'epsg:4326'})
council = council.rename(columns = {'la_s_code':'Council_area_code'})
df = council.merge(council_to_healthboard, on = 'Council_area_code', how ='inner')
# Plotting stuff
fig, ax = plt.subplots(figsize=(15,15))
geo_df1.plot(ax = ax, markersize=35, color = "blue", marker = "*", label = "Postcode Sector")
geo.geometry.plot(ax = ax, color = "red", markersize=20, alpha = 0.8, label = 'SiteName')
#healthboard[healthboard["HBName"]=="Lothian"].plot(ax = ax, alpha = 0.6)
#healthboard[healthboard["HBName"]=="Lothian"].boundary.plot(ax = ax, color = "black", alpha = 0.6)
df[df["HB"]=="S08000024"].boundary.plot(ax =ax, color = "black", alpha = 0.1)
df[df["HB"]=="S08000024"].plot(ax =ax, cmap = "viridis", alpha = 0.1)
links_gp.plot(ax =ax, alpha = 0.25, color='brown', linestyle = "-")
My links_gp.plot has 40 time periods, as a result I want to make one plot, and have a button to adjust the parameters of time. Or if not possible a series of 40 plots. I've tried numerous ways but keep failing on this. I would really appreciate if someone could guide me on this.
I'm aware that you are using matplotlib, but if you don't mind using bokeh instead, you could use the following. To create an interactive plot with a possibility to adjust a parameter, bokeh provides a slider widget which can be used to change the plot based on a custom filter function.
An example from a geopandas dataframe with LineString geometries similar to the one you posted:
import geopandas as gpd
from bokeh.io import show, output_notebook
from bokeh.models import (CDSView, ColumnDataSource, CustomJS,
CustomJSFilter, Slider, Column)
from bokeh.layouts import column
from bokeh.plotting import figure
# prepare data source
links_gp['x'] = links_gp.apply(lambda row: list(row['geometry'].coords.xy[0]), axis=1)
links_gp['y'] = links_gp.apply(lambda row: list(row['geometry'].coords.xy[1]), axis=1)
# drop geometry column, because it can't be serialized to ColumnDataSource
links_gp.drop('geometry', axis=1, inplace=True)
linesource = ColumnDataSource(links_gp)
p = figure(title = 'Bokeh Time Slider',
plot_height = 500,
plot_width = 600,
toolbar_location = 'below',
tools = "pan, wheel_zoom, box_zoom, reset")
slider = Slider(title='Time Period', start=1, end=40, step=1, value=1)
# Callback triggers the filter when the slider moves
callback = CustomJS(args=dict(source=linesource),
code="""source.change.emit();""")
slider.js_on_change('value', callback)
# Custom filter that selects all lines of the time period based on the slider value
custom_filter = CustomJSFilter(args=dict(slider=slider),
code="""
var indices = [];
// iterate through rows of data source and check if time period value equals the slider value
for (var i = 0; i < source.get_length(); i++){
if (source.data['Time Period'][i] == slider.value){
indices.push(true);
} else {
indices.push(false);
}
}
return indices;
""")
# Use filter to determine which lines are visible
view = CDSView(source=linesource, filters=[custom_filter])
# plot lines to map
p.multi_line('x', 'y', source=linesource, color='red', line_width=3, view=view)
layout = column(p, slider)
show(layout)
This will be the result of the above code.
Here is my script to plot data from a Geogtiff file using basemap. The data is categorical and there are 13 categories within this domain. The problem is that some categories get bunched up into one colour and thus some resolution is lost.
Unfortunately, I do not know how to fix this. I read that plt.cm.get_cmp is better for discrete datasets but I have not gotten it to work unfortunately.
gtif = 'some_dir'
ds = gdal.Open(gtif)
data = ds.ReadAsArray()
gt = ds.GetGeoTransform()
proj = ds.GetProjection()
xres = gt[1]
yres = gt[5]
xmin = gt[0] + xres
xmax = gt[0] + (xres * ds.RasterXSize) - xres
ymin = gt[3] + (yres * ds.RasterYSize) + yres
ymax = gt[3] - yres
xy_source = np.mgrid[xmin:xmax+xres:xres, ymax+yres:ymin:yres]
ds = None
fig2 = plt.figure(figsize=[12, 11])
ax2 = fig2.add_subplot(111)
ax2.set_title("Land use plot")
bm2 = Basemap(ax=ax2,projection='cyl',llcrnrlat=ymin,urcrnrlat=ymax,llcrnrlon=xmin,urcrnrlon=xmax,resolution='l')
bm2.drawcoastlines(linewidth=0.2)
bm2.drawcountries(linewidth=0.2)
data_new=np.copy(data)
data_new[data_new==255] = 0
nbins = np.unique(data_new).size
cb =plt.cm.get_cmap('jet', nbins+1)
img2 =bm2.imshow(np.flipud(data_new), cmap=cb)
ax2.set_xlim(3, 6)
ax2.set_ylim(50,53)
plt.show()
labels = [str(i) for i in np.unique(data_new)]
cb2=bm2.colorbar(img2, "right", size="5%", pad='3%', label='NOAH Land Use Category')
cb2.set_ticklabels(labels)
cb2.set_ticks(np.unique(data_new))
Here are the categories that are found within the domain (numbered classes):
np.unique(data_new)
array([ 0, 1, 4, 5, 7, 10, 11, 12, 13, 14, 15, 16, 17], dtype=uint8)
Thanks so much for any help here. I have also attached the output image that shows the mismatch. (not working)
First, this colormap problem is independent of the use of basemap. The following is therefore applicable to any matplotlib plot.
The problem here is that creating a colormap from n values distributes those values equally over the colormap range. Some values from the image therefore fall into the same colorrange within the colormap.
To prevent this, one can generate a colormap with the initial number of categories as shown below.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.colors
# generate some data
data = np.array( [ 0, 1, 4, 5, 7, 10]*8 )
np.random.shuffle(data)
data = data.reshape((8,6))
# generate colormap and norm
unique = np.unique(data)
vals = np.arange(int(unique.max()+1))/float(unique.max())
cols = plt.cm.jet(vals)
cmap = matplotlib.colors.ListedColormap(cols, int(unique.max())+1)
norm=matplotlib.colors.Normalize(vmin=-0.5, vmax=unique.max()+0.5)
fig, ax = plt.subplots(figsize=(5,5))
im = ax.imshow(data, cmap=cmap, norm=norm)
for i in range(data.shape[0]):
for j in range(data.shape[1]):
ax.text(j,i,data[i,j], color="w", ha="center", va="center")
cb = fig.colorbar(im, ax=ax, norm=norm)
cb.set_ticks(unique)
plt.show()
This can be extended to exclude the colors not present in the image as follows:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.colors
# generate some data
data = np.array( [ 0, 1, 4, 5, 7, 10]*8 )
np.random.shuffle(data)
data = data.reshape((8,6))
unique, newdata = np.unique(data, return_inverse=1)
newdata = newdata.reshape(data.shape)
# generate colormap and norm
new_unique = np.unique(newdata)
vals = np.arange(int(new_unique.max()+1))/float(new_unique.max())
cols = plt.cm.jet(vals)
cmap = matplotlib.colors.ListedColormap(cols, int(new_unique.max())+1)
norm=matplotlib.colors.Normalize(vmin=-0.5, vmax=new_unique.max()+0.5)
fig, ax = plt.subplots(figsize=(5,5))
im = ax.imshow(newdata, cmap=cmap, norm=norm)
for i in range(newdata.shape[0]):
for j in range(newdata.shape[1]):
ax.text(j,i,data[i,j], color="w", ha="center", va="center")
cb = fig.colorbar(im, ax=ax, norm=norm)
cb.ax.set_yticklabels(unique)
plt.show()
I've got a little function that generates a plot of two subplots. One subplot is two histograms overlaid and the other subplot is the results of dividing one histogram by the other.
For the second subplot, I don't know how to remove the edges between histogram bars (like the one above it) and I don't know how to reduce its height (such that it is, say, half the height of the one above it). I'm also not sure how to set the title to the very top of the plot.
How could these things be done?
My code is as follows:
import numpy
import matplotlib.pyplot
import datavision # sudo pip install datavision
import shijian # sudo pip install shijian
def main():
a = numpy.random.normal(2, 2, size = 120)
b = numpy.random.normal(2, 2, size = 120)
save_histogram_comparison_matplotlib(
values_1 = a,
values_2 = b,
label_1 = "a",
label_2 = "b",
normalize = True,
label_ratio_x = "frequency",
label_y = "",
title = "comparison of a and b",
filename = "test.png"
)
def save_histogram_comparison_matplotlib(
values_1 = None,
values_2 = None,
filename = None,
number_of_bins = None,
normalize = True,
label_x = "",
label_y = None,
label_ratio_x = "frequency",
label_ratio_y = "ratio",
title = None,
label_1 = "1",
label_2 = "2",
overwrite = True,
LaTeX = False
):
matplotlib.pyplot.ioff()
if LaTeX is True:
matplotlib.pyplot.rc("text", usetex = True)
matplotlib.pyplot.rc("font", family = "serif")
if number_of_bins is None:
number_of_bins_1 = datavision.propose_number_of_bins(values_1)
number_of_bins_2 = datavision.propose_number_of_bins(values_2)
number_of_bins = int((number_of_bins_1 + number_of_bins_2) / 2)
if filename is None:
filename = shijian.propose_filename(
filename = title.replace(" ", "_") + ".png",
overwrite = overwrite
)
values = []
values.append(values_1)
values.append(values_2)
bar_width = 0.8
figure, (axis_1, axis_2) = matplotlib.pyplot.subplots(nrows = 2)
ns, bins, patches = axis_1.hist(
values,
normed = normalize,
histtype = "stepfilled",
bins = number_of_bins,
alpha = 0.5,
label = [label_1, label_2],
rwidth = bar_width,
linewidth = 0
)
axis_1.legend()
axis_2.bar(
bins[:-1],
ns[0] / ns[1],
edgecolor = "#ffffff", # "none"
alpha = 1,
width = bins[1] - bins[0]
)
axis_1.set_xlabel(label_x)
axis_1.set_ylabel(label_y)
axis_2.set_xlabel(label_ratio_x)
axis_2.set_ylabel(label_ratio_y)
matplotlib.pyplot.title(title)
matplotlib.pyplot.savefig(filename)
matplotlib.pyplot.close()
if __name__ == "__main__":
main()
You have 3 questions:
1. How to remove the edges between histogram bars
Here, you can set the linewidth to 0 for the call to bar:
axis_2.bar(
bins[:-1],
ns[0] / ns[1],
linewidth=0,
alpha = 1,
width = bins[1] - bins[0]
)
2. How to reduce the height of the second subplot
Here, we can send kwargs to gridspec when we create the subplots. The relevant option is height_ratios. We send them using the gridspec_kw option to subplots. If we set it to (2,1), that makes the first subplot twice the height of the second one.
figure, (axis_1, axis_2) = matplotlib.pyplot.subplots(
nrows = 2,
gridspec_kw={'height_ratios':(2,1)}
)
3. How to set the title to the very top of the plot
When you call matplotlib.pyplot.title(title), that is actually setting the title of the currently active subplot axes, which in this case is axis_2. To set the title of the overall figure, you can set the suptitle:
matplotlib.pyplot.suptitle(title)
Or alternatively, since you already named your figure, you can use:
figure.suptitle(title)
And likewise, you could use:
figure.savefig(filename)
to save a few keystrokes.
Putting it all together:
I have such a plot, and would like to add a the colorbar code (which color corresponds to what number) on the right hand below. I saw some example which where used for imshow not pie chart.
#!/usr/bin/env python
"""
http://matplotlib.sf.net/matplotlib.pylab.html#-pie for the docstring.
"""
from pylab import *
fracs = [33,33,33]
starting_angle = 90
axis('equal')
for item in range(9):
color_vals = [-1, 0, 1]
my_norm = matplotlib.colors.Normalize(-1, 1) # maps your data to the range [0, 1]
my_cmap = matplotlib.cm.get_cmap('RdBu') # can pick your color map
patches, texts, autotexts = pie(fracs, labels = None, autopct='%1.1f%%', startangle=90, colors=my_cmap(my_norm(color_vals)))
subplot(3,3,item+1)
fracs = [33,33,33]
starting_angle = 90
axis('equal')
patches, texts, autotexts = pie(fracs, labels = None, autopct='%1.1f%%', startangle=90, colors=my_cmap(my_norm(color_vals)))
for item in autotexts:
item.set_text("")
subplots_adjust(left=0.125, bottom=0.1, right=0.9, top=0.9, wspace=0.0, hspace=0.5)
savefig('/home/superiois/Downloads/projectx3/GRAIL/pie1.png')
show()
Also, it would be great if you tell me how to customize the size and location of colorbar code; Thanks.
Usually a legend is more appropriate for discrete values and a colorbar for continuous values. That said, its off course possible since mpl allows you to create a colorbar from scratch.
import matplotlib.pyplot as plt
import matplotlib as mpl
fracs = [33,33,33]
starting_angle = 90
fig, axs = plt.subplots(3,3, figsize=(6,6))
fig.subplots_adjust(hspace=0.1,wspace=0.0)
axs = axs.ravel()
for n in range(9):
color_vals = [-1, 0, 1]
my_norm = mpl.colors.Normalize(-1, 1) # maps your data to the range [0, 1]
my_cmap = mpl.cm.get_cmap('RdBu', len(color_vals)) # can pick your color map
patches, texts, autotexts = axs[n].pie(fracs, labels = None, autopct='%1.1f%%', startangle=90, colors=my_cmap(my_norm(color_vals)))
axs[n].set_aspect('equal')
for item in autotexts:
item.set_text("")
ax_cb = fig.add_axes([.9,.25,.03,.5])
cb = mpl.colorbar.ColorbarBase(ax_cb, cmap=my_cmap, norm=my_norm, ticks=color_vals)
cb.set_label('Some label [-]')
cb.set_ticklabels(['One', 'Two', 'Three'])
I have added custom ticklabels just to show how that would work, to get the default values simply remove the last line.